加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

一个关于空指针的思考

发布时间:2020-12-16 09:07:30 所属栏目:百科 来源:网络整理
导读:最近在看代码时发现一个用于求结构体成员偏移量的方式 #define NBB_OFFSETOF(STRUCT,FIELD) (NBB_BUF_SIZE)((NBB_BYTE *)(((STRUCT *)0)-FIELD) - (NBB_BYTE *)0) 奇怪的是对(STRUCT *)0)-FIELD的引用怎么不会出现错误呢? 于是写了如下代码进行简单的求证 #

最近在看代码时发现一个用于求结构体成员偏移量的方式

#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 = %dn",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))

如有不正确的地方,欢迎探讨!

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读