Address Sanitizer 简介
要解决的问题
由于 C/C++ 这类编程语言与硬件(主要是内存)非常贴近,使用 C/C++ 编程,经常遇到的的一个问题就是内存错误,其中可能包括:
- 内存泄漏:忘记 free 之前在堆中申请的内存,并丢失了所申请内存的指针;
- 内存访问越界:包括对全局内存、栈内存、堆内存访问的越界;
- 释放后使用:访问已经被 free 的内存;
- 返回后使用:访问已经返回的函数栈中的内存;
- ……
这些错误,有的会在程序运行的过程中报错并使程序终止,这还算好的;有的则根本不会报错,暗中影响程序的正确性。
Address Sanitizer 应运而生
因此,Google 开发了一款专门用于检测内存访问错误的工具:AddressSanitizer(简称 ASan),它可以自动检测程序运行时(runtime)发生的许多内存访问错误。
你不需要专门安装它,因为它被集成到了 LLVM 3.1 版本及以后、GCC 4.8 版本及以后了,所以你可以直接让 Clang 或 GCC 打开 AddressSanitizer。比如,写一个最简单的内存访问越界的程序:
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> v(4, 123); // 长度为 4 的 vector
cout << v[6] << "\n"; // 访问越界
return 0;
}
编译的时候打开-fsanitize=address
开关:
clang++ -fsanitize=address -o main main.cpp
编译完成以后,运行程序,就会出现这样的情况:
第一行红色报错:heap-buffer-overflow
,说明是堆上的内存访问越界了。确实,因为vector
是被分配在堆上的。
再看第二行蓝色,说是线程T0
在READ
一块儿长度为 4 (一个int
的大小)的内存的时候发生的错误,紧跟着下面几行就是错误处的调用栈了:
#0 0x10237ae4c in main main.cpp:8
#1 0x10252d0f0 in start+0x204 (dyld:arm64e+0x50f0)
看到栈顶的元素#0
就是main.cpp:8
,说明是 main.cpp 源代码的第 8 行出现了错误,就是cout << v[6] << "\n";
这一句话。这时就已经定位到发生错误的地方了。
如果不使用 AddressSanitizer,这个程序很有可能“正常运行”,即输出v[6]
为0
,让人以为这个程序没问题。但实际上程序是错误的。这种错误单靠 Debugger 是找不出来的,或者说很难找出来。
LeetCode 对 AddressSanitizer 的应用
LeetCode 在编译代码的时候也会自动开启 AddressSanitizer 来检测内存访问越界和释放后使用错误:
因此,你很可能在 LeetCode 上运行的时候会报出这样看不懂的 runtime error:
这里的报错信息较为简陋,因此你可以在自己本机编译调试的时候加上-fsanitize=address
选项来打开 AddressSanitizer,在本机运行,就可以看到更为详细的报错。注意,最好再加上-g
选项,这样程序中就会带有调试符号和源码行号,报错信息中就会显示到底是程序中的哪一个函数的哪一行发生了内存访问错误。
结语
本文仅仅介绍了 Address Sanitizer 最基本的概念和使用方法。如果你对它的详细用法和底层原理很感兴趣,最好的阅读材料就是官方 Wiki:https://github.com/google/sanitizers/wiki。还有其他的常用的内存检测工具,如 Valgrind Memcheck。