In addition to what n.'pronouns'm already said, you can always use a tool like godbolt.org to see how code is translated to machine code. And the parts the belong together are highlighted, if you hover them.
For the part of you code in your loop:
string a = "apple"; // 1
cout << a << endl; // 2
string b = "banana"; // 3
string c = "cherry"; // 4
It will - for x86-64 gcc 10.1 - translate to:
// 1 ===============================================================================
lea rax, [rbp-23]
mov rdi, rax
call std::allocator<char>::allocator() [complete object constructor]
lea rdx, [rbp-23]
lea rax, [rbp-128]
mov esi, OFFSET FLAT:.LC0
mov rdi, rax
call std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string<std::allocator<char> >(char const*, std::allocator<char> const&)
lea rax, [rbp-23]
mov rdi, rax
call std::allocator<char>::~allocator() [complete object destructor]
// 2 ===============================================================================
lea rax, [rbp-128]
mov rsi, rax
mov edi, OFFSET FLAT:_ZSt4cout
call std::basic_ostream<char, std::char_traits<char> >& std::operator<< <char, std::char_traits<char>, std::allocator<char> >(std::basic_ostream<char, std::char_traits<char> >&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)
mov esi, OFFSET FLAT:_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
mov rdi, rax
call std::basic_ostream<char, std::char_traits<char> >::operator<<(std::basic_ostream<char, std::char_traits<char> >& (*)(std::basic_ostream<char, std::char_traits<char> >&))
// 3 ===============================================================================
lea rax, [rbp-22]
mov rdi, rax
call std::allocator<char>::allocator() [complete object constructor]
lea rdx, [rbp-22]
lea rax, [rbp-96]
mov esi, OFFSET FLAT:.LC1
mov rdi, rax
call std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string<std::allocator<char> >(char const*, std::allocator<char> const&)
lea rax, [rbp-22]
mov rdi, rax
call std::allocator<char>::~allocator() [complete object destructor]
// 4 ===============================================================================
lea rax, [rbp-21]
mov rdi, rax
call std::allocator<char>::allocator() [complete object constructor]
lea rdx, [rbp-21]
lea rax, [rbp-64]
mov esi, OFFSET FLAT:.LC2
mov rdi, rax
call std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string<std::allocator<char> >(char const*, std::allocator<char> const&)
lea rax, [rbp-21]
mov rdi, rax
call std::allocator<char>::~allocator() [complete object destructor]
// the object constructed in the loop are now destructed again in reverse order
// 4 ===============================================================================
lea rax, [rbp-64]
mov rdi, rax
call std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string() [complete object destructor]
// 3 ===============================================================================
lea rax, [rbp-96]
mov rdi, rax
call std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string() [complete object destructor]
// 1 ===============================================================================
lea rax, [rbp-128]
mov rdi, rax
call std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string() [complete object destructor]
So as you can see there are two blocks at the end of the machine code that are for the destruction of the strings at 3 and 1, as the only line to which gdb could refer to is the place where that object was constructed it will jump back to that line.
However, if I change the strings to char*, the problem goes away.
If you have:
const char *a = "apple";
const char *b = "banana";
const char *c = "cherry";
Then there will be nothing to be destructed at the and of the block of the for
, because that does not create an instance of an object. apple
, banana
and cherry
in that case will normally be located in the distinct data section in memory that will exist until the application is exited.