Дан фрагмент кода на C++ с утечкой памяти и условной гонкой в многопоточном режиме (опишите небольшой пример с new/delete и std::thread): выявите ошибки, объясните, как их отловить инструментами (valgrind, sanitizer'ы, thread sanitizer) и предложите безопасную переработку кода

2 Окт в 17:06
12 +2
0
Ответы
1

Ниже — компактный пример, который демонстрирует одновременно утечку памяти (new без delete) и условную гонку (data race) при параллельном доступе к общему ресурсу.

Баговый пример (bug.cpp)

#include <iostream>
#include <thread>
#include <vector>
struct Data { int counter; };
int main() {
Data* d = new Data(); // выделили, но дальше не удаляем -> утечка
d->counter = 0;
std::vector<std::thread> threads;
for (int i = 0; i < 4; ++i) {
threads.emplace_back([d]() { // поток захватывает указатель
for (int j = 0; j < 100000; ++j) {
d->counter++; // НЕзащищённый доступ -> data race
}
});
}
for (auto &t : threads) t.join();
std::cout << "counter = " << d->counter << std::endl;
// missing delete d; <-- утечка памяти
return 0;
}

Ошибки

Утечка памяти: выделили d через new, но не вызвали delete — Valgrind/LeakSanitizer сообщат "definitely lost".Условная гонка: несколько потоков одновременно изменяют d->counter без синхронизации — это data race (undefined behavior). ThreadSanitizer обнаружит её.

Как отловить инструментами

1) ThreadSanitizer (TSan) — для data race:

Компиляция (g++/clang++):
g++ -std=c++17 -O1 -g -fsanitize=thread -pthread bug.cpp -o bug-tsanЗапуск:
./bug-tsanОжидаемый вывод: TSan напишет WARNING: ThreadSanitizer: data race и покажет стек вызовов, где происходит конкурентный доступ.

Примечание: TSan специально для гонок; он не сообщает об утечках памяти.

2) AddressSanitizer / LeakSanitizer — для поиска утечек:

Компиляция:
g++ -std=c++17 -O1 -g -fsanitize=address -fno-omit-frame-pointer -pthread bug.cpp -o bug-asanЗапуск:
ASAN_OPTIONS=detect_leaks=1 ./bug-asanОжидаемый вывод: LeakSanitizer сообщит о "LEAK" / "definitely lost" и укажет место new в стеке.

3) Valgrind (классика для утечек, но медленнее):

Сборка без sanitizers:
g++ -std=c++17 -O0 -g -pthread bug.cpp -o bugЗапуск:
valgrind --leak-check=full --show-leak-kinds=all ./bugВалгринд покажет количество "definitely lost" байт и стек-след к месту выделения.

Замечание о совместимости: ASan и TSan обычно нельзя включать одновременно (конфликты). Запускайте их по отдельности.

Безопасная переработка кода

Рекомендации:

Не использовать сырой new/delete — применять умные указатели (std::unique_ptr или std::shared_ptr) для автоматического освобождения.Для совместного доступа использовать std::atomic или синхронизацию (std::mutex), в зависимости от семантики.Запускать тесты с sanitizers и/или valgrind в CI.

Исправленный вариант 1 (используем std::shared_ptr + std::atomic):

#include <iostream>
#include <thread>
#include <vector>
#include <memory>
#include <atomic>
struct Data { std::atomic<int> counter{0}; };
int main() {
auto d = std::make_shared<Data>(); // автоматическое управление памятью
std::vector<std::thread> threads;
for (int i = 0; i < 4; ++i) {
threads.emplace_back([d]() { // копируем shared_ptr, безопасно передаём владение
for (int j = 0; j < 100000; ++j) {
d->counter.fetch_add(1, std::memory_order_relaxed); // атомарно
}
});
}
for (auto &t : threads) t.join();
std::cout << "counter = " << d->counter.load() << std::endl;
// d автоматически удалится, когда последний shared_ptr выйдет из области видимости
return 0;
}

Почему этот вариант безопасен:

Нет утечки — shared_ptr освободит память.Нет data race — counter — std::atomic, атомарные операции безопасны между потоками.

Исправленный вариант 2 (std::unique_ptr + mutex):

#include <iostream>
#include <thread>
#include <vector>
#include <memory>
#include <mutex>
struct Data { int counter = 0; };
int main() {
auto d = std::make_unique<Data>();
std::mutex mtx;
std::vector<std::thread> threads;
for (int i = 0; i < 4; ++i) {
threads.emplace_back([&d, &mtx]() {
for (int j = 0; j < 100000; ++j) {
std::lock_guard<std::mutex> lk(mtx);
++d->counter; // защита мьютексом
}
});
}
for (auto &t : threads) t.join();
std::cout << "counter = " << d->counter << std::endl;
return 0;
}

Почему:

unique_ptr управляет временем жизни (нет утечки).мьютекс обеспечивает корректную последовательность доступа (нет гонки).

Дополнительные советы

Для простого счетчика атомики обычно предпочтительнее (меньше накладных расходов), для сложных структур — mutex или другие примитивы синхронизации.Запускайте sanitizers локально и в CI; TSan часто обнаруживает гонки, которые реже проявляются при обычных запусках.Valgrind хорош для поиска утечек в коде без sanitizers и выявления других проблем с памятью, но работает медленно.

Если хотите, могу:

Запустить (показать пример вывода) TSan/ASan/Valgrind на этом примере,Или переписать пример под конкретные требования (например, использование std::mutex, std::atomic, или pool-подход).
2 Окт в 17:40
Не можешь разобраться в этой теме?
Обратись за помощью к экспертам
Гарантированные бесплатные доработки в течение 1 года
Быстрое выполнение от 2 часов
Проверка работы на плагиат
Поможем написать учебную работу
Прямой эфир