2015-03-03 7 views
33

Problema

ho ricevuto una segnalazione di bug da utente segnala un segfault in libreria io sviluppo.std :: map discussione con vuoti brace-inizializzatori per segfaults argomento di default in GCC

L'esempio minima del codice difettoso è:

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

Quando viene compilato con GCC (I testato 4.8.2 e 4.7.3) la stampa sia corretta 0 la dimensione del contenitore, ma segfaults all'interno del loop (che non dovrebbe essere eseguito affatto).

Soluzioni alternative

Tuttavia, posso risolvere il problema cambiando la dichiarazione:

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

Copia dei map opere così:

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

Cambiando il parametro da const std::map<...>& funziona anche

GCC 4.9.1 funziona correttamente.

Clang compila anche ed esegue il codice bene. (Anche quando si utilizza lo stesso libstdC++ in quanto non gcc 4.8.2)

lavoro esempio: http://coliru.stacked-crooked.com/a/eb64a7053f542efd

Domanda

La mappa non è decisamente in stato valido all'interno della funzione (dettagli sotto). Sembra un bug GCC (o libstdC++), ma voglio essere sicuro di non fare un errore stupido qui. È difficile credere che un tale errore rimanga in gcc per almeno 2 versioni principali.

Quindi la mia domanda è: E 'il modo di inizializzazione di default std::map parametro sbagliato (e bug nel mio codice) o è un bug in stdlibc++ (o gcc)?

Non sto cercando soluzioni alternative (come so cosa fare per far funzionare il codice) Quando integrato nell'applicazione, il codice incriminato viene eseguito correttamente su alcuni computer (anche se compilato con gcc 4.8.2) su alcuni no.

dettagli

compilo usando:

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

Backtrace da 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 è la linea con std::cout << ...

rapporti Asan:

AddressSanitizer: SEGV on unknown address 0xffffffffffffffe8 

Questo mi sembra nullptr - 8

valgrind mostra:

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

Guardando stato interno della mappa mostra che la il codice deve fallire davvero:

std::map::begin() in libstdC++ restituisce valore

this->_M_impl._M_header._M_parent 

dalla sua rappresentazione interna, std::map::end() rendimenti:

&this->_M_impl._M_header 

gdb mostra:

(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 

Così valore della begin() e end() non sono la stessa (begin() è nullptr) come richiesto dallo standard per lo std::map vuoto.

+0

FWIW, Quando ho sostituito 'std :: cout << s.first << "->" << s.second << "\ n"; 'da' std :: cout << "Came here \ n"; ', il programma stampa la riga una volta e poi si blocca. –

+4

"È difficile credere che un tale errore rimanga in gcc per almeno 2 versioni principali." - Devi essere nuovo qui –

risposta

22

appare come questa bug was fixed in 4.8.3/4.9.0, la segnalazione di bug che ha un esempio simile e anche seg-difetti dice:

Il testcase minimo allegato ha la seguente funzione con argomento di default default-costruito:

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

Il costruttore per foo emette il proprio indirizzo; Ho ottenuto il seguente uscita da una singola esecuzione: costruito foo @ 0x7ffff10bdb7f argomento predefinito è a 0x7ffff10bdb60

Essa mostra che solo l'1 foo è stato costruito, e non allo stesso indirizzo quella del argomento di default. È stata una settimana mooolto, ma non riesco a vedere qualcosa nel codice. Nel codice reale su cui si basava questo , si verificava un segfault quando si eseguiva il distruttore di un foo che era stato spostato dall'argomento predefinito, perché la memoria sottostante di era apparentemente non inizializzata.

Possiamo vedere da un live example che 4.9.0 non dimostra questo problema.

Possiamo vedere che questo era funzionalità voluta dal defect report 994 e la successiva risoluzione N3217:

Questo documento presenta formulazione dettagliata cambia rispetto agli attuali Draft C++ di lavoro N3126 per implementare brace-inizializzatori per impostazione predefinita argomenti a favore funzioni, come proposto in N3139 "Un linguaggio incompleto Feature" di Bjarne Stroustrup, affrontando in tal modo anche il problema principale 994.

Questo è anche contemplato nella proposta N3139: An Incomplete Language Feature.

Interessante notare che Visual Studio also has a bug with respect to brace-initializers as default arguments che penso sia ancora irrisolto.

+1

Grazie! Questo è esattamente quello che volevo sapere e non potevo trovo – v154c1

Problemi correlati