c++ - GCC 中默认参数段错误的 std::map 参数和空括号初始化器

标签 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

问题

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

#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

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

std::map::begin()在 libstdc++ 中返回

的值
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() 不同(std::map 为 nullptr) .

最佳答案

看起来像 bug was fixed in 4.8.3/4.9.0 ,具有类似示例和段错误的错误报告说:

The attached minimal testcase has the following function with default-constructed default argument:

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

The constructor for foo outputs its address; I got the following output from a single run: constructed foo @ 0x7ffff10bdb7f default argument is at 0x7ffff10bdb60

It shows that only 1 foo was constructed, and not at the same address as that of the default argument. It's been a loooong week, but I can't see anything wrong with the code. In the real code on which this was based, a segfault was occurring when running the destructor of a foo that was move-constructed from the default argument, because the underlying memory was seemingly uninitialised.

我们可以从 live example 看到4.9.0 没有说明这个问题。

我们可以从 defect report 994 看出这是有意的功能。和随后的决议N3217 :

This paper presents detailed wording changes relative to the current C++ Working Draft N3126 to implement brace-initializers for default arguments for functions, as proposed in N3139 "An Incomplete Language Feature" by Bjarne Stroustrup, thereby also addressing core issue 994.

提案N3139: An Incomplete Language Feature 中也涵盖了这一点。 .

有趣的是 Visual Studio also has a bug with respect to brace-initializers as default arguments我认为仍然没有解决。

关于c++ - GCC 中默认参数段错误的 std::map 参数和空括号初始化器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28837142/

相关文章:

c++ - 标记粘贴运算符 (##) 正在占用我的 C++ 宏中的空格

c++11 为什么这个 move 构造函数不起作用?

c - 如何避免使用gcc在c中包装输入数据?

c - 为什么以及如何,这个 C 程序是否准确显示 7.21?

c++ - 为什么不能对 std::vector 使用前向声明?

c++ - 如何使用 STL 为数据点创建最大和最小堆?

c++ - 判断串口是否存在,Linux C/C++

c++ - 是什么构成了未指定的 block 作用域静态或线程存储持续时间变量的初始化失败?

c++ - 防止 std::atomic 溢出

c++ - 为什么 MSVC (Visual C++) 需要单独的 dllimport 和 dllexport 属性而 gcc 不需要?