std::map GCC 中默认参数段错误的带空大括号初始化器的参数
std::map argument with empty brace-initializers for default argument segfaults in GCC
问题
我收到了用户报告我开发的库中存在段错误的错误报告。
错误代码的最小示例是:
#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++)错误,但我想确定我没有在这里犯一些愚蠢的错误。
很难相信这样的错误会在 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
查看地图的内部状态表明代码确实失败了:
std::map::begin()
in libstdc++ returns
的值
this->_M_impl._M_header._M_parent
从它的内部表示,std::map::end()
returns:
&this->_M_impl._M_header
gdb 显示:
(gdb) print m._M_t._M_impl._M_header
= {_M_color = std::_S_red, _M_parent = 0x0, _M_left = 0x7fffffffd6d8, _M_right = 0x7fffffffd6d8}
(gdb) print &m._M_t._M_impl._M_header
= (std::_Rb_tree_node_base *) 0x7fffffffd6a8
因此 begin()
和 end()
的值与空 std::map
.
标准规定的不同(begin()
是 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 and the subsequent resolution 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 仍未解决。
问题
我收到了用户报告我开发的库中存在段错误的错误报告。
错误代码的最小示例是:
#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++)错误,但我想确定我没有在这里犯一些愚蠢的错误。 很难相信这样的错误会在 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
查看地图的内部状态表明代码确实失败了:
std::map::begin()
in libstdc++ returns
this->_M_impl._M_header._M_parent
从它的内部表示,std::map::end()
returns:
&this->_M_impl._M_header
gdb 显示:
(gdb) print m._M_t._M_impl._M_header
= {_M_color = std::_S_red, _M_parent = 0x0, _M_left = 0x7fffffffd6d8, _M_right = 0x7fffffffd6d8}
(gdb) print &m._M_t._M_impl._M_header
= (std::_Rb_tree_node_base *) 0x7fffffffd6a8
因此 begin()
和 end()
的值与空 std::map
.
begin()
是 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 and the subsequent resolution 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 仍未解决。