0

I'm trying to figure out why the following code breaks. After objects->pop(); is called in the lambda expression, the memory address of objects changes to 0xfeeefeee when debugging in Visual Studio. I can get around this issue by doing auto p = objects; before the pop, and then using p instead of objects in the second line of the lambda. However, I must be missing something fundamental here.

class Obj
{
public:
    Obj(stack<shared_ptr<Obj>>* const objects) {
        fun = [=] {
            objects->pop(); // objects' address changed after this executes
            objects->push(shared_ptr<Obj>(new Obj(objects)));
        };
    }

    function<void(void)> fun;
};

int main()
{
    stack<shared_ptr<Obj>> objects;
    objects.push(shared_ptr<Obj>(new Obj(&objects)));
    objects.top().get()->fun();
    return 0;
}

3 Answers3

3

What you are observing is undefined behavior.

The memory that the captured objects variable occupies is freed when you call objects->pop(); A good hint for this is the value it changes to: 0xFEEEFEEE. This magic number indicates freed memory.

Let's analyse what happens.

stack<shared_ptr<Obj>> objects;
objects.push(shared_ptr<Obj>(new Obj(&objects)));

A stack with shared_ptr's is created. Pushed on top of it is a new Obj instance, whose constructor argument is a pointer to our stack.

Obj(stack<shared_ptr<Obj>>* const objects) {
    fun = [=] { 
       /* ... */
    };
}

A function object is assigned a lambda, which captures the objects argument.

objects.top().get()->fun(); calls the function object:

objects->pop(); // objects' address changed after this executes 

The top element of the stack that objects points to, is the Obj instance to which the fun object belongs. By popping it off, the only (and therefore last) instance of the shared_ptr holding a reference to it goes out of scope and frees the memory that it used. This includes the captured pointer to objects. In a debug build HeapFree sets it to 0xFEEEFEEE. In a release build anything could happen. After this point you're referencing released memory, which is undefined behavior.

Community
  • 1
  • 1
Bart van Nierop
  • 4,130
  • 2
  • 28
  • 32
  • 2
    `The line objects.push(shared_ptr(new Obj(&objects))); creates an std::shared_ptr pointing to the automatic variable objects.` What? No, it doesn't... – Lightness Races in Orbit Apr 11 '14 at 10:01
  • 1
    To elaborate on what Lightness said, `shared_ptr(new Obj(&objects))` creates a perfectly fine shared pointer to a new instance of class `Obj`, which happens to get a pointer to a local variable as a parameter to its constructor. – wolfgang Apr 11 '14 at 10:56
  • @LightnessRacesinOrbit I totally analysed that wrong. The answer is updated. – Bart van Nierop Apr 11 '14 at 11:25
  • This makes complete sense, thanks! To others: the example code here contrived to keep it short. – Justin Stoecker Apr 11 '14 at 16:16
1

When you call objeccts->pop(), the shared_ptr on top of the stack is destroyed. It is the last pointer to your instance of Obj, so that instance is destroyed. That instance contains the function object fun, so the function object is also destroyed.

Any access to the captured variables of the function object after this point is undefined behaviour, which first happens right on the next line.

wolfgang
  • 4,883
  • 22
  • 27
0

For what I could deduce debugging your code, it seems to me that eventually you are doing pop on the top of stack, and with that you are trying to delete the pointer to the objects (the variable in the main function: by the way, you should rename that to eliminate confusion). As it is not on the heap, UB kicks in, and it gives (me) a segmentation fault.

Massa
  • 8,647
  • 2
  • 25
  • 26