I discovered that calling the destructors ~vector and ~string resulted in a much more than just deallocating any allocated heap in use. For vector it zeroed out the object as well which put it in the same state as simply declaring an empty vector, ie: std::vector<int> v; For strings, it deallocated any heap used but it also changed the string to match an empty string. That is, the same as std::string s;
There's nothing illegal about that. Arguably it makes the thing safe in case the programmer fails to replace the destructed object since the destructor will be called again at scope exit.
But I don't see any reason why it generates extra code to modify this memory that has no remaining use at all.
Why is this superfluous code generated?
EDIT Turns out neither GCC nor CLANG change the object at all, they just destroy the allocated heap used by vector. So this excess code is only in MSVC.
EDIT
I found the area in std::string that is changing the string object. It's called _Tidy_deallocate() at location 4630 in xstring. No comments as to what's going on but the name is a hint and the key changes that apply to a short string are at line 4647-4650. Superfluous but it's clear the code changes the object to that of a std::string{}
// MSVC c++20, 32bit
#include <vector>
#include <iostream>
#include <string>
using std::cout, std::vector, std::string;
template<typename T>
void printobj(const T& x)
{
const int N = sizeof(void*);
// hack to show contents at the objects location. Not portable and probably UB
for (size_t i = 0; i < sizeof(T) / N; i++)
cout << (void*)((char*)&x + N * i) << " " << (void*)*(size_t*)((char*)&x + N * i) << "\n";
cout << (void*) x.data() << "\n";
}
int main()
{
vector<int> v{ 1,2,3,4,5 };
printobj(v);
v.~vector<int>(); // deletes allocated space and changes v's guts to 4 null pointers. Same as empty vec.
printobj(v);
string s{ "123412341234" }; // short string, no heap used
printobj(s);
s.~string(); // changes to zero length string with s[0]=='\0'
printobj(s);
// prevent double dtor
std::construct_at(&s, string{});
std::construct_at(&v, vector<int>{});
}
/* Output
00AFFB30 00CFBDA8
00AFFB34 00CF6DB8
00AFFB38 00CF6DCC
00AFFB3C 00CF6DCC
00CF6DB8
00AFFB30 00000000
00AFFB34 00000000
00AFFB38 00000000
00AFFB3C 00000000
00000000
00AFFB0C 00CFBBB0
00AFFB10 34333231
00AFFB14 34333231
00AFFB18 34333231
00AFFB1C CCCCCC00
00AFFB20 0000000C
00AFFB24 0000000F
00AFFB10
00AFFB0C 00000000
00AFFB10 34333200
00AFFB14 34333231
00AFFB18 34333231
00AFFB1C CCCCCC00
00AFFB20 00000000
00AFFB24 0000000F
00AFFB10
*/