48 votes

Pourquoi Clang et VS2013 acceptent-ils des arguments par défaut de type "moving brace-initialized", mais pas GCC 4.8 ou 4.9 ?

Comme le titre le suggère, j'ai un petit programme de démonstration qui se compile avec tous ces compilateurs, mais le noyau se vide lorsqu'il est exécuté après avoir été compilé avec gcc 4.8 et gcc 4.9 :

Une idée de la raison ?

#include <unordered_map>

struct Foo : std::unordered_map<int,int> {
    using std::unordered_map<int, int>::unordered_map;
    // ~Foo() = default; // adding this allows it to work
};

struct Bar {
    Bar(Foo f = {}) : _f(std::move(f)) {}
    // using any of the following constructors fixes the problem:
    // Bar(Foo f = Foo()) : _f(std::move(f)) {}
    // Bar(Foo f = {}) : _f(f) {}

    Foo _f;
};

int main() {
    Bar b;

    // the following code works as expected
    // Foo f1 = {};
    // Foo f2 = std::move(f1);
}

Mes paramètres de compilation :

g++ --std=c++11 main.cpp

Voici un backtrace de GDB :

#0  0x00007fff95d50866 in __pthread_kill ()
#1  0x00007fff90ba435c in pthread_kill ()
#2  0x00007fff8e7d1bba in abort ()
#3  0x00007fff9682e093 in free ()
#4  0x0000000100002108 in __gnu_cxx::new_allocator<std::__detail::_Hash_node_base*>::deallocate ()
#5  0x0000000100001e7d in std::allocator_traits<std::allocator<std::__detail::_Hash_node_base*> >::deallocate ()
#6  0x0000000100001adc in std::__detail::_Hashtable_alloc<std::allocator<std::__detail::_Hash_node<std::pair<int const, int>, false> > >::_M_deallocate_buckets ()
#7  0x000000010000182e in std::_Hashtable<int, std::pair<int const, int>, std::allocator<std::pair<int const, int> >, std::__detail::_Select1st, std::equal_to<int>, std::hash<int>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<false, false, true> >::_M_deallocate_buckets ()
#8  0x000000010000155a in std::_Hashtable<int, std::pair<int const, int>, std::allocator<std::pair<int const, int> >, std::__detail::_Select1st, std::equal_to<int>, std::hash<int>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<false, false, true> >::~_Hashtable ()
#9  0x000000010000135c in std::unordered_map<int, int, std::hash<int>, std::equal_to<int>, std::allocator<std::pair<int const, int> > >::~unordered_map ()
#10 0x00000001000013de in Foo::~Foo ()
#11 0x0000000100001482 in Bar::~Bar ()
#12 0x0000000100001294 in main ()

*** error for object 0x1003038a0: pointer being freed was not allocated ***

11voto

jerry Points 2075

Mise à jour

Il semble qu'un La correction du problème a été vérifiée dans .


Question intéressante. Il semble bien qu'il s'agisse d'un bogue dans la façon dont GCC gère les fichiers = {} des arguments par défaut initialisés, ce qui était un ajout tardif à la norme . Le problème peut être reproduit avec une classe assez simple à la place de std::unordered_map<int,int> :

#include <utility>

struct PtrClass
{
    int *p = nullptr;

    PtrClass()
    {
        p = new int;
    }

    PtrClass(PtrClass&& rhs) : p(rhs.p)
    {
        rhs.p = nullptr;
    }

    ~PtrClass()
    {
        delete p;
    }
};

void DefArgFunc(PtrClass x = {})
{
    PtrClass x2{std::move(x)};
}

int main()
{
    DefArgFunc();
    return 0;
}

Compilé avec g++ (Ubuntu 4.8.1-2ubuntu1~12.04) 4.8.1 il affiche le même problème :

*** glibc detected *** ./a.out: double free or corruption (fasttop): 0x0000000001aa9010 ***
======= Backtrace: =========
/lib/x86_64-linux-gnu/libc.so.6(+0x7eb96)[0x7fc2cd196b96]
./a.out[0x400721]
./a.out[0x4006ac]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xed)[0x7fc2cd13976d]
./a.out[0x400559]
======= Memory map: ========
bash: line 7:  2916 Aborted                 (core dumped) ./a.out

En creusant un peu plus, GCC semble créer un objet supplémentaire (bien qu'il n'appelle le constructeur et le destructeur qu'une fois chacun) lorsque vous utilisez cette syntaxe :

#include <utility>
#include <iostream>

struct SimpleClass
{    
    SimpleClass()
    {
        std::cout << "In constructor: " << this << std::endl;
    }

    ~SimpleClass()
    {
        std::cout  << "In destructor: " << this << std::endl;
    }
};

void DefArgFunc(SimpleClass x = {})
{
        std::cout << "In DefArgFunc: " << &x << std::endl;
}

int main()
{
    DefArgFunc();
    return 0;
}

Sortie :

In constructor: 0x7fffbf873ebf
In DefArgFunc: 0x7fffbf873ea0
In destructor: 0x7fffbf873ebf

Changer l'argument par défaut de SimpleClass x = {} a SimpleClass x = SimpleClass{} produit

In constructor: 0x7fffdde483bf
In DefArgFunc: 0x7fffdde483bf
In destructor: 0x7fffdde483bf

comme prévu.

Ce qui semble se passer, c'est qu'un objet est créé, le constructeur par défaut est appelé, puis quelque chose de similaire à un memcpy est effectuée. C'est cet "objet fantôme" qui est passé au constructeur du mouvement et modifié. Cependant, le destructeur est appelé sur l'objet original, non modifié, qui partage maintenant un pointeur avec l'objet construit pour le déplacement. Les deux tentent finalement de le libérer, ce qui cause le problème.

Les quatre changements qui, selon vous, ont résolu le problème sont logiques compte tenu de l'explication ci-dessus :

// 1
// adding the destructor inhibits the compiler generated move constructor for Foo,
// so the copy constructor is called instead and the moved-to object gets a new
// pointer that it doesn't share with the "ghost object", hence no double-free
~Foo() = default;

// 2
// No  `= {}` default argument, GCC bug isn't triggered, no "ghost object"
Bar(Foo f = Foo()) : _f(std::move(f)) {}

// 3
// The copy constructor is called instead of the move constructor
Bar(Foo f = {}) : _f(f) {}

// 4
// No  `= {}` default argument, GCC bug isn't triggered, no "ghost object"
Foo f1 = {};
Foo f2 = std::move(f1);

Le passage d'un argument au constructeur ( Bar b(Foo{}); ) plutôt que d'utiliser l'argument par défaut résout également le problème.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X