std :: map参数,带有空的brace-initializers,用于GCC中的默认参数segfaults

时间:2015-03-03 16:40:26

标签: c++ c++11 gcc default-arguments

问题

我收到了用户报告我开发的库中的段错误的错误报告。

错误代码的最小例子是:

#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作为容器的大小,但是在循环内部(不应该执行)的段错误。

的变通方法

但是,我可以通过将声明更改为

修复问题
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也编译并运行代码就好了。 (即使使用与失败的gcc 4.8.2相同的libstdc ++)

工作示例:http://coliru.stacked-crooked.com/a/eb64a7053f542efd

问题

地图在函数内部肯定不是有效状态(详见下文)。 它看起来像一个GCC(或libstdc ++)错误,但我想确定我在这里没有犯一些愚蠢的错误。 很难相信这样的bug会在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

来自gdb的Backtrace:

#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

查看地图的内部状态表明代码确实必须失败:

libstdc ++中的

std::map::begin()返回值

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 个答案:

答案 0 :(得分:22)

看起来像这个bug was fixed in 4.8.3/4.9.0,错误报告有一个类似的例子,也有段错误说:

  

附加的最小测试用例具有以下功能   default-construct默认参数:

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

foo的构造函数输出其地址;我得到了以下内容   单次运行的输出:       构造了foo @ 0x7ffff10bdb7f       默认参数为0x7ffff10bdb60

     

它表明只构建了1个foo,而不是在同一个地址   作为默认参数的。这是一个漫长的一周,但我不能   看到代码有什么问题。在这是真正的代码   基于,运行foo的析构函数时发生了段错误   这是从默认参数移动构造的,因为   潜在的记忆似乎没有被初始化。

我们可以从live example看到4.9.0没有证明此问题。

我们可以看到这是defect report 994的有意功能以及随后的解决方案N3217

  

本文介绍了与当前相关的详细措辞变化   C ++ Working Draft N3126默认实现大括号初始值设定项   函数的参数,如N3139“不完全语言”中提出的   特写“由Bjarne Stroustrup撰写,从而也解决了核心问题994。

提案N3139: An Incomplete Language Feature中也包含了这一点。

有趣的是我注意到Visual Studio also has a bug with respect to brace-initializers as default arguments仍未解决。