if 성공/실패 선택
try 성공 고정 catch
if 실패만 표현하고 싶은 경우
1. return 0;
성공은 return 0; ISO(국제표준화기구)에서 지정한, 성공에 대한 반환값은 0이다.
대표적으로 main( ) 함수 실행 성공에 대해 return 0; 이다. 그리고 대다수의 함수에서 성공값이 0이다.
고로 return 0을 반환시 "성공"을 기준으로 코드 구조를 구현할 당위성이 충분하다.
2. return 0; 과 논리 연산
논리 연산에서 True와 False의 의미는 아래와 같다.
True (참) = 0을 제외한 모든 값이다.
False (거짓) = 0
여기서 0을 부정, 거짓, 실패로 인식하여 return 0을 실패로 혼동하는 문제가 발생하는 것 같다.
이런 문제는 if( ) 사용시, 제어문의 흐름까지 혼동시킨다.
3. 우선적으로 유용성 관점에서 고민해 볼 부분
// 함수와 반환값 예시
int socket(int domain, int type, int protocol)
// 성공시 디스크립터 반환, 실패시 -1 반환
int bind(int sockfd, struct sockaddr *myaddr, socklen_t adrlen);
// 성공시 0, 실패시 -1
ssize_t write(int fd , const void *buf , size_t nbytes);
// 성공시 전달한 바이트 수, 실패시 -1 반환
단순히 성공에 대한 식별값이 여러가지 종류일 필요가 있을까?
bind( ) 함수와 같이 0과 -1을 통한 성공과 실패의 단순 식별만 필요하다면, 성공에 대한 식별값은 하나로 충분하다. 여기에서 실패를 살펴보면 실패의 요인을 식별하는것이 더 합당히 필요하다.
성공이 0으로 유일하다면, 실패값은 -1, -2, 3, 4, 여러가지 가능하다. (필요한 리턴값(실패값)이 여러 종류인 경우)
socket( ), write( )함수는 특정 결과값이 반환되는 구조다. (필요한 리턴값(성공값)이 여러 종류인 경우)
4. 기준이 되는 구조
우선, 위 1번의 return 0; 그리고 bind( )함수 기준으로 구현해보자면,
if(0) // 함수 성공, False 대입
return error;
else
return 0; // 성공 처리가 실행된다.
if(-1) // 함수 실패, True 대입
return error; // 실패 처리 실행된다. 실패 식별이 필요한 경우
else
return 0;
5. 혼동 구조
위 2번의 논리 연산에서 발생한 혼동을 바탕으로 socket( ), write( )과 간단히 예로 든다면 다음과 같다. (4번 예시와 비교)
하지만 이는 제어문의 성공과 실패에 대한 위치 구조 자체가 4번과 다르다. (즉, 불규칙적이다.)
// 불규칙적으로 구현된 구조 fd = 4, fd = socket();
if(fd) // 어떤 값이라도 0을 제외한 값이기 때문에
return 0; // 성공값 반환된다.
else
return error; // 0에서만 실행된다.
6. 개선된 구조
위 5번을 아래와 같이 구현한다면, socket( ), write( )를 4번과 동일한 구조의 구현이 가능하다.
// 규칙적 return 구조 fd = 2
if(!fd) // !(True == "0 제외값") 즉, (False == 0)대입
return error;
else
return 0; // 0을 제외한 모든 순간 실행된다.
if(!fd)
return error; // 0 일때만 실행된다.
else
return 0;
7. 결론
함수 실행의 성공값 return 0; 은 논리 연산의 True 가 아니다.
함수 반환값 0은 if 제어문에서 if(0), if(false) 다.
더불어, 구현에 정답은 없다지만 규칙적인 구조는 많은 부분에서 긍정적인 효과를 가져온다.
구조를 if( ) 판단 후가 아닌, if( )에 대입되는 값을 기준으로 구조를 동일하게 규칙적으로 구현 할 수 있다.
단순 성공(0), 실패(-1) 식별이 필요하다면, if(변수), if(함수( ))
특정값 반환이 필요하다면, !연산자를 통해 if(!변수), if(!(함수( )))
if(bind()) // return 0; false
return error;
else
return 0; // bind 성공
if(!(socket())) // !(return 2;) false
return error;
else
return 0; // socket 성공
8. 가독성
// 사실 이 부분이 개인적으로 가장 중요해서 if(0) 사용함
// else가 아래로 내려가 안보이는 코드가 되면, 가독성이 심각하게 떨어짐
if(0)
{
// 실패
}
else
{
// 성공
if(0)
{
// 실패
}
else
{
// 성공
if(0)
{
// 실패
}
else
{
// 성공
}
}
}
8.1 비교
// try ~ catch
try
{
// 성공
if(0)
{
// 실패
}
else
{
// 성공
if(0)
{
// 실패
}
else
{
// 성공
}
}
}
else
{
// 실패
}
9. if(p) & if(p =! null) 문제
질문) NULL이 '\0'과 동일하다는 가정하에, (1)과 (2), 그리고 (3)과 (4)는 동일한 구문일 경우, 아래 (1), (3) 또는 (2), (4) 스타일 선택에 생각해봐야 하는 의미가 있는것인가?
(1)
if (p)
{
/* chunk of code */
}
(2)
if ( p != NULL )
{
/* chunk of code */
}
(3)
while ( p )
{
/* chunk of code */
}
(4)
while ( p != NULL)
{
/* chunk of code */
}
답변 1)
1. C/C++ 는 if(x)를 if(x != 0)로 암묵적으로 변경한다.
한편 0 은 비교 대상이 포인터이면 "null pointer" 로 고려된다.
2. NULL 은 보통 macro 에 (#define 으로) 0 으로 정의되어 전처리기가 NULL 을 모두 0 으로 변경한다.
즉, if (p != NULL) 은 if (p != 0) 으로 컴파일 전에 변경한다.
즉, 보통 컴파일러의 동작 순서는,
if (p) => if (p != 0) => "0 을 null pointer 로 생각하고 pointer 가 null pointer 인지 체크" 의 순서다.
3. 한편 0 을 암묵적으로 null pointer 로 해석하는 문제도 모호한 구석이 있다.
숫자 0 과 "null pointer 상수"는 다르다.
C 에서 같은 기호를 쓰고 있을 뿐, "null pointer 상수" 와 "살아있는 pointer 는 무조건 다른 것" 이지 숫자 0이 아니다.
p != 0 라는 표현을 약속된 null pointer 패턴(예. 0000... (zero bit pattern) or 특정 패턴) 비교하는 것이다.
답변 2)
변수의 경우 (1), (3)
if( msg )
{
}
함수의 경우 (2), (4)
if( getSomething() )
{
}
답변 3)
엄격한 컴파일러를 쓰고, warning을 모두 체크하다보면 2,4 경우가 편하다.
수정을 거듭하다보면 워닝 메시지로 누가 코딩 법칙을 어겼는지 알 수 있다.
출처 : https://kldp.org/node/130179