The definition of NULL is a syntactic crutch to allow a programmer a clear expression that a pointer is, at certain times, pointing nowhere. It's meant for readability - and with increased compiler bloat even for automated checks.

On a hardware level there is no such thing. A pointer is a word, and a word always holds a valid number. So by convention zero was chosen - it could have been any other. Like -1 for example. Selecting 0 offered the advantage that a simple if(pointer) could be used to check if it's a valid pointer (or more correct, not 0).

So my question is what led some C standard to treat the NULL pointer differently from any other pointer?

C also doesn't treat NULL different from any other pointer. The C library in contrast does, but not because of NULL, but rather as its (runtime) value of 0 will make some functions fail, when used as input.

Did K&R want to target an exotic architecture or something?

No. It's a concept needed from a pure software engineering point of view. Having a syntactic construct to handle uninitialized pointers improves readability and possibly enables further checks.


Now, for the historic part, NULL is, like the related handling of TRUE/FALSE inherited from BCPL (through B). Just here it was called nil.


Even with that little historic bit, I'm not sure if Retrocomputing is the right place to ask for this, as it's about a basic concept in software engineering and not anything burdened with historic implication. So a quick search in Software Engineering and Stack Overflow shows that this question has been asked many times. It's something people always stumble on, isn't it?

Answer from Raffzahn on Stack Exchange
Top answer
1 of 7
26

The definition of NULL is a syntactic crutch to allow a programmer a clear expression that a pointer is, at certain times, pointing nowhere. It's meant for readability - and with increased compiler bloat even for automated checks.

On a hardware level there is no such thing. A pointer is a word, and a word always holds a valid number. So by convention zero was chosen - it could have been any other. Like -1 for example. Selecting 0 offered the advantage that a simple if(pointer) could be used to check if it's a valid pointer (or more correct, not 0).

So my question is what led some C standard to treat the NULL pointer differently from any other pointer?

C also doesn't treat NULL different from any other pointer. The C library in contrast does, but not because of NULL, but rather as its (runtime) value of 0 will make some functions fail, when used as input.

Did K&R want to target an exotic architecture or something?

No. It's a concept needed from a pure software engineering point of view. Having a syntactic construct to handle uninitialized pointers improves readability and possibly enables further checks.


Now, for the historic part, NULL is, like the related handling of TRUE/FALSE inherited from BCPL (through B). Just here it was called nil.


Even with that little historic bit, I'm not sure if Retrocomputing is the right place to ask for this, as it's about a basic concept in software engineering and not anything burdened with historic implication. So a quick search in Software Engineering and Stack Overflow shows that this question has been asked many times. It's something people always stumble on, isn't it?

2 of 7
24

The important thing you may be missing is that a null pointer in C is not required by the standard to have the same binary representation as the number zero. It is still a "normal" pointer, but it points to a special location that the program is not allowed to use.

The integer constant 0 is turned into nullptr when used as a pointer. Similarly, 0 will become 0.0 when used in floating-point calculations, or false in boolean operations. Coercions are not just one-way: if(ptr) will convert a pointer into a boolean which indicates whether the pointer is not null. All of these conversions serve to avoid requiring that a null pointer shares the same representation as zero.

Most machines represent integer zero as all-bits-zero and comparisons against that are particularly cheap, so it is worthwhile to arrange things so that sentinel values such as null pointers and end-of-string markers are all-bits-zero, and also that +0.0 and false are also all-bits-zero.

There is some subtlety in C in that a literal 0 is converted into nullptr, whereas casting an integer that happens to be zero into a pointer will produce a pointer to location zero. Because they are the same thing on modern platforms, a whole class of potential bugs go away.

🌐
Stack Overflow
stackoverflow.com › questions › 76799320 › line-86-char-2-runtime-error-store-to-null-pointer-of-type-std-bit-type
c++ - Line 86: Char 2: runtime error: store to null pointer of type 'std::_Bit_type' (aka 'unsigned long') (stl_bvector.h) - Stack Overflow
I'm trying Sieve of Eratosthenes and trying a problem in leetcode but I'm getting this error. Line 86: Char 2: runtime error: store to null pointer of type 'std::_Bit_type' (aka 'unsigned long') (
Discussions

Dereferencing a NULL pointer always segfaults, right? Not if you're clever...
I love this quote from Linkers and Loaders by John Levine: "Unix on the VAX, the follow-on to the PDP-11, used a similar scheme. The first two bytes of every VAX Unix program were zero (a register save mask saying not to save anything.) As a result, a null all-zero pointer was always valid, and if a C program used a null value as a string pointer, the zero byte at location zero was treated as a null string. As a result, a generation of Unix programs in the 1980s contained hard-to-find bugs involving null pointers, and for many years, Unix ports to other architectures provided a zero byte at location zero because it was easier than finding and fixing all the null pointer bugs." More on reddit.com
🌐 r/programming
79
161
March 31, 2010
What happens in OS when we dereference a NULL pointer in C? - Stack Overflow
Let's say there is a pointer and we initialize it with NULL. int* ptr = NULL; *ptr = 10; Now , the program will crash since ptr isn't pointing to any address and we're assigning a value to that , ... More on stackoverflow.com
🌐 stackoverflow.com
Null Pointer Dereferencing Causes Undefined Behavior
Saying that it has "undefined behavior" means that the C language standard says nothing about how it behaves. If its behavior for the current compiler happens to satisfy the language requirements for offsetof, then it's a legitimate implementation. Code that implements the standard library ... More on news.ycombinator.com
🌐 news.ycombinator.com
92
82
April 22, 2015
In C++, does dereferencing a nullptr itself cause undefined behaviour, or is it the acting upon the dereferenced pointer which is undefined? - Software Engineering Stack Exchange
I happen to have a reason why I might want to dereference a nullptr. Of course when I do, my program crashes, and from what I gather, this is due to the compiler playing it safe and stopping my pro... More on softwareengineering.stackexchange.com
🌐 softwareengineering.stackexchange.com
August 3, 2021
🌐
STMicroelectronics Community
community.st.com › stmicroelectronics community › discussions › product forums › stm32 mcus › stm32 mcus products › trap null pointer dereference
Trap NULL pointer dereference | Community
October 29, 2020 - In order to catch NULL pointer dereference defects, we shall configure the MPU to no access (or read-only) for the whole ITCMR_region (or the first 1024 bytes).
🌐
Lobsters
lobste.rs › s › e1kzwp › undefined_behavior_purpose_c
undefined behavior and the purpose of C | Lobsters
January 5, 2017 - (As a reader used to an architecture on which division by zero traps and an architecture which reorders stores, I would say your code is unsafe on its face and the compiler is not being unreasonable here) The standard could say: derefencing a null pointer has an effect undefined by the C language that depends on the semantics of the address space in which your code executes.
🌐
How Not To Code
hownot2code.wordpress.com › 2016 › 08 › 18 › null-pointer-dereferencing-causes-undefined-behavior
Null Pointer Dereferencing Causes Undefined Behavior | How Not To Code
September 13, 2021 - If a null pointer constant is converted to a pointer type, the resulting pointer, called a null pointer, is guaranteed to compare unequal to a pointer to any object or function.
🌐
Reddit
reddit.com › r/programming › dereferencing a null pointer always segfaults, right? not if you're clever...
r/programming on Reddit: Dereferencing a NULL pointer always segfaults, right? Not if you're clever...
March 31, 2010 - Address 0 was valid - the x86 interrupt descriptor table lived there. Actually address 0 was the divide by zero vector. So dereferencing a NULL pointer was perfectly OK. I wrote some code that trapped NULL pointer accesses using the x86 debug registers and they were actually really frequent.
Top answer
1 of 5
76

Short answer: it depends on a lot of factors, including the compiler, processor architecture, specific processor model, and the OS, among others.

Long answer (x86 and x86-64): Let's go down to the lowest level: the CPU. On x86 and x86-64, that code will typically compile into an instruction or instruction sequence like this:

movl $10, 0x00000000

Which says to "store the constant integer 10 at virtual memory address 0". The Intel® 64 and IA-32 Architectures Software Developer Manuals describe in detail what happens when this instruction gets executed, so I'm going to summarize it for you.

The CPU can operate in several different modes, several of which are for backwards compatibility with much older CPUs. Modern operating systems run user-level code in a mode called protected mode, which uses paging to convert virtual addresses into physical addresses.

For each process, the OS keeps a page table which dictates how the addresses are mapped. The page table is stored in memory in a specific format (and protected so that they can not be modified by the user code) that the CPU understands. For every memory access that happens, the CPU translates it according to the page table. If the translation succeeds, it performs the corresponding read/write to the physical memory location.

The interesting things happen when the address translation fails. Not all addresses are valid, and if any memory access generates an invalid address, the processor raises a page fault exception. This triggers a transition from user mode (aka current privilege level (CPL) 3 on x86/x86-64) into kernel mode (aka CPL 0) to a specific location in the kernel's code, as defined by the interrupt descriptor table (IDT).

The kernel regains control and, based on the information from the exception and the process's page table, figures out what happened. In this case, it realizes that the user-level process accessed an invalid memory location, and then it reacts accordingly. On Windows, it will invoke structured exception handling to allow the user code to handle the exception. On POSIX systems, the OS will deliver a SIGSEGV signal to the process.

In other cases, the OS will handle the page fault internally and restart the process from its current location as if nothing happened. For example, guard pages are placed at the bottom of the stack to allow the stack to grow on demand up to a limit, instead of preallocating a large amount of memory for the stack. Similar mechanisms are used for achieving copy-on-write memory.

In modern OSes, the page tables are usually set up to make the address 0 an invalid virtual address. But sometimes it's possible to change that, e.g. on Linux by writing 0 to the pseudofile /proc/sys/vm/mmap_min_addr, after which it's possible to use mmap(2) to map the virtual address 0. In that case, dereferencing a null pointer would not cause a page fault.

The above discussion is all about what happens when the original code is running in user space. But this could also happen inside the kernel. The kernel can (and is certainly much more likely than user code to) map the virtual address 0, so such a memory access would be normal. But if it's not mapped, then what happens then is largely similar: the CPU raises a page fault error which traps into a predefined point at the kernel, the kernel examines what happened, and reacts accordingly. If the kernel can't recover from the exception, it will typically panic in some fashion (kernel panic, kernel oops, or a BSOD on Windows, e.g.) by printing out some debug information to the console or serial port and then halting.

See also Much ado about NULL: Exploiting a kernel NULL dereference for an example of how an attacker could exploit a null pointer dereference bug from inside the kernel in order to gain root privileges on a Linux machine.

2 of 5
6

As a side note, just to compel the differences in architectures, a certain OS developed and maintained by a company known for their three-letter acronym name and often referred to as a large primary color has a most-fasicnating NULL determination.

They utilize a 128-bit linear address space for ALL data (memory AND disk) in one giant "thing". In accordance with their OS, a "valid" pointer must be placed on a 128-bit boundary within that address space. This, btw, causes fascinating side effects for structs, packed or not, that house pointers. Anyway, tucked away in a per-process dedicated page is a bitmap that assigns one bit for every valid location in a process address space where a valid pointer can lay. ALL opcodes on their hardware and OS that can generate and return a valid memory address and assign it to a pointer will set the bit that represents the memory address where that pointer (the target pointer) is located.

So why should anyone care? For this simple reason:

int a = 0;
int *p = &a;
int *q = p-1;

if (p)
{
// p is valid, p's bit is lit, this code will run.
}

if (q)
{
   // the address stored in q is not valid. q's bit is not lit. this will NOT run.
}

What is truly interesting is this.

if (p == NULL)
{
   // p is valid. this will NOT run.
}

if (q == NULL)
{
   // q is not valid, and therefore treated as NULL, this WILL run.
}

if (!p)
{
   // same as before. p is valid, therefore this won't run
}

if (!q)
{
   // same as before, q is NOT valid, therefore this WILL run.
}

Its something you have to see to believe. I can't even imagine the housekeeping done to maintain that bit map, especially when copying pointer values or freeing dynamic memory.

🌐
Medium
medium.com › @gokulraaj › the-null-pointer-exception-trap-how-to-escape-b62b63a2796a
The Null Pointer Exception Trap: How to Escape | by Gokulraaj | Medium
September 15, 2024 - Discover the hidden dangers of Null Pointer Exceptions (NPEs) in Java development. Learn how to identify, prevent, and debug these common errors to avoid costly mistakes and improve your code's reliability. Explore best practices for writing robust and error-free Java code.
Find elsewhere
🌐
Hacker News
news.ycombinator.com › item
Null Pointer Dereferencing Causes Undefined Behavior | Hacker News
April 22, 2015 - Saying that it has "undefined behavior" means that the C language standard says nothing about how it behaves. If its behavior for the current compiler happens to satisfy the language requirements for offsetof, then it's a legitimate implementation. Code that implements the standard library ...
Top answer
1 of 5
3

It is not the compiler that causes your program to crash on dereferencing a null pointer. The problem is that the pointer is pointing to memory that it is illegal to reference, and the operating system kills your program for invalid behavior.

Trying to trick the compiler by obfuscating that it is a null pointer won't work, because it isn't the compiler that detects it.

There is no legitimate reason to dereference a null pointer unless you on a rare system that maps page zero (or you intend your program to crash). It is generally accepted that zeroing a pointer is a good way to mark it as invalid and dereferencing an invalid pointer is a bug. Modern operating systems do not give you a page of memory at that address specifically to make debugging invalid pointers easier.

I would not even call your program crashing from this to be undefined behavior. Dereferencing a pointer with random data in it would give you undefined behavior. Dereferencing a pointer that contains an address not assigned to your program is quite well defined in demand paged memory protected operating systems, and the behavior defined by the operating system is for your program to crash. From the language's perspective, it is still undefined behavior, because what happens is not defined in the scope of the language. Since this behavior is undefined by the language, the compiler can do nothing about it and should do nothing about it.

The exception to this is systems that have no memory protection and systems that intentionally map page zero. Some older systems do this, but most of the modern systems that do are microcontrollers, some of which might even have memory mapped I/O or some other special purpose memory in page zero.

Since null pointer dereferences are typically bugs, it is unlikely a compiler would bother to optimize away null pointer dereferences or put guard code around a possible one, as this would not improve code performance. If they did even bother to detect this, they would do it to emit a warning to assist you in debugging, similar to the "code not reachable" warning. The only reason for the compiler to generate different code around one would be if it knew what you were trying to do.

2 of 5
9

You seem to have a misunderstanding of what Undefined Behavior means.

Undefined Behavior is not something that is "caused" by your code. It is not something that happens. It is something that is.

If you have some piece of code somewhere that dereferences a null pointer, that is Undefined Behavior. UB gives the compiler a lot of leeway.

The way this is usually phrased is that the compiler is allowed to do anything. It is allowed to compile code that dereferences a null pointer into code that formats your hard disk. It is allowed to compile it into code that crashes. It is allowed to compile it into code that does random things. It is even allowed to compile it into code that doesn't crash.

And until a couple of years ago, that's mostly what compilers did. However, that isn't even the most dangerous part.

There is one thing the compiler is also allowed to do: because you are not allowed to write code that exhibits UB, the compiler is allowed to assume that there will be no UB, when optimizing your code. And because of the complex optimizations that modern compilers do, this can have very weird consequences.

Let's say you have an if (userId == 0) statement, where you have UB in the else part. Since you are not allowed to write code that exhibits UB, the compiler is allowed to assume that the else branch will never be taken. This means that the compiler is allowed to assume that userId will always be 0, i.e. it is allowed to assume that the user is always root! And based on this assumption, it is allowed to optimize away other checks as well, opening you up to huge security holes.

This can lead to very extreme, or even worse, very subtle changes to the behavior of program parts far away from the place of the UB.

Top answer
1 of 7
20

A point that most of the answers here are not addressing, at least not explicitly, is that a null pointer is a value that exists during execution, and a null pointer constant is a syntactic construct that exists in C source code.

A null pointer constant, as Karlson's answer correctly states, is either an integer constant expression with the value 0 (a simple 0 is the most common example), or such an expression cast to void* (such as (void*)0).

NULL is a macro, defined in <stddef.h> and several other standard headers, that expands to an implementation-defined null pointer constant. The expansion is typically either 0 or ((void*)0) (the outer parentheses are needed to satisfy other language rules).

So a literal 0, when used in a context that requires an expression of pointer type, always evaluates to a null pointer, i.e., a unique pointer value that points to no object. That does not imply anything about the representation of a null pointer. Null pointers are very commonly represented as all-bits-zero, but they can be represented as anything. But even if a null pointer is represented as 0xDEADBEEF, 0 or (void*)0 is still a null pointer constant.

This answer to the question on stackoverflow covers this well.

This implies, among other things, that memset() or calloc(), which can set a region of memory to all-bits-zero, will not necessarily set any pointers in that region to null pointers. They're likely to do so on most implementations, perhaps even all existing ones, but the language doesn't guarantee it.

This question is really a duplicate of this one, but Stack Exchange doesn't allow marking duplicates across sites.

2 of 7
7

On most CPU architectures, which most likely includes whatever CPU architecture you are working on, a pointer pointing to 0x0000 is the exact same thing as a pointer set to NULL.

HOWEVER:

  • The C standard allows every platform out there to define the internal representation of NULL as it pleases.

  • The C standard says that if you assign zero to a pointer it will be converted to a NULL value for that platform, but if you take a NULL pointer and cast it to int, there are no guarantees that you will get zero back on every platform out there.

So, some hypothetical AcmePC CPU architecture might find it particularly convenient to represent NULL internally as 0x0badf00d, so assigning zero to a pointer will make it point to address 0x0badf00d, and casting that pointer back to int may yield 0x0badf00d.

I do not know of any architecture that works in such a bizarre way, but then again, the standard allows it, and I do not know every single CPU architecture in existence. So:

  • If you do not care at all about portability, then take NULL == 0 for granted.

  • If you care a little bit about portability, but are willing to leave out some systems which you have never heard of, then, again, go ahead and take NULL == 0 for granted.

  • But if you really care for portability, then go by what the standard says and only by what the standard says, and consider NULL as something completely unrelated to 0.

For more information you can look at The C Language Specification.

🌐
Vt
prolangs.cs.vt.edu › refs › docs › kawahito-asplos00.pdf pdf
Effective Null Pointer Check Elimination Utilizing Hardware Trap
In this paper, we have presented a new algorithm for null pointer · check elimination, which has been implemented in the IBM Java · Just-in-Time compiler. The architecture independent optimization · moves null checks backward, and it is iterated for a few times · with other optimizations to eliminate redundant null checks. This · optimization maximizes the effectiveness of other optimizations. Then the architecture dependent optimization converts null checks · to hardware traps in order to minimize the execution cost of null ·
Top answer
1 of 12
75

There's no such thing as "null pointer exception" in C++. The only exceptions you can catch, is the exceptions explicitly thrown by throw expressions (plus, as Pavel noted, some standard C++ exceptions thrown intrinsically by standard operator new, dynamic_cast etc). There are no other exceptions in C++. Dereferencing null pointers, division by zero etc. does not generate exceptions in C++, it produces undefined behavior. If you want exceptions thrown in cases like that it is your own responsibility to manually detect these conditions and do throw explicitly. That's how it works in C++.

Whatever else you seem to be looking for has noting to do with C++ language, but rather a feature of particular implementation. In Visual C++, for example, system/hardware exceptions can be "converted" into C++ exceptions, but there's a price attached to this non-standard functionality, which is not normally worth paying.

2 of 12
30

You cannot. De-referencing a null-pointer is a system thing.

On Linux, the OS raises signals in your application. Take a look at csignal to see how to handle signals. To "catch" one, you'd hook a function in that will be called in the case of SIGSEGV. Here you could try to print some information before you gracefully terminate the program.

Windows uses structured-exception-handling. You could use the instristics __try/__except, as outlined in the previous link. The way I did it in a certain debug utility I wrote was with the function _set_se_translator (because it closely matches hooks). In Visual Studio, make sure you have SEH enabled. With that function, you can hook in a function to call when the system raises an exception in your application; in your case it would call it with EXCEPTION_ACCESS_VIOLATION. You can then throw an exception and have it propagate back out as if an exception was thrown in the first place.

🌐
Wikipedia
en.wikipedia.org › wiki › Null_pointer
Null pointer - Wikipedia
June 13, 2026 - In C, two null pointers of any type are guaranteed to compare equal. The preprocessor macro NULL is provided, defined as an implementation-defined null pointer constant in <stdlib.h>, which in C99 can be portably expressed with #define NULL ((void*)0), the integer value 0 converted to the type ...
🌐
ACM SIGPLAN Notices
dl.acm.org › doi › 10.1145 › 356989.357002
Effective null pointer check elimination utilizing hardware trap | ACM SIGPLAN Notices
We present a new algorithm for eliminating null pointer checks from programs written in Java&trade;. Our new algorithm is split into two phases. In the first phase, it moves null checks backward, and it is iterated for a few times with other optimizations to eliminate redundant null checks and maximize the effectiveness of other optimizations. In the second phase, it moves null checks forward and converts many null checks to hardware traps ...
🌐
GitHub
github.com › llvm › llvm-project › issues › 64383
Thrown null pointer cannot be caught with base-class catch · Issue #64383 · llvm/llvm-project
August 3, 2023 - There are a standard pointer conversion not involving conversions to pointers to private or protected or ambiguous classes: E --- C --- A However it caught by catch(...), which clearly is a bug. For the above scenario, a null pointer of type E is thrown, according to the implementation of ...
Author: llvm
🌐
Hacker News
news.ycombinator.com › item
> - "Undefined behavior" means that C implementations are allowed to assume that... | Hacker News
May 21, 2021 - Please note that the article is making the specific argument that this interpretation of UB is an incorrect interpretation. The author is arguing that you, me, the llvm and gcc teams are wrong to interpret UB that way · Linux had a bug in it a few years ago; the code would dereference a pointer, ...
🌐
Clang
clang.llvm.org › docs › UndefinedBehaviorSanitizer.html
UndefinedBehaviorSanitizer - Clang
The check is not a part of the undefined group. Also it does not support -fsanitize-trap=vptr. ... -fsanitize=undefined: All of the checks listed above other than float-divide-by-zero, unsigned-integer-overflow, implicit-conversion, local-bounds, vptr and the nullability-* group of checks.
Top answer
1 of 1
5

Refinement Types

NonNull is a function of type (a: PointerType) → { n ∈ a | n ≠ NULL }.
Refinement types are types whose inhabitants may be described by predicates. Other examples include

  • Positive (a: Numeric) = { n ∈ a | n ⩾ 0 }
  • Even (a: Numeric) = { n ∈ a | rem n 2 = 0 }
  • Identifier = { s ∈ String | matches s "/[_\p{L}][_\p{L}\p{N}\xB7]*/" }

Languages supporting this include LiquidHaskell and Scala.
Dependent Types are another similar and more general candidate supported by Idris, Agda, Coq, F* and ATS.

Many languages also limit support to "non-null pointer" types, including Rust, Scala and Haskell, as well as other kinds of pointers as in Cyclone.

Automatic Type Narrowing, or Smart Casts

This is the ability to infer narrowing from supertypes to subtypes from context - in your example, from control flow analysis.
In particular, automatic narrowing to a refinement type requires proving the context satisfies that type's predicate. How "smart" a compiler has to be then depends on what kind of refinements you allow: builtin non-null pointer support is trivial compared to arbitrary predicates.

This feature is notably popular among Object-Oriented languages such as Kotlin and Ceylon for the gained ability to downcast object references based on conditions.
eg.

base class A {}
class B : A { int x; }
class C : A { float y; }
fn f(o: A)
{
    if(o is B) {
        println(o.x + 2);
    }
    else if(o is C) {
        println(o.y.ceil());
    }
}

Ceylon goes further with its if(is), if(exists) and if(nonempty) operations.
TypeScript is another popular language supporting this with the goal of representing dynamic type checks in ECMAScript, and therefore supports a range of narrowing checks such as x instanceof y, "x" in y, or x.hasOwnProperty("y"), and extends them to downcasting of union types.

Segmentation Fault (core dumped)

NULL is a trap representation for nonnull pointers. That is, if a pointer qualified with nonnull contains NULL and is read, immediate undefined behavior is invoked.

As far as actually dereferencing NULL, C already works exactly how you are describing it.
That said, note that C does not actually require NULL to represent virtual address 0, and on systems where that address is mappable, "undefined behaviour" is to be understood literally: segmentation faults and NullPointerExceptions may not exist, and only nasal demons are guaranteed.

Meanwhile Zig offers a precedent where null is required to be address 0, and where special allowzero pointer types have to carry an additional flag to represent null.