2015-03-03 8 views
33

Problem

Dostałem raport o błędzie od użytkownika zgłaszania segfault w bibliotece rozwijam.std :: map Argument z pustymi Brace-inicjalizatory naruszenia ochrony pamięci domyślny argument GCC

Minimalna przykład błędnego kod jest:

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

wkompilowaną GCC (I badane 4.8.2 i 4.7.3) prawidłowo drukuje 0 jak rozmiar pojemnika, ale zwraca błąd wewnątrz pętli (które w ogóle nie powinny być wykonywane).

Obejścia

Mogę jednak naprawić problemu poprzez zmianę deklarację:

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

Kopiowanie map działa tak samo:

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

Zmiana parametru do const std::map<...>& działa również.

GCC 4.9.1 działa bez zarzutu.

Clang również kompiluje i uruchamia kod dobrze. (Nawet przy użyciu tego samego libstdC++ za brak gcc 4.8.2)

Praca przykład: http://coliru.stacked-crooked.com/a/eb64a7053f542efd

Pytanie

Mapa na pewno nie jest w poprawnym stanie wewnątrz funkcji (szczegóły ryczeć). Wygląda jak błąd GCC (lub libstdC++), ale chcę mieć pewność, że nie robię tutaj głupiego błędu. Trudno uwierzyć, że taki błąd pozostałby w gcc dla co najmniej 2 wersji głównych.

Moje pytanie brzmi: czy sposób inicjowania domyślnego parametru std::map jest nieprawidłowy (i błąd w moim kodzie), czy jest to błąd w stdlibc++ (lub gcc)?

Nie szukam obejścia (ponieważ wiem, co zrobić, aby wykonać pracę kodu) Po zintegrowaniu z aplikacją, kod naruszający prawa działa dobrze na niektórych komputerach (nawet gdy jest skompilowany z gcc 4.8.2) na niektórzy nie.

Szczegóły

kompilowania go za pomocą:

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

Ślad z 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 jest linia z std::cout << ...

raportów Asan:

AddressSanitizer: SEGV on unknown address 0xffffffffffffffe8 

Wydaje się to jak nullptr - 8

valgrind pokazuje:

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

Patrząc na stan wewnętrzny mapy wynika, że kod naprawdę musi zawieść:

std::map::begin() w libstdC++ zwraca wartość

this->_M_impl._M_header._M_parent 

od jego reprezentacja wewnętrzna std::map::end() powraca:

&this->_M_impl._M_header 

gdb Wystawy:

(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 

Więc wartość begin() i end() nie są takie same (begin() is nullptr) zgodnie z obowiązkiem standardowym dla pustego std::map.

+0

FWIW, Kiedy zastąpiłem 'std :: cout << s.pierwszy << "->" << s.second << "\ n"; 'od' std :: cout << "Przyszedł tutaj \ n"; ', program drukuje wiersz raz, a następnie zawiesza się. –

+4

"Trudno uwierzyć, że taki błąd pozostanie w gcc dla co najmniej 2 wersji głównych." - musisz być nowy tutaj –

Odpowiedz

22

wygląda następująco bug was fixed in 4.8.3/4.9.0, raport błędów, który ma podobny przykład i również SEG-usterek mówi:

Załączony minimalny testcase posiada następujące funkcje z domyślnej skonstruowane domyślnym argumentem:

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

Konstruktor dla foo wyprowadza swój adres; Mam następujący wyjście z jednego biegu: skonstruowane foo @ 0x7ffff10bdb7f domyślny argument jest w 0x7ffff10bdb60

To pokazuje, że tylko 1 foo została skonstruowana, a nie pod tym samym adresem jak w domyślnym argumentem. To był długi tydzień, ale nie widzę nic złego w tym kodzie. W rzeczywistym kodzie, na którym było to oparte , wystąpił błąd segfault podczas uruchamiania destruktora foo , który został skonstruowany z domyślnego argumentu, ponieważ pamięć bazowa była pozornie niezainicjowana.

Możemy zobaczyć z live example, że 4.9.0 nie wykazuje tego problemu.

Widzimy to było zamierzone funkcjonalność z defect report 994 i późniejsze rozdzielczości N3217:

Przedstawiono szczegółową treść zmienia się w stosunku do obecnego projektu C++ robocza N3126 wdrożyć Brace-inicjalizatory domyślnie argumenty za funkcje, jak zaproponowano w N3139 "Niekompletny język" Funkcja "przez Bjarne Stroustrup, tym samym odnosząc się również do głównej kwestii 994.

Jest to również objęte propozycją N3139: An Incomplete Language Feature.

Interesujący jest fakt, że Visual Studio also has a bug with respect to brace-initializers as default arguments, który według mnie jest nadal nierozwiązany.

+1

Dziękuję! Dokładnie to chciałem wiedzieć i nie mogłem nie znajdź. – v154c1

Powiązane problemy