空指针取消引用不会导致先前语句中的段错误

Nam*_*rma 5 c gdb null-pointer

我正在调试崩溃,其中我们有一个类似于 - 的代码片段

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);
...
...
...
     }  
Run Code Online (Sandbox Code Playgroud)

我们碰巧看到了崩溃——分段错误;其调用堆栈如下所示 -

#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 
Run Code Online (Sandbox Code Playgroud)

从调用堆栈中,我们可以注意到传递给函数 xyz_delete 的第二个参数 a 为 NULL。然而,当我们在第 1196 行取消引用 a 时,没有发生崩溃 - 这真是令人惊讶!并且在第 1203 行和第 1204 行对 b 执行的读写操作很少。但是当在第 1245 行对 b->active_timer 调用 free_timer 时,会出现分段错误。 free_timer 又调用 free。

如何取消引用 NULL 指针而不导致崩溃?

对于这里可能发生的事情有任何合乎逻辑的解释吗?

Elz*_*dir 3

C11 标准规定:

一元 * 运算符表示间接。如果操作数指向函数,则结果是函数指示符;如果它指向一个对象,则结果是指定该对象的左值。如果操作数的类型为“指向类型的指针”,则结果的类型为“type”。如果为指针分配了无效值,则一元 * 运算符的行为未定义。

[...]

通过一元 * 运算符取消引用指针的无效值包括空指针、 [...]

[6.5.3.2 地址和间接运算符,C11]

因此,取消引用空指针是一种未定义的行为。请注意,未定义的行为不一定会导致崩溃,并且可以被编译器忽略。

可能的未定义行为包括完全忽略具有不可预测结果的情况,到在翻译或程序执行期间以环境特征的记录方式表现(无论是否发出诊断消息),到终止翻译或执行(发出诊断消息)。

[3.4.3 未定义行为,C11]

例如,某些优化可以跳过指针间接寻址并防止程序崩溃。

在取消引用指针之前,您需要检查指针是否无效,否则您的程序将调用未定义的行为并且可能是不可预测的。启用-Wnull-dereference可以帮助您实现这一点,但它可能无法捕获所有内容。