为何这段C代码未触发Segfault?求技术解释
先看你的代码:
#include <stdlib.h> #include <stdio.h> typedef struct tag_node { struct tag_node *left, *right, *prev; int value; } node; int main() { node *root_node = malloc(sizeof(node)); root_node->value = 1; node **position = &root_node->right; *position = malloc(sizeof(node)); (*position)->value = 2; (*position)->prev = root_node; printf("root node is %d, right node is %d\n", root_node->value, root_node->right->value); node **ptr = &(*position)->prev->prev->prev; printf("This is fine\n"); return 0; }
编译命令:gcc -Wall -Wextra -g -O0 example.c -o example
你预期在node **ptr = &(*position)->prev->prev->prev;行触发段错误,但实际程序正常运行,原因主要有两点:
1. 你对root_node->prev的初始值有误解
malloc函数只负责分配内存,不会初始化内存内容。root_node是malloc出来的,它的prev成员是未初始化的垃圾值,不是你以为的NULL。不过即使root_node->prev确实是NULL,接下来的逻辑依然能解释程序不崩溃的原因。
2. 关键表达式并没有实际解引用无效内存
拆解&(*position)->prev->prev->prev的运算逻辑:
根据C语言运算符优先级,->优先级高于&且右结合,这个表达式等价于:
&( ( ( (*position)->prev )->prev )->prev )
假设( (*position)->prev )->prev是NULL,那么表达式简化为&(NULL->prev)。这里的核心是:取空指针指向的结构体成员的地址,不等于访问该成员的内容。
在大多数编译器实现中,结构体成员的偏移量是编译期就确定的。比如node结构体里prev成员的偏移量,可以用offsetof(node, prev)计算。当编译器处理&ptr->member时,会把它转换成(char*)ptr + offsetof(node, member)的地址计算。如果ptr是NULL,这个计算结果就是offsetof(node, prev)(0加上偏移量),这是一个无效地址,但你的代码只是把这个地址赋值给ptr变量,既没有读取这个地址的内容,也没有向它写入数据。
段错误是操作系统在程序试图读写无效内存区域时才会触发的,仅仅计算并存储一个无效地址并不会触发错误。
最后要注意:这种行为属于C标准定义的未定义行为,不同编译器、平台可能有不同表现,绝对不能依赖这种代码逻辑。
内容的提问来源于stack exchange,提问作者Fyodor

