Std:: map аргумент с пустыми инициализаторами скобок для аргументов segfaults по умолчанию в GCC

Проблема

У меня есть отчет об ошибке от пользователя, сообщающего о segfault в библиотеке, которую я разрабатываю.

Минимальный пример неисправного кода:

#include <map>
#include <string>
#include <iostream>

void f(std::map<std::string, std::string> m = {})
{
        std::cout << m.size() << "\n";
        for (const auto& s: m) {
                std::cout << s.first << "->" << s.second <<"\n";
        }
}

int main()
{
        f();
}

При компиляции с GCC (я тестировал 4.8.2 и 4.7.3) он правильно печатает 0 как размер контейнера, но segfault внутри цикла (который не должен выполняться вообще).

Обходные

Однако я могу исправить проблему, изменив объявление на:

void f(std::map<std::string, std::string> m = std::map<std::string, std::string>{})

Копирование map также работает:

void f(std::map<std::string, std::string> mx = {})
{
        auto m = mx;
        std::cout << m.size() << "\n";
        for (const auto& s: m) {
                std::cout << s.first << "->" << s.second <<"\n";
        }
}

Также изменяется работа с параметром const std::map<...>&.

GCC 4.9.1 отлично работает.

Clang также компилирует и запускает код просто отлично. (даже при использовании того же libstdС++, что и gcc 4.8.2)

Рабочий пример: http://coliru.stacked-crooked.com/a/eb64a7053f542efd

Вопрос

Карта определенно не находится в правильном состоянии внутри функции (подробности ниже). Это похоже на ошибку GCC (или libstdС++), но я хочу быть уверенным, что я не делаю какую-то глупую ошибку. Трудно поверить, что такая ошибка останется в gcc по меньшей мере для 2 основных версий.

Итак, мой вопрос: неправильный способ инициализации параметра std::map по умолчанию (и ошибка в моем коде) или это ошибка в stdlibc++ (или gcc)?

Я не ищу обходные пути (как я знаю, что делать, чтобы сделать работу с кодом) Когда он интегрирован в приложение, код нарушения отлично работает на некоторых компьютерах (даже если скомпилирован с gcc 4.8.2) на некоторых нет.

Подробнее

Я скомпилирую его, используя:

g++-4.8.2 -g -Wall -Wextra -pedantic  -std=c++11 /tmp/c.cpp -o /tmp/t

Backtrace из gdb:

#0  std::operator<< <char, std::char_traits<char>, std::allocator<char> > (__os=..., __str=...) at /usr/src/debug/sys-devel/gcc-4.8.2/build/x86_64-pc-linux-gnu/libstdc++-v3/include/bits/basic_string.h:2758
#1  0x0000000000400f36 in f (m=std::map with 0 elements) at /tmp/c.cpp:9
#2  0x0000000000400fe0 in main () at /tmp/c.cpp:15

/tmp/c.cpp:9 - это строка с std::cout << ...

Отчеты ASAN:

AddressSanitizer: SEGV on unknown address 0xffffffffffffffe8 

Это похоже на nullptr - 8

valgrind показывает:

==28183== Invalid read of size 8
==28183==    at 0x4ECC863: std::basic_ostream<char, std::char_traits<char> >& std::operator<< <char, std::char_traits<char>, std::allocator<char> >(std::basic_ostream<char, std::char_traits<char> >&, std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) (in /usr/lib64/gcc/x86_64-pc-linux-gnu/4.8.2/libstdc++.so.6.0.18)
==28183==    by 0x400BD5: f(std::map<std::string, std::string, std::less<std::string>, std::allocator<std::pair<std::string const, std::string> > >) (c.cpp:9)
==28183==    by 0x400C7F: main (c.cpp:15)
==28183==  Address 0xffffffffffffffe8 is not stack'd, malloc'd or (recently) free'd

Взгляд на внутреннее состояние карты показывает, что код действительно должен завершиться неудачей:

std::map::begin() в libstdС++ возвращает значение

this->_M_impl._M_header._M_parent

из этого внутреннего представления, std::map::end() возвращает:

&this->_M_impl._M_header

gdb показывает:

(gdb) print m._M_t._M_impl._M_header
$5 = {_M_color = std::_S_red, _M_parent = 0x0, _M_left = 0x7fffffffd6d8, _M_right = 0x7fffffffd6d8}
(gdb) print &m._M_t._M_impl._M_header
$6 = (std::_Rb_tree_node_base *) 0x7fffffffd6a8

Таким образом, значение begin() и end() не является одинаковым (begin() - nullptr), как указано стандартом для пустого std::map.

Ответ 1

Похоже, что ошибка была исправлена ​​в 4.8.3/4.9.0, отчет об ошибке, который имеет аналогичный пример, а также seg- ошибки говорят:

Прилагаемый минимальный тест имеет следующую функцию: аргумент по умолчанию, построенный по умолчанию:

void do_something( foo f = {} )
{     std::cout << "default argument is at " << &f << std::endl;
}

Конструктор для foo выводит свой адрес; Я получил следующее вывод из одного прогона:     построено foo @0x7ffff10bdb7f     аргумент по умолчанию равен 0x7ffff10bdb60

Это показывает, что было построено только 1 foo, а не по тому же адресу как и аргумент по умолчанию. Это была недельная неделя, но я не могу что-то не так с кодом. В реальном коде, на котором это было основанный на segfault, возникал при запуске деструктора foo который был построен по ходу из аргумента по умолчанию, поскольку основная память была, по-видимому, неинициализирована.

Из live example видно, что 4.9.0 не демонстрирует эту проблему.

Мы видим, что это была преднамеренная функциональность из отчет о дефектах 994 и последующее разрешение N3217:

В настоящем документе представлены подробные изменения формулировок относительно текущего C + + Рабочий проект N3126 для реализации инициализаторов скобок для дефолтов аргументы для функций, предложенные в N3139 "Неполный язык Feature" от Bjarne Stroustrup, тем самым также рассматривая основной вопрос 994.

Это также рассматривается в предложении N3139: Неполная функция языка.

Интересно отметить, что Visual Studio также имеет ошибку в отношении инициализаторов в виде аргументов по умолчанию, которые, я думаю, все еще не решены.