c++ - parametros - como usar argc y argv en c
std:: map argumento con llaves vacĂas-inicializadores para segfaults predeterminados de argumentos en GCC (1)
Problema
Recibí un informe de error del usuario que informaba un segfault en la biblioteca que desarrollé.
El ejemplo mínimo del código defectuoso es:
#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();
}
Cuando se compiló con GCC (probé 4.8.2 y 4.7.3), imprime correctamente 0
como el tamaño del contenedor, pero segfaults dentro del bucle (que no debe ejecutarse en absoluto).
Soluciones provisionales
Sin embargo, puedo solucionar el problema cambiando la declaración a:
void f(std::map<std::string, std::string> m = std::map<std::string, std::string>{})
Copiar el map
funciona:
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";
}
}
Cambiar el parámetro a const std::map<...>&
también funciona.
GCC 4.9.1 funciona bien.
Clang también compila y ejecuta el código muy bien. (incluso cuando se usa el mismo libstdc ++ como gcc deficiente 4.8.2)
Ejemplo de trabajo : http://coliru.stacked-crooked.com/a/eb64a7053f542efd
Pregunta
El mapa definitivamente no está en estado válido dentro de la función (detalles a continuación). Parece un error GCC (o libstdc ++), pero quiero estar seguro de que no estoy cometiendo un error estúpido aquí. Es difícil creer que ese error permanezca en gcc por lo menos en 2 versiones principales.
Entonces, mi pregunta es: ¿es incorrecta la forma de inicializar el parámetro predeterminado std::map
(y error en mi código) o es un error en stdlibc++
(o gcc
)?
No estoy buscando soluciones (ya que sé qué hacer para hacer el trabajo del código). Cuando se integra en la aplicación, el código ofensivo se ejecuta correctamente en algunas computadoras (incluso cuando se compila con gcc 4.8.2) en algunas no.
Detalles
Lo compilo usando:
g++-4.8.2 -g -Wall -Wextra -pedantic -std=c++11 /tmp/c.cpp -o /tmp/t
Backtrace de 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 es la línea con std::cout << ...
ASAN informa:
AddressSanitizer: SEGV on unknown address 0xffffffffffffffe8
Esto parece nullptr - 8
valgrind muestra:
==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
Ver el estado interno del mapa muestra que el código realmente tiene que fallar:
std::map::begin()
en libstdc ++ devuelve valor de
this->_M_impl._M_header._M_parent
desde su representación interna, std::map::end()
devuelve:
&this->_M_impl._M_header
gdb muestra:
(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
Por lo tanto, el valor de begin()
y end()
no es el mismo ( begin()
es nullptr) como lo exige el estándar para empty std::map
.
Parece que este error se corrigió en 4.8.3 / 4.9.0 , el informe de error que tiene un ejemplo similar y también seg-faults dice:
El archivo de prueba mínimo adjunto tiene la siguiente función con el argumento predeterminado construido por defecto:
void do_something( foo f = {} ) { std::cout << "default argument is at " << &f << std::endl; }
El constructor para foo saca su dirección; Obtuve el siguiente resultado de una sola ejecución: construido por defecto @ 0x7ffff10bdb7f el argumento predeterminado está en 0x7ffff10bdb60
Muestra que solo se construyó 1 foo, y no en la misma dirección que la del argumento predeterminado. Ha sido una semana muy larga, pero no puedo ver nada malo con el código. En el código real en el que se basaba esto, se producía una falla de segmento cuando se ejecutaba el destructor de un foo que se construyó con movimiento desde el argumento predeterminado, porque la memoria subyacente aparentemente no se había inicializado.
Podemos ver a partir de un ejemplo en vivo que 4.9.0
no demuestra este problema.
Podemos ver que esto fue una funcionalidad intencional del informe de defectos 994 y la resolución posterior N3217 :
Este documento presenta cambios de redacción detallados relativos al actual borrador de C ++ Working Draft N3126 para implementar llaves inicializadoras para argumentos predeterminados para funciones, como se propone en N3139 "Una característica de lenguaje incompleto" de Bjarne Stroustrup, abordando también la cuestión central 994.
Esto también se trata en la propuesta N3139: Una función de lenguaje incompleto .
Es interesante observar que Visual Studio también tiene un error con respecto a los inicializadores de corchetes como argumentos predeterminados que creo que aún no se han resuelto.