10-2-다.NULL 포인터

NULL 포인터는 0으로 정의되어 있는 포인터 상수값이다. 아주 특수한 시스템에서는 0이 아닐 수도 있지만 일반적으로 0이라고 생각하면 큰 무리가 없다. stdio.h 헤더 파일을 보면 다음과 같은 매크로 정의문을 볼 수 있다. 0이라는 상수보다는 좀 더 쉽게 구분되고 의미를 명확히 표현할 수 있는 NULL이라는 명칭의 매크로 상수를 쓰는 것이 좋다.

 

#define NULL 0

 

어떤 포인터 변수가 NULL값을 가지고 있다면 이 포인터는 0번지를 가리키고 있는 것이다. 0번지라면 메모리 공간의 제일 처음에 해당하는 첫 번째 바이트인데 이 위치도 분명히 실존하는 메모리 공간이므로 포인터가 0번지를 가리킬 수도 있다. 그러나 대부분의 플랫폼에서 0번지는 ROM이거나 시스템 예약 영역에 해당되므로 응용 프로그램이 이 번지에 어떤 값을 저장하거나 읽을 수 없도록 보호되어 있다.

시스템 영역에 응용 프로그램이 고유의 데이터를 저장할 수는 없으므로 포인터 변수가 0번지를 가리키는 상황은 발생할 수 없다. 그래서 이런 상황은 일종의 에러로 간주되며 그렇게 하기로 약속되어 있다. 포인터를 리턴하는 거의 대부분의 함수는 에러가 발생했을 때 NULL값을 리턴한다. strchr 함수는 문자열에서 특정 문자를 검색하여 발견된 위치를 리턴하는데 예를 들어 문자열 str에서 문자 r의 위치를 찾고 싶다면 다음과 같이 호출한다.

 

pos=strchr(str,'r')

 

str에 'r'이 있으면 그 번지를 찾아 pos에 대입하는데 만약 찾는 문자가 없으면 에러를 의미하는 특이값 NULL을 리턴한다. 이 함수가 NULL을 리턴했다는 것은 찾는 문자가 없다는 뜻이지 찾는 문자가 0번지에 있다는 뜻이 아니다. 그래서 이런 함수를 호출할 때는 리턴값이 NULL인지 아닌지를 항상 점검해 보고 NULL을 에러로 해석하여 적절한 처리를 할 필요가 있다. 포인터를 리턴하는 함수를 호출하는 구문은 일반적으로 다음과 같이 작성한다.

 

if (func()==NULL) {

     // 에러 처리

} else {

     // 하고 싶은 일

}

 

함수의 리턴값이 NULL인지 아닌지 점검해 보고 NULL이 아닐 때만 원하는 작업을 하며 에러 발생시 적절하게 에러 처리해야 한다. 에러 처리가 필요없다면 if (func()) { 하고 싶은 일 } 형태로 좀 더 간단하게 쓸 수도 있다. 만약 리턴값을 점검하지 않고 리턴된 NULL을 0번지로 해석하여 이 영역을 읽거나 쓰게 되면 프로그램은 곧바로 다운되어 버린다. 왜냐하면 0번지는 응용 프로그램이 절대로 건드려서는 안되는 시스템 영역이기 때문이다. 다음 예제는 문자열에서 특정 문자를 찾아 다른 문자로 바꾸는데 안전하게 에러 점검을 하고 있다.

 

: NullTest

#include <Turboc.h>

 

void main()

{

     char str[]="korea";

     char *p;

 

     p=strchr(str,'r');

     if (p != NULL) {

          *p='s';

     }

     puts(str);

}

 

"korea"라는 문자열에서 'r'을 찾아 's'로 변경하되 'r'이 발견되지 않으면 아무 것도 하지 않도록 했다. 만약 NULL 점검을 하지 않고 'z'를 찾아 's'로 바꾸려고 한다면 0번지를 잘못 건드리는 오동작을 할 가능성이 있다.

ptr=1234처럼 포인터에 상수 번지를 대입한다거나 포인터를 정수 상수와 비교하는 것은 허락되지 않는다. 왜냐하면 응용 프로그램 수준에서 절대 번지를 프로그래밍해야 할 경우가 없으며 반드시 운영체제가 제공한 위치의 메모리만을 사용할 수 있기 때문이다. 만약 꼭 그렇게 하려면 ptr=(char *)1234; 식으로 캐스트 연산자를 통해 강제로 대입할 수는 있겠지만 일반적이지 않다. 하드웨어를 직접 다루는 디바이스 드라이버 정도 되어야 절대 번지를 사용할 일이 있을 것이다.

포인터와 상수를 직접 연산할 수 없다는 것은 쉽게 이해가 되는데 이 규칙의 예외가 존재한다. 바로 NULL이다. NULL은 실제로 0으로 정의된 정수 상수이지만 이 상수는 아주 특별하게도 포인터 변수와 직접적인 연산이 허용된다. ptr=NULL;이라는 대입문은 ptr을 무효화시키며 if (ptr == NULL)이라는 비교 연산문은 ptr이 무효한지 아닌지를 검사하는 적법한 문장이다.