Последнее обновление:2026-10-12
Проверка памяти с помощью 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
В этой статье мы рассмотрим инструмент проверки памяти 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;
}

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;
}

3. stack buffer overflow¶
В функции vulnerableFunction определён символьный массив buffer из 8 символов. Затем с помощью 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

6. use after scope¶
Адрес локальной переменной x присваивается указателю p, однако после выхода из внутренней области видимости предпринимается попытка обратиться через указатель p к уже вышедшей из области действия локальной переменной x, что приводит к ошибке использования вне области видимости. Это также неопределённое поведение.
g++-13 stack_use_after_scope.cc -Wall -g -fsanitize=address
ASAN_OPTIONS=detect_stack_use_after_scope=0 ./a.out

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, а если сначала скомпилировать b, а потом a — получим 0. При возникновении такой проблемы мы можем использовать AddressSanitizer для проверки порядка инициализации.
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.