最近在看代码时发现一个用于求结构体成员偏移量的方式
#define NBB_OFFSETOF(STRUCT,FIELD) (NBB_BUF_SIZE)((NBB_BYTE *)(&((STRUCT *)0)->FIELD) - (NBB_BYTE *)0)
奇怪的是对(STRUCT *)0)->FIELD的引用怎么不会出现错误呢?
于是写了如下代码进行简单的求证
#include <stdio.h> #include <string.h> #pragma pack(1) typedef struct { char sex; short score; int age; }student; main() { int x= (char *)&((student *)0)->age - (char *)0; printf("x = %d\n",x); return ; }
其中int x= (char *)&((student *)0)->age - (char *)0这一行代码用于求age在结构体中的偏移量(结果是3),对main函数反汇编后的结果如下:
08048424 <main>: 8048424: 8d 4c 24 04 lea 0x4(%esp),%ecx 8048428: 83 e4 f0 and $0xfffffff0,1)">esp 804842b: ff 71 fc pushl -0x4(%ecx) 804842e: 55 push %ebp 804842f: 89 e5 mov %esp,1)">ebp 8048431: 51 push %8048432: 83 ec 24 sub $0x24,1)">esp #分配空间 8048435: c7 45 f8 03 00 00 movl $0x3,-0x8(%ebp) #将0x3放入栈 804843c: 8b 45 f8 mov -0x8(%ebp),1)">eax 804843f: 89 44 04 mov %eax,esp) 8048443: c7 04 20 85 08 movl $0x8048520,(%esp) 804844a: e8 05 ff ff ff call 8048354 <printf@plt> 804844f: b8 00 mov $0x0,1)">eax 8048454: 83 c4 24 add $esp 8048457: 59 pop %8048458: 5d pop %8048459: 8d 61 fc lea -0x4(%ecx),1)">esp 804845c: c3 ret
从上述可以看出,在为printf函数分配空间后直接计算出了结果($0x3),并将该值放入栈中,其中并没有对0地址进行任何访问
在对空指针错误发生的场景进行思考后,总结出了以下场景:
1:对空指针进行赋值,即写操作,如int *p =NULL;*p=6;
2:对空指针进行引用,即读操作,如int *p = NULL;int a = *p;
对场景1,写验证代码如下:
int *p =NULL;*p=6; ; } 反汇编后的结果为: 080483e4 <main>: 80483e4: 8d 4c ecx 80483e8: esp 80483eb: ff ecx) 80483ee: ebp 80483ef: ebp 80483f1: ecx 80483f2: 10 sub $0x10,1)">esp 80483f5: c7 ebp) #取0地址 80483fc: 8b eax 80483ff: c7 06 0x6,1)">eax) #将0x0地址内容设置为0x6,该处会段错误 8048405: b8 eax 804840a: 10 add $esp 804840d: ecx 804840e: 5d pop %ebp 804840f: 8d 8048412: c3 ret
对场景2,写验证代码如下:
int *p = NULL;int a = *p; ; }
反汇编后的结果为:
080483e4 <main>45 f4 0xc(%ebp) #对p赋值0x0 80483fc: 8b 45 f4 mov -0xc(%ebp),1)">eax 80483ff: 8b 00 mov (%eax),1)">eax #对0地址取值,此处会导致段错误 8048401: 45 f8 mov %eax,1)">0x8(%ebp) #*p赋值给a 8048404: b8 8048409: esp 804840c: ecx 804840d: 5d pop %ebp 804840e: 8d 8048411: c3 ret
得出的总结如下:
导致空指针段错误的原因是对空指针地址进行了读或写操作(printf一个空指针其实也是对空指针进行了读操作,然后将内容写到显卡对应的内存)。
(NBB_BYTE *)(&((STRUCT *)0)->FIELD并没有对0地址进行读或写操作,该表达式中的0更应该看做是一个虚拟地址,代表了结构体的首地址,这样可以方便地计算出结构体成员的偏移量,因此 (NBB_BUF_SIZE)((NBB_BYTE *)(&((STRUCT *)0)->FIELD) - (NBB_BYTE *)0)可以简化为(NBB_BUF_SIZE)((NBB_BYTE *)(&((STRUCT *)0)->FIELD))
如有不正确的地方,欢迎探讨!
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。