3

コンパイラを使用して AIX でCppcheckを構築する作業を行っています (前の質問を参照)。チェッカー クラスはすべてクラスから派生し、そのコンストラクターは各オブジェクトをグローバル リストに登録します。xlCCheck

check.h

class Check {
public:
    Check() {
        instances().push_back(this);
        instances().sort();
    }
    static std::list<Check *> &instances();
    virtual std::string name() const = 0;
private:
    bool operator<(const Check *other) const {
        return (name() < other->name());
    }
};

checkbufferoverrun.h

class CheckBufferOverrun: public Check {
public:
    // ...
    std::string name() const {
        return "Bounds checking";
    }
};

私が抱えていると思われる問題は、instances().sort()通話にあります。sort()は静的リスト内の各ポインターでCheck::operator<()which 呼び出しを呼び出しますが、リストに追加されたばかりのインスタンスはまだコンストラクターを完全に実行していません (まだ 内にあるため)。したがって、コンストラクターが完了する前にそのようなポインターを呼び出すと、未定義の動作になるはずです。Check::name()instances()CheckCheck::Check()->name()CheckBufferOverrun

これは本当に未定義の動作ですか、それともここで微妙な点が欠けていますか?

への呼び出しが厳密に必要だとは思わないことに注意してくださいsort()。ただし、その効果は、Cppcheck がすべてのチェッカーを決定論的な順序で実行することです。これは、エラーが検出された順序での出力にのみ影響します。これにより、特定の順序での出力が期待されているため、一部のテスト ケースが失敗する原因となります。

更新:上記の質問はまだ(ほとんど)残っています。ただし、コンストラクターでの呼び出しが問題を引き起こさなかった本当の理由sort()(つまり、純粋な仮想関数の呼び出しによるクラッシュ) は、Check::operator<(const Check *)実際にはsort()!によって呼び出されないためだと思います。むしろ、代わりにポインターsort()を比較するように見えます。これは と の両方で発生し、Cppcheck コード自体に問題があることを示しています。g++xlC

4

4 に答える 4

4

はい、未定義です。標準では、10.4/6 で具体的にそう述べられています。

メンバ関数は、抽象クラスのコンストラクタ (またはデストラクタ) から呼び出すことができます。そのようなコンストラクタ (またはデストラクタ) から作成 (または破棄) されるオブジェクトに対して、直接的または間接的に純粋仮想関数への仮想呼び出し (10.3) を行うことの効果は定義されていません。

于 2011-02-01T21:50:18.300 に答える
2

コンストラクターからの純粋仮想関数の呼び出しは常に未定義の動作であることは事実です。

仮想ポインターは、コンストラクターが完全に実行される ("}" を閉じる) まで設定されていると見なすことができないため、仮想関数 (または純粋仮想関数) への呼び出しは、コンパイル自体の時点でセットアップする必要があります (静的にバインドされます)。電話)。

ここで、仮想関数が純粋仮想関数である場合、コンパイラは通常、そのような純粋仮想関数に対して独自の実装を挿入します。そのデフォルトの動作は、セグメンテーション フォールトを生成することです。標準では、純粋仮想関数の実装を規定していませんが、ほとんどの C++ コンパイラは前述のスタイルを採用しています。

あなたのコードが実行時のいたずら行為を引き起こしていない場合、そのコードは前述の呼び出しシーケンスで呼び出されていません。以下の2つの機能の実装コードを投稿できれば

instances().push_back(this);
instances().sort();

その後、何が起こっているのかを確認するのに役立つかもしれません。

于 2011-02-01T21:49:51.690 に答える
0

あなたの本当の問題は、Checker の基本クラスと、Check の (派生) インスタンスを登録するためのメカニズムの 2 つを混同していることだと思います。

とりわけ、これは特に堅牢ではありません。あなたの Checker クラスを使用したいかもしれませんが、別の方法で登録したいかもしれません。

たぶん、次のようなことができます: Checker は保護された ctor を取得します (これはとにかく抽象的であるため、派生クラスのみが Checker ctor を呼び出す必要があります)。

派生クラスには、保護された ctor と、インスタンスを作成するための public static メソッド (「名前付きコンストラクター パターン」) もあります。この作成メソッドは Checker サブクラスを作成し、(この時点で完全に作成された) それを CheckerRegister クラス (これも抽象的であるため、必要に応じてユーザーが独自に実装できます) に渡します。

任意のシングルトン パターンまたは依存性注入メカニズムを使用して、Checkerregister をインスタンス化し、Checker サブクラスで使用できるようにします。

これを行う簡単な方法の 1 つは、Checker で getCheckerRegister 静的メソッドを使用することです。

したがって、Checker サブクラスは次のようになります。

class CheckBufferOverrun: public Check { protected: CheckBufferOverrun : Check("Bounds Checking") { // すべての派生には名前があるので、それを単に引数として渡さないのはなぜですか? } public: CheckBufferOverrun makeCheckBufferOverrun() { CheckBufferOverrun that = new CheckBufferOverrun();

   // get the singleton, pass it something fully constructed
   Checker.getCheckerRegister.register(that) ;
   return that;
}

これが大量のボイラープレート コードになるように思われる場合は、テンプレートを記述します。C++ の各テンプレート インスタンスは実際の一意のクラスであるため、それが心配な場合は、Checker から派生したものを登録する、テンプレート化されていない基本クラスを記述します。

于 2011-02-01T22:10:02.557 に答える
0

オブジェクトの構築が完了していない限り、純粋仮想関数は呼び出されない可能性があります。ただし、基底クラス A で純粋仮想として宣言され、B (A から派生) で定義されている場合、B の構築が完了しているため、C (B から派生) のコンストラクターはそれを呼び出すことができます。

あなたの場合、代わりに静的コンストラクターを使用してください。

class check {
private Check () { ... }
public:
    static Check* createInstance() {
        Check* check = new Check();
        instances().push_back(check);
        instances().sort();
    }
...
}
于 2011-02-01T21:56:27.223 に答える