捕获和调试无效使用对移动的lambda内部的局部变量的引用

Vit*_*meo 6 c++ debugging sanitizer undefined-behavior c++14

我在我的一个真实项目中遇到了一个难以调试的情况,我不小心访问了已被移动的lambda中的局部变量的引用.访问是从另一个线程完成的,但移动的lambda保持活着直到第二个线程完成.

该错误仅在禁用优化时发生,并且是由粗心重构引起的.

我创建了一个最小的例子(可在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);
        });
}
Run Code Online (Sandbox Code Playgroud)

令人惊讶的是,没有一个消毒剂和警告标志设法在这里提供帮助.

我已经尝试过以下编译器和消毒剂的组合

-Wall -Wextra -Wpedantic -g -O0
Run Code Online (Sandbox Code Playgroud)

标志始终启用:

  • 编译器:Arch Linux x64上的g ++ 6.1.1 ; Arch Linux 4.0上的clang ++ 3.8.0 ; Fedora x64上的g ++ 5.3.1 ; Fedora x64上的clang ++ 3.7.0.

  • 消毒剂:-fsanitize=address; -fsanitize=undefined,-fsanitize=thread.

这些组合都没有产生任何有用的诊断.我希望AddressSanitizer告诉我我正在访问一个悬空引用,或者UndefinedSanitizer在访问它时捕获UB,或者ThreadSanitizer告诉我一个单独的线程正在访问一个无效的内存位置.

有可靠的方法来诊断这个问题吗?我应该将此示例作为功能请求/缺陷发布到任何消毒剂的错误跟踪器吗?

Leo*_*eon 4

valgrind 的 memcheck 工具在默认设置下发现了这个问题。然而,这种讨厌的错误有机会逃脱 memcheck。我不确定这个问题是否会在真实的程序中出现。

第一个 lambda 被移动的事实与问题无关(尽管它可能使调试过程变得复杂)。问题是由于访问已完成执行的函数中的局部变量(同样,访问发生在不同的线程中这一事实只会使调查变得更加困难,但不会以任何其他方式导致错误)。第一个 lambda 保持活动状态这一事实决不应该保护您 - 局部变量属于 lambda调用,而不是 lambda 本身。