Подтвердить что ты не робот

Почему Clang и VS2013 принимают начальные аргументы по умолчанию, но не GCC 4.8 или 4.9?

Как и в заголовке, у меня есть небольшая демонстрационная программа, которая компилируется со всеми этими компиляторами, но с дампами ядра при запуске после компиляции с gcc 4.8 и gcc 4.9:

Любые идеи относительно того, почему?

#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);
}

Мои настройки компиляции:

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

Вот обратная трассировка из 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 ***

4b9b3361

Ответ 1

Update

Похоже, что исправление проблемы было проверено в.


Интересный вопрос. Кажется, что это ошибка в том, как GCC обрабатывает инициализированные аргументы по умолчанию = {}, что было поздним дополнением к стандарту. Проблема может быть воспроизведена с помощью довольно простого класса вместо 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;
}

Скомпилированный с g++ (Ubuntu 4.8.1-2ubuntu1 ~ 12.04) 4.8.1, он отображает ту же проблему:

*** 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

Копаясь немного глубже, GCC, кажется, создает дополнительный объект (хотя он вызывает только конструктор и деструктор один раз), когда вы используете этот синтаксис:

#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;
}

Выход:

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

Изменение аргумента по умолчанию от SimpleClass x = {} до SimpleClass x = SimpleClass{} вызывает

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

как ожидалось.

Кажется, что происходит создание объекта, вызывается конструктор по умолчанию, а затем выполняется нечто похожее на memcpy. Этот "объект-призрак" - это то, что передается конструктору перемещения и изменено. Однако деструктор вызывается на исходном, немодифицированном объекте, который теперь разделяет некоторый указатель с объектом, созданным движением. Оба в конце концов пытаются освободить его, вызывая проблему.

Четыре изменения, которые вы заметили, исправили проблему, с учетом приведенного выше объяснения:

// 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);

Передача аргумента конструктору (Bar b(Foo{});) вместо использования аргумента по умолчанию также решает проблему.