zoukankan      html  css  js  c++  java
  • 一个关于空指针的思考

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

    #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;
    
    int main()
    {
    int x= (char *)&((student *)0)->age - (char *)0;
    printf("x = %d
    ",x);
    return 0;
    }

    其中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,%esp
    804842b: ff 71 fc pushl -0x4(%ecx)
    804842e: 55 push %ebp
    804842f: 89 e5 mov %esp,%ebp
    8048431: 51 push %ecx
    8048432: 83 ec 24 sub $0x24,%esp #分配空间
    8048435: c7 45 f8 03 00 00 00 movl $0x3,-0x8(%ebp) #将0x3放入栈
    804843c: 8b 45 f8 mov -0x8(%ebp),%eax 
    804843f: 89 44 24 04 mov %eax,0x4(%esp) 
    8048443: c7 04 24 20 85 04 08 movl $0x8048520,(%esp)
    804844a: e8 05 ff ff ff call 8048354 <printf@plt>
    804844f: b8 00 00 00 00 mov $0x0,%eax
    8048454: 83 c4 24 add $0x24,%esp
    8048457: 59 pop %ecx
    8048458: 5d pop %ebp
    8048459: 8d 61 fc lea -0x4(%ecx),%esp
    804845c: c3 ret 


    从上述可以看出,在为printf函数分配空间后直接计算出了结果($0x3),并将该值放入栈中,其中并没有对0地址进行任何访问

    在对空指针错误发生的场景进行思考后,总结出了以下场景:
    1:对空指针进行赋值,即写操作,如int *p =NULL;*p=6;
    2:对空指针进行引用,即读操作,如int *p = NULL;int a = *p;

    对场景1,写验证代码如下:

    int main()
    {
    int *p =NULL;*p=6;
    return 0;
    }
    反汇编后的结果为:
    080483e4 <main>:
    80483e4: 8d 4c 24 04 lea 0x4(%esp),%ecx
    80483e8: 83 e4 f0 and $0xfffffff0,%esp
    80483eb: ff 71 fc pushl -0x4(%ecx)
    80483ee: 55 push %ebp
    80483ef: 89 e5 mov %esp,%ebp
    80483f1: 51 push %ecx
    80483f2: 83 ec 10 sub $0x10,%esp
    80483f5: c7 45 f8 00 00 00 00 movl $0x0,-0x8(%ebp) #取0地址
    80483fc: 8b 45 f8 mov -0x8(%ebp),%eax 
    80483ff: c7 00 06 00 00 00 movl $0x6,(%eax) #将0x0地址内容设置为0x6,该处会段错误
    8048405: b8 00 00 00 00 mov $0x0,%eax
    804840a: 83 c4 10 add $0x10,%esp
    804840d: 59 pop %ecx
    804840e: 5d pop %ebp
    804840f: 8d 61 fc lea -0x4(%ecx),%esp
    8048412: c3 ret

    对场景2,写验证代码如下:

    int main()
    {
    int *p = NULL;int a = *p;
    return 0;
    }

    反汇编后的结果为:

    080483e4 <main>:
    80483e4: 8d 4c 24 04 lea 0x4(%esp),%ecx
    80483e8: 83 e4 f0 and $0xfffffff0,%esp
    80483eb: ff 71 fc pushl -0x4(%ecx)
    80483ee: 55 push %ebp
    80483ef: 89 e5 mov %esp,%ebp
    80483f1: 51 push %ecx
    80483f2: 83 ec 10 sub $0x10,%esp
    80483f5: c7 45 f4 00 00 00 00 movl $0x0,-0xc(%ebp) #对p赋值0x0
    80483fc: 8b 45 f4 mov -0xc(%ebp),%eax
    80483ff: 8b 00 mov (%eax),%eax #对0地址取值 ,此处会导致段错误
    8048401: 89 45 f8 mov %eax,-0x8(%ebp) #*p赋值给a
    8048404: b8 00 00 00 00 mov $0x0,%eax
    8048409: 83 c4 10 add $0x10,%esp
    804840c: 59 pop %ecx
    804840d: 5d pop %ebp
    804840e: 8d 61 fc lea -0x4(%ecx),%esp
    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))

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

  • 相关阅读:
    Mysql 常用小技巧
    【JS学习】require('fs')(fs模块用于对系统文件及目录进行读写操作。)
    【JS学习】js中forEach与for循环
    【JS学习】ES6之async和await
    【JS学习】关于Vue.use()详解
    【Npm学习】npm run dev 和 npm run serve
    【Jenkins学习】修改插件下载源地址
    【JS学习】js中const,var,let区别
    【Go学习】知识分享之Golang——go mod时使用代理模式goproxy和私有模式GOPRIVATE
    【JS学习】export 和 export default 的区别
  • 原文地址:https://www.cnblogs.com/charlieroro/p/8482585.html
Copyright © 2011-2022 走看看