空指针访问的陷阱

简介: # 背景 之前用AddressSanitizer工具验证阿里云短视频SDK的稳定性时出现了一个崩溃问题,报错堆栈在一个空指针对象访问其成员函数处,但是从整体代码执行流程分析发现其对象指针一直是空的,诡异的是不使用工具运行时无论如何都不会崩溃。 ASAN工具报错发生在如下行,此处```mAudioRenderServicePtr```为NULL。 ```c this->OnStop(fa

背景

之前用AddressSanitizer工具验证阿里云短视频SDK的稳定性时出现了一个崩溃问题,报错堆栈在一个空指针对象访问其成员函数处,但是从整体代码执行流程分析发现其对象指针一直是空的,诡异的是不使用工具运行时无论如何都不会崩溃。

ASAN工具报错发生在如下行,此处mAudioRenderServicePtr为NULL。

this->OnStop(false, mAudioRenderServicePtr->GetAddr());

下面对正常运行和工具调试出现的结果一致进行分析,以及延伸的讨论一下关于空指针访问的一些陷阱。

分析

空指针即未指向任何对象,于是从语言层面来讲在访问空指针时应该一定发生崩溃,但在多函数调用及参数传递过程中,以及C++语言的封装特性、编译器是否优化的情况下,则不一定发生崩溃或者崩溃延迟。

先看看GetAddr这个成员函数的声明:

const MdfAddr& GetAddr(){ return mAddr; }

这里发现返回值为成员变量mAddr的常量引用,如下是反汇编对应的指令

    0xad0b9880 <+512>: ldr.w  r0, [r5, #0x124]
    0xad0b9884 <+516>: movs   r1, #0x0
    0xad0b9886 <+518>: add.w  r2, r0, #0x3c

从汇编指令看到,最后一行直接将this指针加上成员变量的偏移值0x3c并返回,也即返回的是mAddr的地址0x3c。

以及成员函数OnStop的函数声明

void OnStop(bool isAsync, const MdfAddr &srcAddr);

第二个参数实际使用的是MdfAddr的常量引用,因此参数会直接传递其对象地址0x3c,而不会访问0x3c指向的内存值,后面函数执行过程中也并未访问mAddr的成员变量,所以无工具运行时不会发生崩溃。

至于有工具时会发生崩溃,是因为ASAN会对所有地址访问进行监控,当出现异常时就会中断崩溃。

其它情况

  • 如上述的情况,如果我们在OnStop里实际访问了第二个参数里的成员变量,即实际访问了0x3c指针指向的内存,就会立即产生崩溃,比如OnStop的声明是这样

void OnStop(bool isAsync, MdfAddr srcAddr);

第二个参数会直接传递MdfAddr的值,因此会访问0x3c指向的实际内存,从而会立即导致崩溃,输出类似如下的信息

Build fingerprint: 'Xiaomi/cancro_wc_lte/cancro:6.0.1/MMB29M/8.9.13:user/release-keys'
02-21 16:43:27.589 290-290/? A/DEBUG: Revision: '0'
02-21 16:43:27.589 290-290/? A/DEBUG: ABI: 'arm'
02-21 16:43:27.589 290-290/? A/DEBUG: pid: 19854, tid: 19933, name: Thread-3246  >>> com.aliyun.aliyunvideosdkpro <<<
02-21 16:43:27.589 290-290/? A/DEBUG: signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr --------
02-21 16:43:27.610 290-290/? A/DEBUG:     r0 00000000  r1 00004ddd  r2 00000006  r3 99c11978
02-21 16:43:27.610 290-290/? A/DEBUG:     r4 99c11980  r5 99c11930  r6 00000019  r7 0000010c
02-21 16:43:27.610 290-290/? A/DEBUG:     r8 0000000b  r9 b3472bd0  sl 00000000  fp 133822cc
02-21 16:43:27.610 290-290/? A/DEBUG:     ip 00000006  sp a8260188  lr b6816c69  pc b6819058  cpsr 400d0010
02-21 16:43:27.646 290-290/? A/DEBUG: backtrace:
02-21 16:43:27.646 290-290/? A/DEBUG:     #00 pc 00042058  /system/lib/libc.so (tgkill+12)
02-21 16:43:27.646 290-290/? A/DEBUG:     #01 pc 0003fc65  /system/lib/libc.so (pthread_kill+32)
02-21 16:43:27.646 290-290/? A/DEBUG:     #02 pc 0001c403  /system/lib/libc.so (raise+10)
02-21 16:43:27.646 290-290/? A/DEBUG:     #03 pc 000195b5  /system/lib/libc.so (__libc_android_abort+34)
02-21 16:43:27.646 290-290/? A/DEBUG:     #04 pc 00017508  /system/lib/libc.so (abort+4)
02-21 16:43:27.647 290-290/? A/DEBUG:     #05 pc 000a18b3  /system/lib/libclang_rt.asan-arm-android.so (_ZN11__sanitizer5AbortEv+40)
02-21 16:43:27.647 290-290/? A/DEBUG:     #06 pc 000a6449  /system/lib/libclang_rt.asan-arm-android.so (_ZN11__sanitizer3DieEv+60)
02-21 16:43:27.647 290-290/? A/DEBUG:     #07 pc 0008ffc0  /system/lib/libclang_rt.asan-arm-android.so (_ZN6__asan19ScopedInErrorReportD2Ev+352)
02-21 16:43:27.647 290-290/? A/DEBUG:     #08 pc 000900b8  /system/lib/libclang_rt.asan-arm-android.so (_ZN6__asan18ReportDeadlySignalEiRKN11__sanitizer13SignalContextE+160)
02-21 16:43:27.647 290-290/? A/DEBUG:     #09 pc 0008f0fc  /system/lib/libclang_rt.asan-arm-android.so (_ZN6__asan18AsanOnDeadlySignalEiPvS0_+188)
02-21 16:43:27.647 290-290/? A/DEBUG:     #10 pc 0036d2e1  /system/lib/libart.so (_ZN3art12FaultManager11HandleFaultEiP7siginfoPv+208)
02-21 16:43:27.647 290-290/? A/DEBUG:     #11 pc 0001756c  /system/lib/libc.so
  • 考虑另一种场景,如何实现的代码
class MyClass
{
public:
    int calcValue(int x, int y) const
    {
        return x + y;
    }
};

如果我们将一些实际未使用类成员变量的全局函数作为类的成员函数,那么在对象指针为NULL时访问这一类函数,则不会发生崩溃,因为在执行calcValue时this指针并不会用到。

总结

  • 如果直接访问空指针指向的内存,如内建类型,则会立即发生崩溃
  • 访问空指针对象指向的成员函数时,则不一定会发生崩溃
  • 使用引用或者常量引用类型的参数时,内存访问会延迟,内存访问异常也会延迟
目录
相关文章
|
12月前
|
存储 编译器 程序员
【C++】类与对象(一)类的定义 访问限定符 类的实例化 this指针
【C++】类与对象(一)类的定义 访问限定符 类的实例化 this指针
|
7天前
使用指针访问数组元素
【10月更文挑战第30天】使用指针访问数组元素。
19 3
|
2天前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
13 4
|
6天前
使用指针访问数组元素
【10月更文挑战第31天】使用指针访问数组元素。
13 2
|
5月前
|
存储 安全 编译器
C++进阶之路:探索访问限定符、封装与this指针的奥秘(类与对象_上篇)
C++进阶之路:探索访问限定符、封装与this指针的奥秘(类与对象_上篇)
43 0
|
6月前
|
存储 编译器 C语言
【C++】类与对象【定义、访问限定符、this指针】
【C++】类与对象【定义、访问限定符、this指针】
31 1
|
6月前
|
存储 编译器 程序员
【C++】类和对象①(什么是面向对象 | 类的定义 | 类的访问限定符及封装 | 类的作用域和实例化 | 类对象的存储方式 | this指针)
【C++】类和对象①(什么是面向对象 | 类的定义 | 类的访问限定符及封装 | 类的作用域和实例化 | 类对象的存储方式 | this指针)
|
6月前
|
存储 编译器 C语言
【C++练级之路】【Lv.2】类和对象(上)(类的定义,访问限定符,类的作用域,类的实例化,类的对象大小,this指针)
【C++练级之路】【Lv.2】类和对象(上)(类的定义,访问限定符,类的作用域,类的实例化,类的对象大小,this指针)
|
存储 C语言
【C语言】 条件操作符 -- 逗号表达式 -- []下标访问操作符,()函数调用操作符 -- 常见关键字 -- 指针 -- 结构体
【C语言】 条件操作符 -- 逗号表达式 -- []下标访问操作符,()函数调用操作符 -- 常见关键字 -- 指针 -- 结构体