NULL != 0

Exploring why NULL and 0 are not equivalent in GNU C through pre-processor results and memory analysis.
March 28, 2026

NULL != 0

Reqruies
  • Compiler: x86_64-linux-gnu 7.4.0, x86_64-linux-gnu 4.8.5
  • Assembly: AT&T
  • Code Base: glibc 2.23.90
본 포스트의 모든 내용은 GNU C를 기준으로 작성되었습니다.
흔히 혼용해서 사용하곤 하지만, GNU C에서 NULL0은 엄밀히 말해 동일하지 않습니다.
NULL != 0
그렇다면 우리가 사용하는 NULL의 실체는 무엇일까요?
간단한 샘플 코드를 통해 전처리 단계에서 어떻게 변하는지 확인해 보겠습니다.

What is NULL?

Sample Source Code - null.c
c
Preprocess only result
bash
전처리 결과에서 볼 수 있듯이, GNU C에서 NULL((void *)0)으로 치환됩니다.
NULL == ((void *)0)

Including stdio.h instead of stddef.h

stddef.h 대신 stdio.h를 포함하더라도 결과는 동일합니다.
이는 stdio.h가 내부적으로 stddef.h를 포함하여 NULL 정의를 가져오기 때문입니다.
Content of stdio.h
c

NULL Definition in stddef.h

GCC의 stddef.h에서 NULL이 정의되는 방식을 보면 환경에 따라 차이가 있음을 알 수 있습니다.
NULL definition in stddef.h
c
GNU C++ 컴파일러(__GNUG__가 정의된 경우)에서는 NULL__null이라는 내장 키워드로 정의되며, 표준 C++(__cplusplus 환경)에서는 단순히 0으로 정의됩니다.

Difference in Size

0NULL의 가장 결정적인 차이는 Type에 있습니다.
C 언어에서 0int 리터럴이지만, NULLvoid * 타입입니다.
64비트 환경에서는 이 두 타입의 크기가 서로 다릅니다.
sizeof NULL in 64-bit
c
Size of result
bash
일반적인 대입 연산(char *p = 0;)에서는 컴파일러가 0을 널 포인터 상수로 인식하여 적절히 처리해주지만, 타입을 명시하지 않는 가변 인자 함수(Variadic Function)에서는 이 크기 차이가 심각한 문제를 일으킬 수 있습니다.

Potential Issues with Variadic Functions

가변 인자 함수인 va_arg를 사용하는 예제를 살펴보겠습니다.
Problem with literal 0 in variadic functions
c
expect() 함수는 가변 인자로 char *(8바이트) 타입을 기대합니다.
하지만 마지막 인자로 0을 전달하면 컴파일러는 이를 int(4바이트)로 취급합니다.
x86-64 호출 규약상 스택에는 8바이트 공간이 할당되지만 하위 4바이트만 0으로 채워지게 됩니다.
이후 va_arg(ap, char *)가 스택에서 8바이트를 읽으려고 하면, 정상적으로 채워진 4바이트 데이터와 상위 4바이트의 쓰레기 값이 합쳐져 엉뚱한 포인터 주소로 해석됩니다.
이로 인해 인자 불일치(Argument mismatch)가 발생하고 프로그램은 비정상 종료될 수 있습니다.
Result with gcc-4.8
bash
이 문제를 해결하려면 명시적으로 NULL을 전달하거나 (char *)0으로 캐스팅해야 합니다.
Fixed code
c
참고로, GCC 7 버전 이상의 최신 컴파일러에서는 이러한 실수를 방지하기 위해 인자 전달 시 pushq를 사용하여 8바이트 단위를 보장하는 등 더 똑똑하게 동작하기도 합니다.
Assembly result - gcc-7
bash

man execl Recommendations

execl()과 같은 가변 인자 시스템 호출 함수의 매뉴얼 페이지에서도 인자 리스트의 끝을 알리는 종결자로 반드시 NULL을 사용하며, 이를 (char *)NULL로 캐스팅할 것을 강력히 권고하고 있습니다.
man execl excerpt
Doc

The list of arguments must be terminated by a null pointer, and, since these are variadic functions, this pointer must be cast (char *) NULL.

이는 가변 인자 구조상 컴파일러가 인자의 타입을 추론할 수 없으므로, 프로그래머가 명시적으로 포인터 크기(8바이트)의 널 값을 전달해야 함을 의미합니다.

Conclusion

결론적으로, C 언어에서 NULL0은 문맥에 따라 유사하게 동작할 수 있지만 근본적으로는 타입과 크기가 다릅니다.
특히 가변 인자 함수나 하드웨어 제어와 같이 메모리 레이아웃이 중요한 환경에서는 반드시 이를 구분하여 사용해야 합니다.
언제나 포인터에는 NULL, 정수에는 0이라는 기본 원칙을 지키는 것이 안전한 코드를 만드는 첫걸음입니다.
Jooojub
System S/W engineer
Explore Tags
Series
    Recent Post
    © 2026. jooojub. All right reserved.