should crash but doesn't
This statement should be taken with a grain of salt. C++ has no concept of "must crash". It has a concept of undefined behaviour, which may or may not result in crashes. Even so, your code has no undefined behaviour.
c_str's of both instances refer to same place (this I understand, copy
constructor copied A bit-by-bit, and string internally stores data in
char*, and pointer got copied.
You are talking about the implementation of std::string
. You must must instead look at its interface in order to decide which operations are safe and which aren't.
Other than that, the implementation you are talking about, called copy-on-write or "COW", is obsolete since C++11. Latest GCC versions have abandoned it.
See GCC 5 Changes, New Features, and Fixes:
A new implementation of std::string
is enabled by default, using the
small string optimization instead of copy-on-write reference counting.
Small-string optimisation is the same technique used also, for example, in the Visual C++ implementation of std::string
. It works in a completely different way, so your understanding of how std::string
works on the inside is no longer correct if you use a sufficiently new GCC version, or it has never been correct if you use Visual C++.
but the question is, why doesn't this code crash?
Because it uses std::string
operations correctly according to the documentation of its interface and because your compiler is not completely broken.
You are basically asking why your compiler produces a working binary for correct code.
a1 and a2 are stack variables,
Yes (the correct term would be that the objects have "automatic storage duration").
when desconstructing them string B's will also get deconstructed, won't internal char* of those strings (that point to same memory location) get deleted twice?
Your compiler's std::string
implementation makes sure that this does not happen. Either it doesn't use COW at all, or the destructor contains code that checks if the shared buffer was already deleted.
If you are using an older GCC version, then you can just look at the source code of your std::string
implementation to find out how exactly it's done. It's open source, after all -- but beware, for it might look a bit scary. For example, here's the destructor code for an older GCC version:
~basic_string()
{ _M_rep()->_M_dispose(this->get_allocator()); }
Then look at _M_dispose
(in the same file) and you'll see that it's a very complicated implementation with various checks and synchronisations.
Also consider this:
If the sheer act of copying a std::string
would result in crashes, then the whole class would be completely pointless, wouldn't it?