Reputation: 53
I am debugging a crash where we have a code snippet similar to -
1184 static void
1185 xyz_delete (<struct type1> *c, <struct type2> **a)
1186 {
...
...
...
...
1196 b = *a;
1197 if (!b) {
1198 return;
1199 }
...
...
1203 prev = b->next;
1204 b->next = NULL;
...
...
1245 free_timer(b->active_timer);
...
...
...
}
And we happened to see a crash - segmentation fault; whose callstack is shown below -
#1 0x456789123 in __free [__be___free] (ptr=<optimized out>, saved_caller_pc=0x123456789 , attr=0x0) at free.c:1234
#2 0x345678912 in xyz_delete [__be_xyz_delete...] (c=c@entry=0x234567891, a=a@entry=0x0) at myfile.c:1245
#3 0x455678912 in abc (apple=0x52453545, a=<optimized out>, hello=12) at myfile:1312
From the call stack, we can notice the 2nd argument a passed to function xyz_delete is NULL. However, when we dereferenced a at line# 1196, there is no crash - which is really surprising! And there are few read and write operations being performed on b at line# 1203 and 1204. But a segmentation fault is seen when free_timer is called on b->active_timer at line# 1245. free_timer inturn calls free.
How could a NULL pointer be dereferenced without causing a crash?
Any logical explanation for what could be happening here?
Upvotes: 5
Views: 696
Reputation: 4062
Dereferencing NULL is undefined, thus the compiler is free to do anything. Literally anything, including removing the always undefined piece of code (some versions of clang did this IIRC). Crashing on null pointer is a reasonable termination, but it's not guaranteed in any way. Moreover, the compiler is also free to assume the UB won't happen and perform some optmizations basing on that assumption, see this article.
Upvotes: 2
Reputation: 969
The C11 standard states that :
The unary * operator denotes indirection. If the operand points to a function, the result is a function designator; if it points to an object, the result is an lvalue designating the object. If the operand has type "pointer to type", the result has type "type". If an invalid value has been assigned to the pointer, the behavior of the unary * operator is undefined.
[...]
Among the invalid values for dereferencing a pointer by the unary * operator are a null pointer, [...]
[6.5.3.2 Address and indirection operators, C11]
Dereferencing a null pointer is thus an undefined behaviour. Note that undefined behaviours won't necessarily cause a crash, and can be ignored by the compiler.
Possible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message).
[3.4.3 undefined behavior, C11]
For example, some optimisations could skip the pointer indirection and prevent the program from crashing.
You need to check if a pointer is invalid before dereferencing it, otherwise your program invoke undefined behaviour and can be unpredictable. Enabling -Wnull-dereference
can help you with that, but it may not catch everything.
Upvotes: 3