3

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

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

4

2 回答 2

4

如果你看一下反汇编,第一个 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.clearlambda 被销毁并且第二次尝试获取捕获的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();
    }
};

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

于 2013-06-16T16:25:13.137 回答
3

您正在删除正在运行的 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事物本身没有什么意义,不是吗?

于 2013-06-16T16:42:42.110 回答