Menu

C 언어 NULL 포인터: NULL의 의미와 검사하는 법

NULL은 포인터가 아무것도 가리키지 않을 때 담는 주소입니다. NULL이 진짜 무엇인지, 역참조하면 왜 죽는지, 0이나 초기화되지 않은 포인터와 어떻게 다른지, 그리고 널 버그를 코드에서 몰아내는 방어 패턴을 다룹니다.

이 페이지에는 실행 가능한 에디터가 있습니다 - 편집하고 실행하면 결과를 바로 볼 수 있습니다.

포인터는 항상 어떤 값을 담고 있습니다. 아직 가리킬 객체가 없을 때 - 대입되기 전, 가리키던 것이 해제된 뒤, 또는 함수가 돌려줄 것이 없을 때 - "여기엔 아무것도 없다"를 분명히 뜻하는 값이 필요합니다. 그 값이 널 포인터이고, 여러분이 쓰는 이름이 NULL입니다.

이를 둘러싼 규율은 작고, 끊임없이 값을 합니다. 가리킬 대상이 없으면 포인터를 NULL로 두고, 역참조하기 전에 NULL인지 확인하고, 결과가 없으면 NULL을 반환하세요.

NULL은 실제로 무엇인가

NULL<stddef.h>에 정의되어 있고 <stdio.h>, <stdlib.h>, <string.h> 등에서도 딸려 오는 매크로입니다. 널 포인터 상수로 확장되는데, 보통 ((void*)0)이거나 그냥 0입니다.

C 표준이 보장하는 중요한 성질은 이렇습니다. 널 포인터는 어떤 실제 객체나 함수를 가리키는 포인터와도 같지 않습니다. 같은 타입의 널 포인터 둘은 언제나 서로 같습니다.

대부분의 시스템은 널 포인터에 대해 (nil)이나 0x0, 즉 주소 0을 출력합니다. 그것은 흔한 구현이지 규칙이 아닙니다. 표준은 비트가 0이라고 약속한 적이 없습니다. 약속하는 것은 비교이므로, p == NULL이라고 쓰고 표현 방식에 의존하는 것은 절대 쓰지 마세요.

NULL 역참조는 죽는다

세상의 모든 널 검사가 존재하는 이유입니다.

int *p = NULL;
printf("%d\n", *p);   // 미정의 동작 - 거의 확실히 크래시

윈도우, macOS, 리눅스에서 주소 공간의 첫 페이지는 일부러 매핑되지 않은 채 남겨지므로, CPU가 접근을 잡아채고 운영체제가 프로그램을 죽입니다. 이런 것을 보게 됩니다.

Segmentation fault (core dumped)          # Linux
zsh: segmentation fault  ./program        # macOS

그 즉각적인 크래시는 기능입니다. 3주 뒤에 발견하는 조용한 손상 대신, 논리 버그를 그것을 일으킨 바로 그 줄에서 요란한 실패로 바꿔 줍니다. 크래시를 읽고 그 줄을 찾는 방법은 세그멘테이션 폴트를 보세요.

그래서 C에서 "널 포인터"는 잡아낼 수 있는 예외가 아닙니다. 복구할 메커니즘이 없고, 미리 검사하는 것만 있습니다.

실패할 수 있는 것을 확인하라

실패할 수 있는 함수는 NULL을 반환해 그것을 알립니다. 확인은 선택적인 예의가 아닙니다. 대안은 크래시입니다.

표준 라이브러리에는 이런 것이 가득합니다. malloc, calloc, realloc, fopen, strchr, strstr, getenv, bsearch 전부가 "결과 없음"을 NULL로 답합니다. 할당에 관한 이야기 전체는 동적 메모리를 보세요.

검사하는 관용구는 둘 다 흔합니다.

if (p == NULL) { /* 처리 */ }   // 명시적 - 명확해서 선호됨
if (!p)        { /* 처리 */ }   // 간결 - NULL이 거짓이라는 데 기댐

널 포인터는 조건에서 거짓으로, 유효한 포인터는 참으로 검사되므로 둘 다 동작합니다. 낯선 사람이 버그를 찾으려고 코드를 훑을 때는 명시적인 형태가 읽기 좋습니다.

NULL과 0과 초기화되지 않은 포인터

초보자가 뭉뚱그리는 세 가지 다른 상황입니다.

int *a = NULL;     "아무것도 가리키지 않음" - 검사 가능, 확인해도 안전
int *b;            초기화 안 됨 - 쓰레기 값, 검사 불가능
int *c = &x;       실제 객체를 가리킴

위험한 것은 b입니다. 그 바이트는 그 스택 자리에 남아 있던 무엇이고, 0일 수도 있고(멀쩡해 보이겠죠) 앞선 호출이 남긴 그럴듯한 주소일 수도 있습니다. 어떤 검사도 유효한 포인터와 구별하지 못하고, 디버그 빌드와 릴리스 빌드에서 동작이 달라집니다.

모든 포인터를 초기화하세요. 아직 주소가 없다면 NULL이 그 주소입니다. -Wall -Wextra로 빌드하면 초기화되지 않은 사용을 컴파일러가 많이 짚어 줍니다.

0에 대해서는, 포인터 문맥에서 정수 상수 0이 널 포인터 상수 그 자체이므로 p = 0;도 올바른 C입니다. 그래도 NULL을 선호하세요. 의도를 드러내고, 컴파일러가 변환해 줄 수 없는 가변 인자 호출에서 중요합니다.

execl("/bin/ls", "ls", 0);       // 위험 - 포인터를 기대하는 자리에 int가 갈 수 있다
execl("/bin/ls", "ls", (char *)NULL);   // 올바름

C23은 이 부류의 모호함을 통째로 피하는 자기 타입을 가진 키워드 nullptr을 추가합니다. NULL은 어디서나 여전히 올바르고 이식성이 있습니다.

방어 패턴

포인터를 받는 함수의 맨 위에서 지키세요.

해제한 뒤에는 NULL로 설정하세요. free는 여러분의 포인터를 바꾸지 않습니다. 메모리를 놓아줄 뿐입니다. 남겨진 낡은 포인터가 댕글링 포인터이고, 그것을 쓰는 것은 종종 곧바로 죽지 않는 미정의 동작입니다.

이 예제 안의 두 사실은 외워 둘 만합니다. free(NULL)은 아무 일도 하지 않도록 정의되어 있으므로 정리 코드에서 굳이 지킬 필요가 없습니다. 그리고 free 뒤에 p = NULL로 두면, 조용히 데이터를 망가뜨릴 수 있는 해제 후 사용을 즉각적이고 디버깅 가능한 크래시로 바꿔 줍니다.

"결과 없음"을 뜻하려면 NULL을 반환하고, 그렇다고 말해 두세요.

// 일치하는 원소의 포인터를 반환하고, 없으면 NULL을 반환한다
int *find(int *arr, int n, int target);

함수 위 주석에 적어 두세요. NULL이 가능하다는 것을 아는 호출자는 검사를 쓰고, 모르는 호출자는 쓰지 않습니다.

빠른 체크리스트

  • 모든 포인터를 실제 주소나 NULL로 초기화하세요.
  • malloc, calloc, realloc, fopen의 반환값을 쓰기 전에 확인하세요.
  • 잘못된 입력으로 호출될 수 있는 함수의 맨 위에서 포인터 매개변수를 확인하세요.
  • free 직후에 포인터를 NULL로 설정하세요.
  • 포인터를 뜻할 때는 0이 아니라 NULL이라고 쓰세요.
  • -Wall -Wextra로 빌드하고, 가능하면 새니타이저 아래에서 실행하세요. gcc -fsanitize=address,undefined가 널 역참조를 정확한 보고와 함께 잡아 줍니다.

자주 묻는 질문

C에서 NULL 포인터란 무엇인가요?

널 포인터 상수를 담고 있는 포인터입니다. 어떤 실제 객체의 주소와도 같지 않음이 보장되는 값이죠. "이 포인터는 아직 아무것도 가리키지 않는다"를 말하는 표준적인 방법이고, NULL이 그것을 위한 매크로로 <stddef.h>와 여러 헤더에 정의되어 있습니다.

C에서 NULL 포인터를 역참조하면 어떻게 되나요?

미정의 동작입니다. 실제로는 데스크톱과 서버 시스템에서 주소 0이 일부러 매핑되지 않은 채 남겨져 있으므로 즉시 세그멘테이션 폴트로 죽습니다. 메모리 보호가 없는 임베디드 시스템에서는 대신 조용히 무언가를 읽거나 망가뜨릴 수 있는데, 훨씬 나쁩니다.

C에서 NULL은 0과 같나요?

포인터 문맥에서는 그렇습니다. 정수 상수 0이 널 포인터 상수이므로 p = 0;p == NULL 모두 동작합니다. 하지만 포인터에는 NULL을, 숫자에는 0을 쓰세요. 둘 중 무엇을 뜻했는지 읽는 사람에게 알려 주고, 맨 0이 포인터가 아닌 int로 전달될 수 있는 가변 인자 호출을 안전하게 지켜 줍니다.

NULL 포인터와 초기화되지 않은 포인터의 차이는 무엇인가요?

NULL 포인터는 확실히 아무것도 가리키지 않고, 검사할 수 있습니다. 초기화되지 않은 포인터는 그 메모리에 있던 바이트를 그대로 담고 있어 유효해 보이는 주소일 수도 있으므로, 잡아낼 검사가 없습니다. 더 나은 값이 없을 때는 언제나 포인터를 NULL로 초기화하세요.

Coddy programming languages illustration

Coddy로 코딩 배우기

시작하기