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
All content in this post is based on GNU C.
Although they are often used interchangeably, in GNU C, NULL and 0 are strictly speaking not identical.
NULL != 0
So, what exactly is the NULL we use?
Let's check how it changes during the preprocessing stage with simple sample code.

What is NULL?

Sample Source Code - null.c
c
Preprocess only result
bash
As seen in the preprocessing result, in GNU C, NULL is replaced by ((void *)0).
NULL == ((void *)0)

Including stdio.h instead of stddef.h

Even if you include stdio.h instead of stddef.h, the result is the same.
This is because stdio.h internally includes stddef.h to pull in the NULL definition.
Content of stdio.h
c

NULL Definition in stddef.h

Looking at how NULL is defined in GCC's stddef.h, you can see differences depending on the environment.
NULL definition in stddef.h
c
In the GNU C++ compiler (when __GNUG__ is defined), NULL is defined as the built-in keyword __null, while in standard C++ (__cplusplus environments), it is simply defined as 0.

Difference in Size

The most decisive difference between 0 and NULL lies in their Type.
In C, 0 is an int literal, whereas NULL is of type void *.
In a 64-bit environment, the sizes of these two types are different.
sizeof NULL in 64-bit
c
Size of result
bash
In common assignments (e.g., char *p = 0;), the compiler recognizes 0 as a null pointer constant and handles it appropriately. However, in Variadic Functions where types are not explicitly specified, this size difference can cause serious problems.

Potential Issues with Variadic Functions

Let's look at an example using va_arg, a variadic function.
Problem with literal 0 in variadic functions
c
The expect() function expects char * (8 bytes) as variadic arguments.
However, if you pass 0 as the last argument, the compiler treats it as an int (4 bytes).
According to the x86-64 calling convention, an 8-byte space is allocated on the stack, but only the lower 4 bytes are filled with 0.
When va_arg(ap, char *) later tries to read 8 bytes from the stack, the correctly filled 4-byte data combined with the garbage value in the upper 4 bytes is interpreted as an incorrect pointer address.
This leads to an argument mismatch, and the program may terminate abnormally.
Result with gcc-4.8
bash
To resolve this issue, you must explicitly pass NULL or cast it as (char *)0.
Fixed code
c
As a side note, modern compilers like GCC 7 or later handle this more intelligently by using pushq to ensure 8-byte alignment when passing arguments, thereby preventing such mistakes.
Assembly result - gcc-7
bash

man execl Recommendations

The manual pages for variadic system call functions like execl() also strongly recommend using NULL as the terminator for the argument list and casting it as (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.

This means that because the compiler cannot infer the types of variadic arguments, the programmer must explicitly pass a null value of pointer size (8 bytes).

Conclusion

In conclusion, while NULL and 0 might behave similarly depending on the context in C, they are fundamentally different in type and size.
They must be used distinctively, especially in environments where memory layout is critical, such as variadic functions or hardware control.
Adhering to the basic principle of using NULL for pointers and 0 for integers is the first step toward writing safe code.
Jooojub
System S/W engineer
Explore Tags
Series
    Recent Post
    © 2026. jooojub. All right reserved.