2

When a function ends, the stack is de-allocated using mov rsp, rbp (leave). Any variable or object within that stack frame is now out of scope.

When an object with a destructor goes out of scope, I expected the destructor to be called during the function epilogue

https://godbolt.org/z/hr7KE6e5q

However, looking at the above disassembly, that does not seem to be the case. A similar function (_ZN3FooD1Ev) to the destructor is called before the epilogue, but it doesn't seem to be the destructor (which seems to be _ZN3FooD2Ev.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
Happy Jerry
  • 164
  • 1
  • 8
  • 4
    Godbolt has the option to demangle symbols. Use it. – tkausl May 22 '22 at 17:51
  • 3
    `_ZN3FooD1Ev` and `_ZN3FooD2Ev` are both destructors, see https://stackoverflow.com/questions/6613870/gnu-gcc-g-why-does-it-generate-multiple-dtors/6614369#6614369. – interjay May 22 '22 at 17:51
  • GCC generates multiple destructors for different destruction scenarios: base object destructor, complete object destructor, and deleting destructor. – Eljay May 22 '22 at 17:55
  • 2
    On many architectures there is something called the red-zone that leaf functions can use. A certain region below the `rsp` (256 byte on x86_64 iirc) can be freely used by a function without having to change the `rsp`. So the `mov rsp, rbp` would just move the "allocated" stack frame into the red zone. it's not "de-allocated" yet. Basically the scope only ends with the `ret`. It's up to the compiler and architecture how he manages the stack and that might not always match what c++ calls scope. – Goswin von Brederlow May 22 '22 at 18:06
  • @GoswinvonBrederlow Thank you! I really like that answer. Do you mind having it as answer so I could accept? – Happy Jerry May 22 '22 at 18:16
  • Feel free to pretty it up. You should also mention that a function can have many more scopes where the compiler doesn't change `esp` at all. `esp` is only changed on function calls and doesn't really have anything to do with scope as such. – Goswin von Brederlow May 22 '22 at 18:37
  • Related: [Why does the x86-64 GCC function prologue allocate less stack than the local variables?](https://stackoverflow.com/q/13201644) possible duplicate even, since that explains how a variable's storage can still be live after a `leave` instruction. But I'm not seeing any calls coming after a `leave` or `add rsp, xx`, so I'm not sure what you're talking about. – Peter Cordes May 22 '22 at 21:32
  • It is weird that `_ZN3FooD1Ev` isn't defined anywhere... but it turns out it was just hidden by Godbolt as a directive: `.weak _ZN3FooD1Ev` / `.set _ZN3FooD1Ev,_ZN3FooD2Ev`. So it's just an alias for the destructor you can see. That seems like a totally separate question from calling after `leave`, which isn't happening here. – Peter Cordes May 22 '22 at 21:32
  • @PeterCordes ` call Foo::~Foo() [complete object destructor]` The destructor is called before the function epilog. This was my assumption how the destructor call looks like in assembly. But I see that the destructor is called multiple times on the assembly level. This may be a separate question – Happy Jerry May 22 '22 at 22:40
  • Your `main` function has 3 `Foo` objects: the return-value object that `bar` gets a pointer to in RDI, the arg object that it gets a pointer to in RSI, and the original `Foo a`. After the call to `bar`, all 3 of these objects have to get destroyed. You can see the compiler passing different pointers to the destructor, i.e. different `this` values, so you can tell it's destroying separate objects. – Peter Cordes May 22 '22 at 22:51
  • There are implicitly two calls to the default copy constructor, which you don't see because they got inlined. If you define an explicit copy constructor, you will see it gets called twice: once in `main` to copy `a` into the temporary to be passed to `bar`, and once in `bar` to copy `x` into the return value. Then the constructors and destructors will all match up. https://godbolt.org/z/aqPWP5TWb – Nate Eldredge May 24 '22 at 01:20

0 Answers0