为何对NULL指针的结构体成员取地址不会导致程序崩溃?
NULL指针访问结构体成员的差异解析
示例代码
#include <stdio.h> #include <stdlib.h> #include <string.h> typedef struct substruct { int integer1; unsigned char boolean1; unsigned char boolean2; char *ptr1; char *ptr2; } substruct_t; typedef struct tester { char *ptr1; //0x00 char *ptr2; //0x08 unsigned char boolean1; //0x10 unsigned char boolean2; //0x11 int integer1; //0x12 // padding byte 0x16 -> 0x18 char *ptr3; //0x18 // It is not pointer. substruct_t local; // 0x20 char *ptr4; } tester_t; int main() { tester_t *tester; tester = NULL; printf("0\n"); substruct_t *localptr = &(tester->local); printf("1\n"); // seg fault substruct_t test_local = tester->local; printf("2\n"); substruct_t *test_local_ptr = &test_local; printf("3\n"); // localptr = &(0x20); ??? // void *voidptr = 0x20; // substruct_t *testptr = (substruct_t *)&(voidptr); // printf("4\n"); return 0; }
编译运行结果
$ gcc -Wall -g -o asdf asdf.c $ ./asdf 0 1 [1] 1777 segmentation fault ./asdf
问题
程序执行&(tester->local)(tester为NULL指针)时未崩溃,成功打印出1,但执行substruct_t test_local = tester->local;时触发段错误,无法打印2。为何会出现这种差异?
原因解析
- 取地址操作仅做偏移计算:
&(tester->local)的本质是编译器根据tester_t的内存布局,计算local成员相对于结构体起始地址的偏移量(这里是0x20)。因为tester是NULL(起始地址为0),所以最终得到的地址是0 + 0x20 = 0x20。整个过程只是数学上的地址计算,没有实际去读取或写入该地址对应的内存,因此不会触发内存访问违规。 - 直接访问需要读取内存:
substruct_t test_local = tester->local;是要把tester->local这个结构体成员的所有内容复制到test_local中。这就需要程序去访问地址0x20处的内存,读取其中的数据。而NULL指针指向的低地址区域属于操作系统保护的内存空间,用户程序无权访问,一旦尝试读取就会触发段错误。 - 标准与实现的说明:C标准中,对NULL指针直接解引用(比如访问成员)属于未定义行为;但取成员地址的操作,在GCC这类常见编译器中只是做偏移计算,没有实际解引用内存,因此不会崩溃。不过这种行为是编译器的具体实现,标准并没有保证它一定安全,不要依赖这种特性写代码。
内容的提问来源于stack exchange,提问作者kiyo
相关产品推荐
相关产品推荐

