C语言 在前面的语句中,空指针解引用不会导致段错误



我正在调试一个崩溃,我们有一个类似于-

的代码片段
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);
...
...
...
}  

我们碰巧看到一个崩溃分割错误;其调用栈如下所示-

#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 

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

如何在不引起崩溃的情况下解引用NULL指针?

对于这里可能发生的事情有什么合理的解释吗?

C11标准规定:

一元*操作符表示间接。如果操作数指向一个函数,则结果是一个函数指示符;如果它指向一个对象,则结果是一个左值,用于指定该对象。如果操作数具有指向type的类型指针,则结果具有类型类型。如果给指针赋了无效的值,则一元*操作符的行为是未定义的。

[…]

通过一元*操作符解引用指针的无效值包括空指针、[…]

[6.5.3.2地址和间接操作符,C11]

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

可能的未定义行为范围从完全忽略具有不可预测结果的情况,到在翻译或程序执行期间以环境特征的文档方式行为(有或没有发出诊断消息),到终止翻译或执行(发出诊断消息)。

[3.4.3未定义行为,C11]

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

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

解引用NULL是未定义的,因此编译器可以自由地做任何事情。任何事情,包括删除总是未定义的代码片段(clang的某些版本在IIRC中这样做)。在空指针上崩溃是一种合理的终止,但不能以任何方式保证。此外,编译器还可以自由地假设UB不会发生,并基于该假设执行一些优化,请参阅本文。

最新更新