To add to the accepted answer, because the asker is still confused about "why does it crash on the deletion of the first object?":
Let's look at the diassembly, because it cannot lie, even in the face of an incorrect program that exhibits UB (unlike the debugger).
https://godbolt.org/z/pstZu5
(Take note that rsp
- our stack pointer - is never changed aside from the adjustment at the beginning and end of main
).
Here is the initialization of adam
:
lea rax, [rsp+24]
// ...
mov QWORD PTR [rsp+16], 0
mov QWORD PTR [rsp+8], rax
mov BYTE PTR [rsp+24], 0
It seems [rsp+16]
and [rsp+24]
hold size and capacity of the string, while [rsp+8]
holds the pointer to the internal buffer. That pointer is set up to point into the string object itself.
Then adam.name
is overwritten with "adam"
:
call std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_replace(unsigned long, unsigned long, char const*, unsigned long)
Due to small string optimization, the buffer pointer at [rsp+8]
probably still points to the same place (rsp+24
) to indicate the string that we have a small buffer and no memory allocation (that's my guess to be clear).
Later on we initialize student
much in the same way:
lea rax, [rsp+72]
// ...
mov QWORD PTR [rsp+64], 0
// ...
mov QWORD PTR [rsp+56], rax
mov BYTE PTR [rsp+72], 0
Note how student
's buffer pointer points into student
to signify a small buffer.
Now you brutally replace the internals of student
with those of adam
. And suddenly, student
's buffer pointer doesn't point to the expected place anymore. Is that a problem?
mov rdi, QWORD PTR [rsp+56]
lea rax, [rsp+72]
cmp rdi, rax
je .L90
call operator delete(void*)
Yep! If the internal buffer of student
points anywhere else than where we initially set it to (rsp+72
), it will delete
that pointer. At this point we don't know where exactly adam
's buffer pointer (that you copied into student
) points to, but it's certainly the wrong place. As explained above, "adam"
is likely still covered by small string optimization, so adam
's buffer pointer was likely in the exact same place as before: rsp+24
. Since we copied that into student
and it is different from rsp+72
, we call delete(rsp+24)
- which is in the middle of our own stack. The environment doesn't think that's very funny and you get a segfault right there, in the first deallocation (the second one wouldn't even delete
anything because the world would still be fine over there - adam
was unharmed by you).
Bottom line: Don't try to outclever the compiler ("it can't segfault because it'll be on the same heap!"). You will lose. Follow the rules of the language and nobody gets hurt. ;)
Side note: This design in gcc
might even be intentional. I believe they could just as easily store a nullptr
instead of pointing into the string object to denote a small string buffer. But in that case you wouldn't segfault from this malpractice.