Why do std string and vector's destructor generate unnecessary code?

Viewed 85

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.

Compiler Explorer

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
    */
0 Answers
Related