We can see that although the object itself is indeed on the stack (we can see both s1 and s2 addresses are on the stack), its internal data can be somewhere else, either in the stack (s1.data) or in the heap (s2.data).
What's going on here? How can an object have its memory sometimes in the heap and sometimes in the stack?
This is because std::string is an object, and objects have constructors.
Like all other OOP languages, a constructor is called when an object is created.
Inside the constructor, a programmer can do whatever they want, like malloc a new memory or perform some calculations.
In the GCC implementation of std::string, there are two implementations of std::string. One is called a short string, and the other is called a long string.
Depending on the string length, if it's less than 23 characters, it allocates memory on the stack. Otherwise, it allocates new memory in the heap and puts the actual string content in the heap.
To verify this, we can add an allocation tracker to our program.
Run the program again, and we can see the string object malloc and free new memory behind the scenes when the string length is long.
But where does the program free the memory?
Here comes the destructor.
Stack Objects
For objects inside the stack, the destructor will be called "automatically" when the variable goes out of scope. (There are also other cases where the destructor will be called!!!)
Let's see some examples.
Let's start with a class that tracks object construction and destruction.
1 2 3 4 5 6 7 8 9 10 11 12 13 14
classNanami{ std::string name; public: Nanami(const std::string& name) { this->name = name; std::cout << name <<": owu I got created" << std::endl; } ~Nanami() { std::cout << name <<": owo I got destroyed" << std::endl; } intlol(){ return1+1; } };
For local variables, objects will be constructed when they are initialized and destructed when the function returns. Therefore, all allocated memory inside the object will be freed in the end, just like normal stack variables.
For objects inside a block, they act like local variables. When the program goes out of the block, the object calls the destructor and frees all the memory.
For xvalue scope, it calls the destructor when the statement finishes.
However, things are very different when you allocate objects on the heap.
Heap Objects
For objects on the heap, their lifecycle is fully controlled by the programmer. This means the object is constructed when the programmer uses new and destructed when the programmer uses delete.
The destructor only runs when delete is invoked.
So normally, if you create a new object and then delete it at the end, everything is fine.
Local (stack) objects, which are allocated within a function on the stack, have their lifetimes managed by the compiler. This means their destruction time is determined: when the program completes a certain code scope.
Heap objects, generally allocated via new, have lifetimes that differ from stack objects. Their lifetimes are fully controlled by the programmer, meaning manual control of the heap object's lifetime is required. Their destruction occurs when the corresponding delete is called.
there is also another case I didn't mention, its object inside objects. Maybe I will cover it later. but for now, just stick with those two concepts.
voidprint_address_location(constchar * name, void *addr){ if (is_stack_address(addr)) { printf("%s (%p) is in [stack]\n", name, addr); } elseif (is_heap_address(addr)) { printf("%s (%p) is in [heap]\n", name, addr); } else { printf("%s (%p) is in other segment\n", name, addr); } }