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

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

ただいまの
回答率

90.87%

  • C++

    3117questions

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

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

解決済

回答 4

投稿

  • 評価
  • クリップ 1
  • VIEW 511

LouiS0616

Python総合1位

前置き

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

#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:\...>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では裏でいろいろ動いているようですね。


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

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

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

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 4

checkベストアンサー

+5

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

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

void* operator new(std::size_t size) {
    std::ios_base::Init init;  // ★

    void* ptr = std::malloc(size);
    if(ptr == nullptr) {
        throw std::bad_alloc();
    }

    std::cout << "reached\n";
    return ptr;
}

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/05/01 18:43

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

    キャンセル

  • 2018/05/04 17:10

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

    キャンセル

  • 2018/05/04 17:34

    実験目的です。
    もともとは『江添亮の詳説C++17』のコードを試したところ、遭遇したエラーでした。
    https://github.com/EzoeRyou/cpp17book/blob/master/013-cpp14-core-sized-deallocation.md

    > よほどの特殊事情がない限りglobal operator new/deleteをオーバーライドするのは避けるべき
    そうですね。あっさり足を打ち抜いてしまいました。

    キャンセル

+5

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

#include <cstdio>
#include <cstdlib>
#include <execinfo.h>
#include <new>

void* operator new(std::size_t size) {
    void* ptr = std::malloc(size);
    if(ptr == nullptr) {
        throw std::bad_alloc();
    }

    size_t i;
    void *trace[128];
    char **ss_trace;
    size_t _size = backtrace(trace, sizeof(trace) / sizeof(trace[0]));
    ss_trace = backtrace_symbols(trace, _size);
    if (ss_trace == NULL) {
        /*Failure*/
        throw std::bad_alloc();
    }
    /*例えば表示*/
    for (i = 0; i < _size; i++) {
        printf("%s\n", ss_trace[i]);
    }
    free(ss_trace);

    std::puts("reached");
    return ptr;
}

int main(void) {
    auto p = new int;
    delete(p);

    return 0;
}

結果

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

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/05/01 17:46

    ご回答ありがとうございます。
    確かにboostの利用いかんによって、出力が異なりますね。

    gcc + boost + new内のcout の組み合わせでセグフォが発生しているわけですが、これはどのような理由が考えられるでしょうか?

    キャンセル

  • 2018/05/01 18:12

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

    キャンセル

  • 2018/05/01 18:22

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

    キャンセル

+2

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

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

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/05/01 15:36 編集

    ご回答ありがとうございます。
    試してみたところ、deleteをオーバーロードした場合も同様にセグフォが発生してしまいました。
    https://wandbox.org/permlink/scYe0kcXNFgWYaXp

    キャンセル

+2

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

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

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

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

#include <stdio.h>
#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";
    printf("REACHED\n");
    return ptr;
}

int main(void) {
    auto p = new int;
    delete(p);

    return 0;
}

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/05/01 15:26 編集

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

    キャンセル

  • 2018/05/01 15:29

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

    キャンセル

  • 2018/05/01 15:39 編集

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

    無限ループは発生していないように思います。

    キャンセル

  • 2018/05/01 15:57 編集

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

    キャンセル

  • 2018/05/01 16:24 編集

    お、iostreamのインクルードを避けたところ、セグフォが発生しませんでした。
    https://wandbox.org/permlink/CUfk4k0mLKp2Gf19

    KSwordOfHasteさんの説が濃厚な気がします。

    ---
    追記:すみません、勘違いだったようです。原因は相変らず謎です...

    キャンセル

  • 2018/05/01 16:54

    boostじゃないかな

    キャンセル

  • 2018/05/01 17: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?」というコメントしかできません><

    キャンセル

  • 2018/05/01 18:02

    > こうしたものを一通りオーバーロードしておかないと矛盾が起きるという話はないんでしょうか?

    それも考えられますね。
    しかしそうなると、『なぜcoutがないときは平気なのか?』という謎が依然残ります...

    キャンセル

  • 2018/05/01 18:08

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

    キャンセル

  • 2018/05/01 18:17 編集

    今のところ、次の状況でセグフォが発生しています。
    ・ gccで
    ・ boost1.66以降を利用して
    ・ newをオーバーロードし、内部でcoutに対する出力を行った とき

    回避する方法はあるにしても、この条件が不思議に思えて仕方ないのです。

    キャンセル

  • 2018/05/01 18:57

    なるほどそういうことですか。それはboostの実装にこの点に影響するようなもの(バグかどうかはさておき)が入っているからということでは満足できないのですよね?

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

    キャンセル

  • 2018/05/01 19:08

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

    キャンセル

  • 2018/05/01 19:19

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

    キャンセル

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

  • ただいまの回答率 90.87%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

  • 解決済

    Javaでは実装できるメソッドを持つenumについて

    Javaでは以下のように方向を管理するクラスDirectionが作れ、直感的に使えます。 public enum Direction { FRONT(0), RIGH

  • 解決済

    [c]一つのデータでカラム数が変わるデータの読み込み

    質問失礼します。プログラム初心者です。 以下のような〜〜〜.datを fp1 = fopen(fname1,"r"); while((ret = fscanf(fp1,"%d %d

  • 解決済

    2次元配列の値を関数の引数として渡したい

    タイトルの通り2次元配列で作ったものを関数の引数として渡したいです。また、2次元配列の大きさは固定ではありません。 私が書いたコードは、以下のようになります。 #inclu

  • 解決済

    C++でchar型の数値を数字列に変換したい

    ログ出力のマクロを作っています。 前提条件として、 1) パラメータに、任意の数値型と任意のポインタ型の値が渡される 2) 数値型の数値を10進の数字列でバッファに出力 3) 

  • 解決済

    std::vectorの添字がうまく機能しません

    前提・実現したいこと 現在C++とSiv3Dで落ち物パズルゲームを制作しています。 発生している問題・エラーメッセージ std::vectorを使用して'Block'クラスを格

  • 受付中

    C++ 合計値 範囲指定

    C++で合計値を出力するプログラムを作成しています。プログラムを実行するとすべての和が出力されるのですが、 これを指定した範囲、例えば2、3、4、5の合計値を出力したい場合どうした

  • 解決済

    OPENCVにおけるエラー

    openCVをはじめようとして、以下のエラーがでました。 プログラミング初心者です。 visual studio community 2017を使っています。 opencv3.3.

  • 解決済

    マルチバイト文字のchar*をvector<char*>に変換したい

     前提・実現したいこと マルチバイト文字が格納されているchar*型の文字列をvector<char*>に変換したいです。 char* String = "abcあいうえお"

同じタグがついた質問を見る

  • C++

    3117questions

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