Reputation: 93264
Today I came across a very strange bug. I created a minimal example:
https://gist.github.com/SuperV1234/5792381
Basically, on some machines, "test 2" segfaults; on others, it works as intended. On my desktop computer it works, both on Windows 8 x64 and Linux Mint 15 x64. On my laptop computer it segfaults, both on Windows 8 x64 and Linux Mint 15 x64.
What puzzles me is:
Is this a compiler bug? Or is there a difference between Game::test1()
and the 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; }
Real code (not a minimal example) is available here: https://github.com/SuperV1234/SSVOpenHexagon/commit/77784ae142768f964666afacfeed74300501ec07
Backtrace from real code: http://paste2.org/W7yeCxOO
Upvotes: 3
Views: 667
Reputation: 98348
You are deleting the lambda while it is being run. I don't think it is a sane thing to do.
Your code is somewhat equivalent to this:
Command *c = new Command;
c->func = [&] { delete c; };
c->fun();
If you really need to do something like this, you may copy the function before calling it:
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: You are aware that your clear
/ reset
thing, as it is, makes little sense, aren't you?
Upvotes: 3
Reputation: 1719
If you look at the disassembly, the first lambda looks like this:
test1();
mov eax,dword ptr [this]
mov ecx,dword ptr [eax]
call Game::test1 (021717h)
The first two lines get the address of the captured Game
object and pass it to the Game::test1
.
The second lambda looks like this:
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)
The problem here is that after timeline.clear
the lambda is destroyed and the second attempt to get the captured Game
object puts some garbage in ecx
. As a result of that, Timeline::reset
gets called with an invalid pointer.
Edit: your lambdas are basically this:
struct lambda_1 {
Game* game;
void operator()() {
game->test1();
}
};
struct lambda_2 {
Game* game;
void operator()() {
game->timeline.clear();
game->timeline.reset();
}
};
So what happens is that you're trying to access a member of a deleted object.
Upvotes: 4