Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

G++-11 destruction order changed from G++9

We have following code(it's more complicated ofc, I tried to make a minimal example).

#include <iostream>
#include <vector>
#include <string>
#include <memory>

template<typename T>
struct use_type
{
    use_type(T& v) : value(v) {}
    T& value;
};

template<typename T>
use_type<T> use(T& value) { return use_type<T>(value); }

template<typename T>
use_type<T> use(const T& value) { return use_type<T>(const_cast<T&>(value)); }

template<typename T>
struct printer_helper
{
    void use(const use_type<T>& use) { uses.push_back(use); }
    void final_action()
    {
        for (const auto& use : uses)
        {
            std::cout << use.value.size() << std::endl;
            for (const auto& v : use.value)
            {
                std::cout << v << ",";
            }
            std::cout << std::endl;
        }
    }
    std::vector<use_type<T>> uses;
};

template<typename T>                                        
struct printer
{
    printer() { helper = new printer_helper<T>(); }
    printer<T>& operator , (const use_type<T>& t)
    {
        helper->use(t);
        return *this;
    }
    ~printer()
    {
        helper->final_action();
        delete helper;
    }
    printer(const printer&) = delete;
    printer& operator =(const printer&) = delete;
    printer(printer&&) = default;
    printer& operator =(printer&&) = default;
    printer_helper<T>* helper;
};

template<typename T>
struct printer_creator
{
    printer<T> operator << (const char*) { return {}; }
};

int main()
{
    using vec = std::vector<std::string>;
    {
    /*vec v1{"abc", "bcd"};
    vec v2{"new", "old", "real"};*/
    printer_creator<vec> p;
    p << "", use(vec{"abc", "bcd"}), use(vec{"new", "old", "real"});
    //p << p, use(v1), use(v2);
    }
}

This code works perfectly fine on g++ before g++-11. With g++-11 it's either segfault or trash in the output. Is it UB? And if yes, probably you can tell me, which gcc change makes it fail and quotes from standard if possible?

Live examples:

g++-10 g++-11

like image 409
ForEveR Avatar asked Dec 08 '22 09:12

ForEveR


2 Answers

The temporary vectors are destructed at the end of the line where they are created. p is destructed at the end of its scope (i.e. after the vectors). As the destructor of p uses references to the vectors which are now dangling your code has undefined behaviour.

If it ever worked that was pure "luck" that the vector destructors left the vectors in a usable state.

like image 123
Alan Birtles Avatar answered Dec 27 '22 15:12

Alan Birtles


In

p << "", use(vec{"abc", "bcd"}), use(vec{"new", "old", "real"});

You create 3 temporaries

  • printer<vec> from p << "")
  • vec{"abc", "bcd"}
  • vec{"new", "old", "real"}

without sequence between them (see note below), so they may be created in any order.

Only destruction from left to right is correct (so construction from right to left), other order would use dangling reference and is UB. And as you don't have guarantee of order...

Note:
We lose short-circuit by overloading operator &&/operator ||,
we lose left to right evaluation order by overloading operator,

like image 31
Jarod42 Avatar answered Dec 27 '22 16:12

Jarod42