LoginSignup
3
3

More than 5 years have passed since last update.

C/C++インタプリタ CINTのデストラクタ不正呼び出し問題と格闘した話 その1

Last updated at Posted at 2018-10-19

C/C++インタプリタ CINT

CERN(欧州原子核共同研究機関)で開発された、素粒子実験のデータ解析用ソフトウェアROOTのスクリプト言語として採用されていたC/C++インタプリタです。CINTの開発者は後藤 正治さんという方で、ROOT 5まではスクリプト言語としてCINTが採用されていました。ROOT 6からはClang/LLVMベースのClingというC/C++インタプリタが採用されています。

実験

ROOT 5で以下のプログラムを実行してみます。内容は手抜き文字列クラスとその動作確認用関数です。

HelloWorld.cpp
#include <cstdio>
#include <cstring>

using namespace std;

class my_string
{
public:
    my_string();
    my_string(const char*);
    my_string(const my_string&);
    explicit my_string(int);
    ~my_string();
    my_string& operator =(const char*);
    my_string& operator =(const my_string&);
    my_string& operator +=(const char*);
    my_string& operator +=(const my_string&);
    const char* c_str() const;

private:
    void assign(const char*);
    void append(const char*);

    char* m_buf;
};

my_string::my_string()
    : m_buf(NULL)
{
    assign("");
}

my_string::my_string(const char* s)
    : m_buf(NULL)
{
    assign(s);
}

my_string::my_string(const my_string& s)
    : m_buf(NULL)
{
    assign(s.m_buf);
}

my_string::my_string(int d)
    : m_buf(NULL)
{
    char buf[32];

    sprintf(buf, "%d", d);
    assign(buf);
}

my_string::~my_string()
{
    delete[] m_buf;
}

my_string& my_string::operator =(const char* s)
{
    assign(s);

    return *this;
}

my_string& my_string::operator =(const my_string& s)
{
    assign(s.m_buf);

    return *this;
}

my_string& my_string::operator +=(const char* s)
{
    append(s);

    return *this;
}

my_string& my_string::operator +=(const my_string& s)
{
    append(s.m_buf);

    return *this;
}

const char* my_string::c_str() const
{
    return m_buf;
}

void my_string::assign(const char* s)
{
    char* const buf = m_buf;

    m_buf = new char[strlen(s) + 1];
    strcpy(m_buf, s);
    delete[] buf;
}

void my_string::append(const char* s)
{
    char* const buf = m_buf;

    m_buf = new char[strlen(buf) + strlen(s) + 1];
    strcpy(m_buf, buf);
    strcat(m_buf, s);
    delete[] buf;
}

my_string operator +(const char* s1, const my_string& s2)
{
    my_string result = s1;

    result += s2;

    return result;
}

my_string operator +(const my_string& s1, const char* s2)
{
    my_string result = s1;

    result += s2;

    return result;
}

my_string operator +(const my_string& s1, const my_string& s2)
{
    my_string result = s1;

    result += s2;

    return result;
}

void HelloWorld()
{
    const my_string message = "Hello World ";
    const int year = 2018;

    const my_string s1 = "Hello World " + my_string(year);
    printf("%s\n", s1.c_str());

    const my_string s2 = message + "2018";
    printf("%s\n", s2.c_str());

    const my_string s3 = message + year;
    printf("%s\n", s3.c_str());
}

ソースファイルをロードしてHelloWorld関数を実行すると、"Hello World 2018"という文字列が3回表示されるはずが2回しか表示されず、最後にエラーメッセージが表示されています。
Image1.png
int型の引数を持つ変換コンストラクタにexplicitが指定されているため、149行目で暗黙の型変換が行われず、operator +関数の呼び出しに失敗しているようです。

const my_string s3 = message + year;

この行は以下のように修正する必要があります。

const my_string s3 = message + my_string(year);

149行目の修正後に、ソースファイルを再ロードしてHelloWorld関数を実行してみます。
Image2.png
すると、ROOTのプロセスが異常終了してしまいました。いったい何が起きたのでしょうか?
Image3.png

調査

原因を突き止めるため、最初の間違ったプログラムの149行目にブレークポイントを設定して、そこからステップ実行してみます。すると、エラー発生後に変数s3のデストラクタが呼び出されました。
Image4.png
operator +関数の呼び出しに失敗したため、変数s3のコンストラクタは呼び出されていません。多くの場合、メンバー変数m_bufには不正な値が入っているため、delete演算子によってヒープ領域の管理情報が破壊されてしまい、ROOTのプロセスがいつ異常終了してもおかしくない状態になってしまったのです。なぜ、未初期化の変数に対してデストラクタが呼び出されたのでしょうか?

CINTのソースコードを調査した結果、以下の2つの原因が判明しました。

  • CINTの変数生成処理では、インタプリタで処理される型の変数は、メモリの確保と変数管理テーブルへの登録が式の評価前に行われる。
  • CINTの変数管理テーブルには、その変数が初期化済みかどうかを表す情報が(基本的には)存在しない。

これらの原因によってデストラクタが不正に呼び出されるのはインタプリタで処理される型だけで、コンパイル済みクラスライブラリに含まれる型では問題ありません。

次回の記事(その2)では、この問題の解決のために行ったCINTのソースコードへの修正について解説したいと思います。

その2

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3