• 一个关于空指针的思考


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

    #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))

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

  • 相关阅读:
    POJ3320 Jessica's Reading Problem
    POJ3320 Jessica's Reading Problem
    CodeForces 813B The Golden Age
    CodeForces 813B The Golden Age
    An impassioned circulation of affection CodeForces
    An impassioned circulation of affection CodeForces
    Codeforces Round #444 (Div. 2) B. Cubes for Masha
    2013=7=21 进制转换
    2013=7=15
    2013=7=14
  • 原文地址:https://www.cnblogs.com/charlieroro/p/8482585.html
Copyright © 2020-2023  润新知