問題
私が開発したライブラリで segfault を報告しているユーザーからバグ レポートを受け取りました。
問題のあるコードの最小限の例は次のとおりです。
#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++) のバグのように見えますが、ここで愚かな間違いを犯していないことを確認したいと思います。このようなバグが少なくとも 2 つのメジャー バージョンで gcc に残るとは信じがたいです。
だから私の質問は次のとおりです:デフォルト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()
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()
そのため、との値は、 empty の標準で義務付けられてend()
いるものと同じではありません ( begin()
nullptr です) std::map
。