Я столкнулся с трудной для отладки ситуацией в одном из моих реальных проектов, где я случайно получил доступ к ссылке на локальную переменную внутри лямбды, которая была перемещена. Доступ осуществлялся из другого потока, но перемещенная лямбда сохранялась до тех пор, пока не закончился второй поток.
Ошибка возникала только при отключенной оптимизации и была вызвана неосторожным рефакторингом.
Я создал минимальный пример (доступно здесь на wandbox) что воспроизводит проблему:
struct state
{
int x = 100;
};
template <typename TF>
void eat1(TF&& f)
{
// Call the lambda.
f();
// Simulate waiting for the second thread
// to finish.
std::this_thread::sleep_for(1000ms);
}
template <typename TF>
void eat0(TF&& f)
{
// Move the lambda to some other handler.
eat1(std::forward<TF>(f));
}
void use_state(state& s)
{
// Will print `100`.
std::cout << s.x << "\n";
// Separate thread. Note that `s` is captured by
// reference.
std::thread t{[&s]
{
// Simulate computation delay.
std::this_thread::sleep_for(500ms);
// Will print garbage.
std::cout << s.x << "\n";
}};
t.detach();
}
int main()
{
eat0([]
{
// Local lambda variable that will be accessed
// after the lambda is moved.
state s;
// Function that takes `s` by reference and
// accesses it in a separate thread after the
// lambda is moved.
use_state(s);
});
}
Удивительно, но ни один из дезинфицирующих средств и предупреждающих флагов не помог здесь.
Я пробовал следующие комбинации компиляторов и дезинфицирующих средств, с
-Wall -Wextra -Wpedantic -g -O0
флаги всегда включены:
Составители: g ++ 6.1.1 на Arch Linux x64; лязг ++ 3.8.0 на Arch Linux x64; g ++ 5.3.1 на Fedora x64; clang ++ 3.7.0 на федоре х64.
Дезинфицирующие: -fsanitize=address
; -fsanitize=undefined
, -fsanitize=thread
,
Ни одна из комбинаций не дала никакой полезной диагностики. Я ожидал либо AddressSanitizer чтобы сказать мне, что я получил доступ к свисающей ссылке, или UndefinedSanitizer поймать UB при доступе к нему, или ThreadSanitizer сказать мне, что отдельный поток обращался к неправильной ячейке памяти.
Есть ли надежный способ диагностики этой проблемы? Должен ли я опубликовать этот пример на каком-либо трекере ошибок sanitizers как запрос / дефект функции?
Инструмент memcheck от valgrind обнаружил эту проблему при настройках по умолчанию. Тем не менее, этот вид неприятных ошибок имеет шансы избежать Memcheck. Я не уверен, что проблема будет решена в реальной программе.
Тот факт, что первая лямбда была перемещена, не имеет отношения к проблеме (хотя, возможно, это усложнило процесс отладки). Проблема заключается в доступе к локальной переменной в функции, которая завершила свое выполнение (опять же, тот факт, что доступ произошел из другого потока, только усложнил исследование, но никак не способствовал возникновению ошибки). Тот факт, что первая лямбда была сохранена, ни в коем случае не должен вас защищать — локальные переменные принадлежат лямбде вызов а не сама лямбда.
Других решений пока нет …