- const README: rewrite the space-saving claim per issue #5 follow-ups (constant-expression const vs #define immediates; which const qualify as constant expressions) - const README: fix the class-section diagnosis per issue #338 (overload resolution picks the const overload; the restriction only surfaces once that overload is removed) - sizeof README: virtual-inheritance result is compiler-dependent (GCC merges vbptr+vfptr -> 16, MSVC keeps them separate -> 32) #246 - static README: new section on internal linkage / access restriction with anonymous-namespace comparison #142 - 10-day practice: full problem list table + fixed day links #136 - new design_pattern/singleton/singleton_default.hpp: reusable thread-safe CRTP singleton template, compile+concurrency tested #303
6.8 KiB
来自 C++那些事网站 的文章,由站点同步脚本导出。
AddressSanitizer 内存检测
C和C++是非常不安全且容易出错的编程语言,Address Sanitizer是由Google开发的一种工具,用于检测内存访问错误,如使用后释放(use-after-free)和内存泄漏。它已集成到GCC版本>= 4.8中,可用于C和C++代码。Address Sanitizer使用运行时插桩来跟踪内存分配,这意味着您必须使用AddressSanitizer构建代码,以充分利用其功能。
- Use after free (dangling pointer dereference)
- Heap buffer overflow
- Stack buffer overflow
- Global buffer overflow
- Use after return
- Use after scope
- Initialization order bugs
- Memory leaks
本文将会以上面8种场景来介绍内存检测工具AddressSanitizer。
1.Use after free
以下面代码为例。首先,通过new关键字分配了一个整数的内存空间,并将其地址赋给指针 ptr。然后,使用 delete 关键字释放了这块内存。但是,在释放后,程序仍然尝试通过指针 ptr 访问已经释放的内存,并将值 10 赋给该内存位置,这就是使用后释放错误。
#include <iostream>
int main() {
int* ptr = new int;
delete ptr;
*ptr = 10; // Use after free
return 0;
}
在编译时添加下面选项。
-fsanitize=address
运行生成的bin文件,得到如下结果。
2.heap buffer overflow
首先,通过 new 关键字在堆上分配了一个包含两个整数的数组,并将数组的起始地址赋给指针 arr。然后,程序试图将值 10 存储在数组的第三个位置(索引为 2),这超出了数组的有效范围,导致堆缓冲区溢出错误。
#include <iostream>
int main() {
int* arr = new int[2];
arr[2] = 10; // Heap buffer overflow
delete[] arr;
return 0;
}
同上面用法:
g++-13 heap_buffer_overflow.cc -Wall -g -fsanitize=address
然后:
./a.out
得到下面检测结果。
3.stack buffer overflow
在 vulnerableFunction 函数中,定义了一个包含 8 个字符的字符数组 buffer。然后,使用 std::cin 从标准输入读取数据存储到 buffer 中,但没有限制输入的大小。如果输入的字符数超过了 buffer 的大小,就会发生栈缓冲区溢出错误。
void vulnerableFunction() {
char buffer[8];
std::cin >> buffer; // Stack buffer overflow
std::cout << "You entered: " << buffer << std::endl;
}
用法同上。
4.global buffer overflow
全局缓冲区(buffer 数组)在程序的全局范围内定义,而没有受到足够的输入大小限制。如果输入的字符数超过了缓冲区的大小,就会发生全局缓冲区溢出错误。与栈缓冲区溢出类似,全局缓冲区溢出可能导致程序的不稳定性和安全性问题。
#include <iostream>
char buffer[8];
int main() {
std::cin >> buffer; // 全局缓冲区溢出错误
std::cout << "You entered: " << buffer << std::endl;
return 0;
}
用法同上,得到:
5.use after return
在函数 f 中,局部变量 i 的地址被返回,但在 main 函数中,尝试通过返回的指针访问已经被销毁的局部变量。当出现这种场景是,可以对bin文件进行检测,设置ASAN_OPTIONS=detect_stack_use_after_return=1。
g++-13 stack_use_after_return.cc -Wall -g -fsanitize=address
ASAN_OPTIONS=detect_stack_use_after_return=1 ./a.out
代码:
#include <iostream>
int *f() {
int i = 42;
int *p = &i;
return p;
}
int main() { return *f(); }
我们便会得到:
6.use after scope
将局部变量 x 的地址赋给指针 p,然而,在超出该内部作用域后,尝试通过指针 p 访问已经超出范围的局部变量 x,导致范围外使用错误。这也是未定义行为。
#include <iostream>
int main() {
int* p = 0;
{
int x = 0;
p = &x;
}
*p = 5;
return 0;
}
编译与运行:
g++-13 stack_use_after_scope.cc -Wall -g -fsanitize=address
ASAN_OPTIONS=detect_stack_use_after_scope=0 ./a.out
实测clang++可以,g++无法检测。
7.Initialization order bugs
在两个不同的源文件中,存在全局变量 a 的初始化,但初始化的顺序不确定。在 init_order_b.cc 文件中,尝试使用变量 a 的值,但由于初始化顺序不一致,可能导致未定义的行为。
代码如下:
// init_order_a.cc
int duplicate(int n) { return n * 2; }
auto a = duplicate(2);
// init_order_b.cc
#include <iostream>
extern int a;
auto b = a;
int main() {
std::cout << b << std::endl;
return 0;
}
当我们编写这样的代码时,其结果是不确定的,可能是4也可能是0,取决于初始化的顺序。
例如:
clang++ init_order_a.cc init_order_b.cc -Wall -g -fsanitize=address
这样输出是4,而先编译b,后编译a,得到0。
clang++ init_order_b.cc init_order_a.cc -Wall -g -fsanitize=address
当出现这种问题时,我们可以使用AddressSanitizer检测初始化顺序。
ASAN_OPTIONS=check_initialization_order=true ./a.out
8.memory leak
通过 new 关键字动态分配了一个整数的内存,但在程序结束前未使用 delete 关键字释放这块内存,导致内存泄漏。
#include <iostream>
int main() {
int* ptr = new int;
// Missing delete ptr; // Memory leak
return 0;
}
设置ASAN_OPTIONS=detect_leaks=1。
ASAN_OPTIONS=detect_leaks=1 ./a.out







