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

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

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

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

Q&A

解決済

4回答

684閲覧

newをオーバーロードしたときに、環境によってセグフォが発生する

LouiS0616

総合スコア35658

C++

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

1グッド

1クリップ

投稿2018/05/01 06:11

前置き

次のコードをWandboxのgcc6.3.0でビルド・実行したところ、セグフォが発生しました

C++

1#include <cstdlib> 2#include <iostream> 3 4void* operator new(std::size_t size) { 5 void* ptr = std::malloc(size); 6 if(ptr == nullptr) { 7 throw std::bad_alloc(); 8 } 9 10 std::cout << "reached\n"; 11 return ptr; 12} 13 14int main(void) { 15 auto p = new int; 16 delete(p); 17 18 return 0; 19}

しかし、同じコードを手元の環境でコンパイル・実行すると、エラーなく実行されます。

C:...>ver Microsoft Windows [Version 10.0.16299.371] C:...>g++ --version g++ (MinGW.org GCC-6.3.0-1) 6.3.0 Copyright (C) 2016 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. C:...>type prog.cc #include <cstdlib> #include <iostream> void* operator new(std::size_t size) { void* ptr = std::malloc(size); if(ptr == nullptr) { throw std::bad_alloc(); } std::cout << "reached\n"; return ptr; } int main(void) { auto p = new int; delete(p); return 0; } C:...>g++ prog.cc -Wall -Wextra -std=gnu++17 -o prog C:...>prog.exe reached

なお、

  • std::cout << "reached\n";をコメントアウトすると、セグフォは発生しません。
  • Wandbox + clang の場合は、同じコードでもセグフォは発生しません。

本題

  • 上手く動作しないのは、コードに問題があるからなのでしょうか?
  • もしそうであれば、どうしてなのでしょうか?

試したこと

試しにcoutをprintfに置き換えてみたところ、セグフォは発生しませんでした。
しかし、Wandboxと手元の環境では動作が異なりました。

Wandboxでの実行結果

reached reached reached reached reached reached reached reached reached reached reached reached reached reached reached reached reached reached reached reached reached reached reached reached reached reached reached reached reached reached reached reached reached reached reached reached reached reached reached reached reached reached reached reached reached reached reached reached

手元での実行結果 (環境は前述したとおり)

reached

Wandboxでは裏でいろいろ動いているようですね。


**『オープンソースなんだから、自分で読めよ』**とか、
**『直接イシュー投げろよ』**とか言われてしまったらそれまでですが...
コードに問題があるのか、環境に問題があるのかわからないため、質問させていただきました。

なにか質問に不足があればご指摘のほどよろしくお願いします。

yohhoy👍を押しています

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

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

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

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

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

guest

回答4

0

ベストアンサー

異なるライブラリ(翻訳単位)に属する「std::coutなどの標準入出力ストリームオブジェクトの初期化」と「Boostライブラリ中のグローバルオブジェクト初期化」順序は不定となります。Boost側からoperator newが呼び出されるタイミングではstd::coutが未初期化となっているケースがあり、このため一部環境でSEGVが発生すると考えられます。

下記コード★部のように、std::ios_base::Initクラスで明示的に標準入出力ストリームを初期化すると、Boostライブラリをリンクしても安全に動作するようです。

C++

1void* operator new(std::size_t size) { 2 std::ios_base::Init init; // ★ 3 4 void* ptr = std::malloc(size); 5 if(ptr == nullptr) { 6 throw std::bad_alloc(); 7 } 8 9 std::cout << "reached\n"; 10 return ptr; 11}

投稿2018/05/01 09:29

編集2018/05/01 09:33
yohhoy

総合スコア6189

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

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

LouiS0616

2018/05/01 09:43

ご回答ありがとうございます。 なるほど、初期化の順序に依存してしまっていたのですね。疑問が解消しました。
yohhoy

2018/05/04 08:10

実験目的であれば良いのですが、よほどの特殊事情がない限りglobal operator new/deleteをオーバーライドするのは避けるべきですね。大抵はclass単位のoperator new/deleteオーバーライドで済むはずです。
guest

0

コールスタックの表示
適当に合体させてみました。

c++

1#include <cstdio> 2#include <cstdlib> 3#include <execinfo.h> 4#include <new> 5 6void* operator new(std::size_t size) { 7 void* ptr = std::malloc(size); 8 if(ptr == nullptr) { 9 throw std::bad_alloc(); 10 } 11 12 size_t i; 13 void *trace[128]; 14 char **ss_trace; 15 size_t _size = backtrace(trace, sizeof(trace) / sizeof(trace[0])); 16 ss_trace = backtrace_symbols(trace, _size); 17 if (ss_trace == NULL) { 18 /*Failure*/ 19 throw std::bad_alloc(); 20 } 21 /*例えば表示*/ 22 for (i = 0; i < _size; i++) { 23 printf("%s\n", ss_trace[i]); 24 } 25 free(ss_trace); 26 27 std::puts("reached"); 28 return ptr; 29} 30 31int main(void) { 32 auto p = new int; 33 delete(p); 34 35 return 0; 36}

結果

という訳で、どうやらboost内で呼ばれているようですね

投稿2018/05/01 08:02

編集2018/05/01 08:05
asm

総合スコア15147

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

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

LouiS0616

2018/05/01 08:46

ご回答ありがとうございます。 確かにboostの利用いかんによって、出力が異なりますね。 gcc + boost + new内のcout の組み合わせでセグフォが発生しているわけですが、これはどのような理由が考えられるでしょうか?
asm

2018/05/01 09:12

なんでしょうね、コレ boost1.66以降を有効にした状態でnew内でcout/cerrを使うと起きますね
LouiS0616

2018/05/01 09:22

ほんとですね、boost1.65.1までなら動くのですね。
guest

0

グローバルなnew演算子をオーバーロードするならその実装は慎重であるべきと思います。ご質問のコードでは

std::cout << "reached\n";

の行がありますが、これが動くときにグローバールなnewが呼び出されてしまうような実装になってないとは保証できないと思います。

上の行をC++のnewが呼び出され得ないようにすると一応動きました。

C++

1#include <stdio.h> 2#include <cstdlib> 3#include <iostream> 4 5void* operator new(std::size_t size) { 6 void* ptr = std::malloc(size); 7 if(ptr == nullptr) { 8 throw std::bad_alloc(); 9 } 10 11 // std::cout << "reached\n"; 12 printf("REACHED\n"); 13 return ptr; 14} 15 16int main(void) { 17 auto p = new int; 18 delete(p); 19 20 return 0; 21}

投稿2018/05/01 06:25

編集2018/05/01 06:32
KSwordOfHaste

総合スコア18392

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

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

KSwordOfHaste

2018/05/01 06:27 編集

あ・・・maisumakunさんのご指摘もごもっともと思います。そちらも重要と思います。上のコードが動いたのは「たまたま」ですね orz
maisumakun

2018/05/01 06:29

確かに、どこからnewが呼ばれるかわからない、というのもありますね(実行例の中に、無限ループしているものもあるようですし)。
LouiS0616

2018/05/01 11:22 編集

ご回答ありがとうございます。 例えばmain関数を空にしても、newは何度か呼び出されているようです。 https://wandbox.org/permlink/L5shJ78szCGfpXqy 無限ループは発生していないように思います。
KSwordOfHaste

2018/05/01 06:58 編集

cin/coutのようにグローバルなインスタンスのコンストラクターの中でヒープにnewでバッファを確保したり・・・といったことはありそうですね。一般的にリンクされてるライブラリーによってもそういうものは増減するのではないでしょうか。
LouiS0616

2018/05/01 07:39 編集

お、iostreamのインクルードを避けたところ、セグフォが発生しませんでした。 https://wandbox.org/permlink/CUfk4k0mLKp2Gf19 KSwordOfHasteさんの説が濃厚な気がします。 --- 追記:すみません、勘違いだったようです。原因は相変らず謎です...
asm

2018/05/01 07:54

boostじゃないかな
KSwordOfHaste

2018/05/01 08:05

asmさんがおっしゃるようboostを使わないようにするとreachedは1回しか表示されなくなりますね~ ちなみにstd=gnu++17とのことですがnew/deleteには(自分が知らない)オーバーロードが増えてるようなので http://en.cppreference.com/w/cpp/memory/new/operator_new こうしたものを一通りオーバーロードしておかないと矛盾が起きるという話はないんでしょうか? void* operator new ( std::size_t count, std::align_val_t al); 自分だと「何このnew?」というコメントしかできません><
LouiS0616

2018/05/01 09:02

> こうしたものを一通りオーバーロードしておかないと矛盾が起きるという話はないんでしょうか? それも考えられますね。 しかしそうなると、『なぜcoutがないときは平気なのか?』という謎が依然残ります...
KSwordOfHaste

2018/05/01 09:08

ところで状況がよくわからないのですが、今はsegmentatin faultは起きなくなったのですよね?reachedが複数回表示されるのが問題なのでしょうか? 何が問題になっているかわからなくなってきました ^^;;
LouiS0616

2018/05/01 09:18 編集

今のところ、次の状況でセグフォが発生しています。 ・ gccで ・ boost1.66以降を利用して ・ newをオーバーロードし、内部でcoutに対する出力を行った とき 回避する方法はあるにしても、この条件が不思議に思えて仕方ないのです。
KSwordOfHaste

2018/05/01 09:57

なるほどそういうことですか。それはboostの実装にこの点に影響するようなもの(バグかどうかはさておき)が入っているからということでは満足できないのですよね? 自分はnewの中でC++のランタイムの動作に影響を及ぼすような処理(副作用)をすること自体が問題だと思うので「もしそういうことをしたとき、なぜ特定のバージョンで問題がおきるか」を突き止めるならboostの機能から推測したり実装を調べたりデバッグすることがすべきことのように思えます。
KSwordOfHaste

2018/05/01 10:08

yohhoyさんの回答が納得ですね。 なお自分はC++のランタイムに影響(副作用ってコメントしましたが)を及ぼすような動作をnew内ですべきでないと思ったのですが、それはいいすぎで、単にnew内で何かをするなら「その処理やっても問題ないように配慮すべし」というだけのことだったのかなと思いました。
LouiS0616

2018/05/01 10:19

演算子オーバーロード恐ろしや、と改めて思いました。 今回はたまたま試したコードで遭遇したエラーでしたが、必要に駆られてこのような処理を書く際は慎重を期さないといけないですね。質問してよかったです。
guest

0

気になる点として、「newだけオーバーロードしてしまっているために、結果としてmallocで取得したメモリをdeleteで開放しようとしてしまっている」ということがあります。

newを(メモリ確保の方法から変える形で)オーバーロードした以上は、deleteのオーバーロードも事実上必須かと思います。

投稿2018/05/01 06:16

maisumakun

総合スコア145121

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

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

guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.50%

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

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

質問する

関連した質問