質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.30%
C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Q&A

解決済

1回答

618閲覧

c++ queueのpop()時にsegmentation faultする

iwannatto

総合スコア1

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

0グッド

1クリップ

投稿2024/05/19 04:03

実現したいこと

c++ queueのpop()時に発生するsegmentation faultの原因を知りたい

発生している問題・分からないこと

c++でAtCoderの問題( https://atcoder.jp/contests/abc339/tasks/abc339_d )を解いている最中にstd::queue::pop()にてsegmantation faultが発生した。

エラーメッセージ

error

1...(省略) 2671 36 43 5 4 6 5670 66 72 6 3 7 8671 96 100 6 1 7 11Segmentation fault (core dumped)

該当のソースコード

C++

1#include <algorithm> 2#include <bitset> 3#include <cassert> 4#include <cmath> 5#include <complex> 6#include <functional> 7#include <iomanip> 8#include <iostream> 9#include <iterator> 10#include <limits> 11#include <list> 12#include <map> 13#include <numeric> 14#include <queue> 15#include <set> 16#include <string> 17#include <tuple> 18#include <utility> 19#include <unordered_map> 20#include <unordered_set> 21#include <vector> 22using namespace std; 23 24// #include <atcoder/all> 25// using namespace atcoder; 26 27// #include <boost/multiprecision/cpp_dec_float.hpp> 28// #include <boost/multiprecision/cpp_int.hpp> 29// namespace mp = boost::multiprecision; 30// // 任意長整数型 31// using Bint = mp::cpp_int; 32 33using ll = long long int; 34 35#define FOR(i, a, b) for (ll i = (a); i < (ll)(b); ++i) 36#define RFOR(i, a, b) for (ll i = (a); i > (ll)(b); --i) 37#define REP(i, n) for (ll i = 0; i < (ll)(n); ++i) 38#define INF numeric_limits<int>::max() 39#define MINF numeric_limits<int>::min() 40#define LLINF numeric_limits<long long int>::max() 41#define LLMINF numeric_limits<long long int>::min() 42 43using Pair = pair<ll, char>; 44using Map = map<ll, ll>; 45using Set = set<ll>; 46using Hash = unordered_map<int, int>; 47using Tuple = tuple<double, int, int>; 48 49// using mint = modint998244353; 50// using mint = modint1000000007; 51// using mint = modint; 52 53void print1d(vector<ll>& v) { 54 REP(i, v.size()) { 55 cout << v[i] << " "; 56 } 57 cout << endl; 58} 59 60void print2d(vector<vector<ll>> &v) { 61 // cout << hex; 62 REP(i, v.size()) { 63 REP(j, v[i].size()) { 64 cout << v[i][j] << " "; 65 } 66 cout << endl; 67 } 68 // cout << dec; 69} 70 71void printfield(vector<string> &S) { 72 REP(i, S.size()) { 73 cout << S[i] << endl; 74 } 75} 76 77vector<vector<ll>> dxy = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}}; 78 79// const int MAX = 510000; 80// mint fac[MAX], finv[MAX], inv[MAX]; 81 82// // テーブルを作る前処理 83// void COMinit() { 84// const int MOD = mint::mod(); 85// fac[0] = fac[1] = 1; 86// finv[0] = finv[1] = 1; 87// inv[1] = 1; 88// for (int i = 2; i < MAX; i++){ 89// fac[i] = fac[i - 1] * i; 90// inv[i] = MOD - inv[MOD%i] * (MOD / i); 91// finv[i] = finv[i - 1] * inv[i]; 92// } 93// } 94 95// // 二項係数計算 96// mint COM(int n, int k){ 97// if (n < k) return 0; 98// if (n < 0 || k < 0) return 0; 99// return fac[n] * finv[k] * finv[n - k]; 100// } 101 102// --- 103 104class Status { 105public: 106 ll i1, j1; 107 ll i2, j2; 108 109 bool completed() { 110 return (i1 == i2) && (j1 == j2); 111 } 112 113 bool operator<(const Status& s2) const { 114 return i1 < s2.i1 || j1 < s2.j1 || i2 < s2.i2 || j2 < s2.j2; 115 } 116}; 117 118int main() { 119 ll N = 10; 120 vector<string> S(N); 121 S[0] = ".........."; 122 S[1] = ".........."; 123 S[2] = ".........."; 124 S[3] = ".........."; 125 S[4] = "....P....."; 126 S[5] = ".....P...."; 127 S[6] = ".........."; 128 S[7] = ".........."; 129 S[8] = ".........."; 130 S[9] = ".........."; 131 132 Status s; 133 s.i1 = -1; 134 REP(i, N) { 135 REP(j, N) { 136 if (S[i][j] == 'P') { 137 if (s.i1 == -1) { 138 s.i1 = i; 139 s.j1 = j; 140 } else { 141 s.i2 = i; 142 s.j2 = j; 143 } 144 } 145 } 146 } 147 148 map<Status, ll> m; 149 queue<pair<ll, Status>> q; 150 m[s] = 0; 151 q.push(pair<ll, Status>(0, s)); 152 153 154 while (!q.empty()) { 155 156 auto [d, s] = q.front(); 157 cout << q.size() << endl; 158 cout << d << endl; 159 cout << s.i1 << " " << s.j1 << " " << s.i2 << " " << s.j2 << endl; 160 q.pop(); // <======= segmentation fault 161 162 if (s.completed()) { 163 cout << d << endl; 164 return 0; 165 } 166 167 REP(k, 4) { 168 ll ni1 = s.i1 + dxy[k][0]; 169 ll nj1 = s.j1 + dxy[k][1]; 170 ll ni2 = s.i2 + dxy[k][0]; 171 ll nj2 = s.j2 + dxy[k][1]; 172 Status ns = s; 173 174 if (0 <= ni1 && ni1 < N && 0 <= nj1 && nj1 < N && S[ni1][nj1] != '#') { 175 ns.i1 = ni1; 176 ns.j1 = nj1; 177 } 178 if (0 <= ni2 && ni2 < N && 0 <= nj2 && nj2 < N && S[ni2][nj2] != '#') { 179 ns.i2 = ni2; 180 ns.j2 = nj2; 181 } 182 183 if (m.count(ns) == 0) { 184 m[ns] = d + 1; 185 q.push(pair<ll, Status>(d + 1, ns)); 186 } 187 } 188 } 189 190 cout << -1 << endl; 191 192 return 0; 193}

試したこと・調べたこと

  • teratailやGoogle等で検索した
  • ソースコードを自分なりに変更した
  • 知人に聞いた
  • その他
上記の詳細・結果
  • エラーメッセージの下から4行目の出力(671)がqueue.size()であり、空のqueueをpopしたのが原因ではないことを確認した。

  • このコードは113行目のStatusの比較演算子の実装に問題があり、そこを直すとsegmentation faultも出なくなる。ただ、これはqueueへpushされる値が変わってsegmentation faultが発生しなくなっただけなので、なぜこの条件下でpop()でsegmentation faultが発生するかの回答にはなっていない。

  • gdbでstack traceを追ってみると次のようになっていた。

txt

1(gdb) bt 2#0 __GI___libc_free (mem=0x7) at malloc.c:3102 3#1 0x0000563d22dd0490 in __gnu_cxx::new_allocator<std::pair<long long, Status> >::deallocate ( 4 this=0x7ffcaeee7950, __p=0x7) at /usr/include/c++/9/ext/new_allocator.h:128 5#2 0x0000563d22dcf88d in std::allocator_traits<std::allocator<std::pair<long long, Status> > >::deallocate (__a=..., __p=0x7, __n=12) at /usr/include/c++/9/bits/alloc_traits.h:469 6#3 0x0000563d22dcde0e in std::_Deque_base<std::pair<long long, Status>, std::allocator<std::pair<long long, Status> > >::_M_deallocate_node (this=0x7ffcaeee7950, __p=0x7) 7 at /usr/include/c++/9/bits/stl_deque.h:627 8#4 0x0000563d22dcbec5 in std::deque<std::pair<long long, Status>, std::allocator<std::pair<long long, Status> > >::_M_pop_front_aux (this=0x7ffcaeee7950) at /usr/include/c++/9/bits/deque.tcc:576 9#5 0x0000563d22dc7576 in std::deque<std::pair<long long, Status>, std::allocator<std::pair<long long, Status> > >::pop_front (this=0x7ffcaeee7950) at /usr/include/c++/9/bits/stl_deque.h:1616 10#6 0x0000563d22dc4766 in std::queue<std::pair<long long, Status>, std::deque<std::pair<long long, Status>, std::allocator<std::pair<long long, Status> > > >::pop (this=0x7ffcaeee7950) 11--Type <RET> for more, q to quit, c to continue without paging-- 12 at /usr/include/c++/9/bits/stl_queue.h:295 13#7 0x0000563d22dbd3f9 in main () at a.cc:160

#0のmem=0x7が明らかに怪しいが、なぜこうなるかがわからなかった。

補足

特になし

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

jimbe

2024/05/19 19:35

paiza.io にコピペして 158-159 を(表示しきれないので) コメントにして実行したら、q.size が 2467 で 10 が表示されました。
melian

2024/05/19 21:26

paize.io では C コンパイラとして clang が使われているそうです。以下の様に AddressSanitizer を利用すると stack overflow が発生している事が判ります。AddressSanitizer は gcc でも利用可能で(-fsanitize=address を指定)、実行結果は clang の場合と同じです。 $ clang++ --version Ubuntu clang version 16.0.6 (15) $ clang++ -g -fsanitize=address a.cc -o a $ ./a   : ==465390==ERROR: AddressSanitizer: stack-buffer-overflow on address ... #0 0x5c1548396ccd in Status::operator<(Status const&) const a.cc:114:24     : #5 0x5c154838e32d in main a.cc:184:5 This frame has 11 object(s):      : [176, 224) 'm' (line 148) <== Memory access at offset 224 overflows this variable      : SUMMARY: AddressSanitizer: stack-buffer-overflow a.cc:114:24 in Status::operator<(Status const&) const
tmp

2024/05/20 03:19

q.front()でとってきたStatusクラスのsは q.popで解放されてしまわないのですか?
iwannatto

2024/05/20 10:18

@jimbe 別環境で試すのをサボっていたのですがpaiza.ioという手があったのですね…… ありがとうございます。 @melian sanitizerは存在は知っていたのですが結構簡単に利用できるんですね…… 関係ないと思っていたmが悪さをしているというのも貴重な結果です。ありがとうございます。 @tmp コメントありがとうございます。 参照で取っているわけではないのでsはコピーコンストラクタで初期化されると考えています。
guest

回答1

0

ベストアンサー

あくまで状況証拠からの推測にすぎませんが、直接の原因はStatus::operator<ではないでしょうか。

該当コードをg++ -fsanitize=addressでコンパイルして実行すると、Status::operator<にて stack-buffer-overflow が検出されます。

map - cpprefjp C++日本語リファレンスによると、std::mapの要素の比較演算子は、狭義の弱順序でなければならないそうです。Status::operator<がその条件を満たしていないために、std::mapにてメモリ範囲外アクセスが発生していると推察されます。(どういう理屈でそうなるのかについては、実装を追うのが大変なので追及していません)

std::mapにてメモリ範囲外アクセスが発生した結果、たまたまメモリ上隣り合っているstd::queueの管理領域が破壊されて、その破壊されたメモリをもとにstd::queue::pop()した結果、segmentation fault が発生したのではないでしょうか。

私もほとんど知識がないので、これ以上の調査が必要なら、自力でお願いします。

投稿2024/05/19 21:19

actorbug

総合スコア2479

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

iwannatto

2024/05/20 10:23

回答ありがとうございます。 mapが悪さをしているというのは頭から抜けていたのですが、確かにここの要件を満たしていないと何が起きても文句が言えないですね。 納得したのでベストアンサーとさせていただきます。ありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.30%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問