【问题标题】:Lambda machine-dependent segmentation-fault (possible compiler bug?)Lambda 机器相关分段错误(可能的编译器错误?)
【发布时间】:2013-06-12 15:53:13
【问题描述】:

今天我遇到了一个非常奇怪的错误。我创建了一个最小的例子:

https://gist.github.com/SuperV1234/5792381

基本上,在某些机器上,“测试 2”会出现段错误;在其他人身上,它按预期工作。 在我的台式计算机上,它可以在 Windows 8 x64 和 Linux Mint 15 x64 上运行。 在我的笔记本电脑上,它在 Windows 8 x64 和 Linux Mint 15 x64 上都会出现段错误。

让我困惑的是:

  • 它在某些机器上运行而在其他机器上崩溃的事实
  • 只需将 lambda 内容包装在另一个函数中即可修复段错误

这是编译器错误吗?或者Game::test1()和lambda body有区别吗?

// Test 1 works
// Test 2 segfaults... on some machines.
// Compiled with -std=c++11, GCC 4.8.1, tested both on native Linux, Windows and Wine

#include <iostream>
#include <functional>
#include <vector>

using namespace std;

struct Command
{
    function<void()> func;
    void reset() { }
};

struct Timeline
{
    vector<Command*> commands;
    void clear() 
    {
        for(auto& c : commands) delete c;
        commands.clear();
    }
    void reset() { for(auto& c : commands) c->reset(); }
};

struct Game
{
    Timeline timeline;

    void test1() { timeline.clear(); timeline.reset(); }
    void run()
    {
        {
            cout << "Starting test 1..." << endl;

            Command* cmd{new Command};
            cmd->func = [&]{ test1(); };
            timeline.commands.push_back(cmd); cmd->func();

            cout << "Successfully ending test 1..." << endl;
        }

        {
            cout << "Starting test 2..." << endl;

            Command* cmd{new Command};
            cmd->func = [&]{ timeline.clear(); timeline.reset(); };
            timeline.commands.push_back(cmd); cmd->func();

            cout << "Successfully ending test 2..." << endl;
        }
    }
};

int main() { Game{}.run(); return 0; }

此处提供了真实代码(不是最小示例):https://github.com/SuperV1234/SSVOpenHexagon/commit/77784ae142768f964666afacfeed74300501ec07

来自真实代码的回溯:http://paste2.org/W7yeCxOO

【问题讨论】:

    标签: c++ c++11 lambda g++ segmentation-fault


    【解决方案1】:

    如果你看一下反汇编,第一个 lambda 看起来像这样:

          test1();
    mov         eax,dword ptr [this]  
    mov         ecx,dword ptr [eax]  
    call        Game::test1 (021717h)  
    

    前两行获取捕获的Game对象的地址,并将其传递给Game::test1

    第二个 lambda 看起来像这样:

          timeline.clear();
    mov         eax,dword ptr [this]  
    mov         ecx,dword ptr [eax]  
    call        Timeline::clear (08415D2h)  
          timeline.reset(); 
    mov         eax,dword ptr [this]  
    mov         ecx,dword ptr [eax]  
    call        Timeline::reset (08416D6h)  
    

    这里的问题是,在timeline.clear 之后,lambda 被销毁,并且第二次尝试获取捕获的Game 对象会将一些垃圾放入ecx。因此,Timeline::reset 被调用时使用了一个无效的指针。

    编辑:你的 lambdas 基本上是这样的:

    struct lambda_1 {
        Game* game;
        void operator()() {
            game->test1();
        }
    };
    
    struct lambda_2 {
        Game* game;
        void operator()() {
            game->timeline.clear();
            game->timeline.reset();
        }
    };
    

    所以发生的情况是您试图访问已删除对象的成员。

    【讨论】:

    • 谢谢,通俗易懂,写得很好。知道为什么它在某些机器上没有段错误吗?
    • @Vittorio Romeo afaik 访问已删除对象的成员会导致未定义的行为,因此任何事情都可能发生。我的猜测是它在发布配置中对你有用。在发布中删除的对象通常不会立即被垃圾数据覆盖。
    • 实际上,我在不同的机器(笔记本电脑和台式机,都运行双启动 Linux Mint 和 Windows 8 )。台式机从未出现过段错误(即使在重新启动后),笔记本电脑总是如此,两种操作系统都如此。也许我永远不会发现这件事发生的原因,但这仍然很有趣
    【解决方案2】:

    您正在删除正在运行的 lambda。我不认为这是明智的做法。

    你的代码有点等价于这个:

    Command *c = new Command;
    c->func = [&] { delete c; };
    c->fun();
    

    如果你真的需要做这样的事情,你可以在调用之前复制函数:

    Command *c = new Command;
    c->func = [&] { delete c; };
    auto f = c->func; //copy the function
    f();  //c->func is deleted, but f is not!
    

    PS:你知道你的 clear / reset 的东西,事实上,没有什么意义,不是吗?

    【讨论】:

    • 您的意思是示例中的清除/重置内容?如果是这样,显然是的 :) 我试图模仿“真实代码”。
    猜你喜欢
    • 2014-12-23
    • 2014-05-16
    • 1970-01-01
    • 2021-05-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-12-26
    • 1970-01-01
    相关资源
    最近更新 更多