4

クラス宣言の最後に仮想関数を追加すると、バイナリの非互換性が回避される理由を誰かに説明してもらえますか?

私が持っている場合:

class A
{ 
  public:
    virtual ~A();
    virtual void someFuncA() = 0;
    virtual void someFuncB() = 0;
    virtual void other1() = 0;
  private:
    int someVal;
};

その後、このクラス宣言を次のように変更します。

class A
{ 
  public:
    virtual ~A();
    virtual void someFuncA() = 0;
    virtual void someFuncB() = 0;
    virtual void someFuncC() = 0;
    virtual void other1() = 0;
  private:
    int someVal;
};

前の宣言に対してコンパイルされた別の .so からコアダンプを取得します。しかし、クラス宣言の最後 (「int someVal」の後) に someFuncC() を配置すると、次のようになります。

class A
{ 
  public:
    virtual ~A();
    virtual void someFuncA() = 0;
    virtual void someFuncB() = 0;
    virtual void other1() = 0;
  private:
    int someVal;
  public:
    virtual void someFuncC() = 0;
};

コアダンプが表示されなくなりました。誰かがこれがなぜなのか教えてもらえますか? そして、このトリックは常に機能しますか?

PS。コンパイラは gcc ですが、これは他のコンパイラで動作しますか?

4

5 に答える 5

6

別の答えから:

これがメモリリークにつながるか、ハードディスクを消去するか、妊娠させるか、厄介な鼻の悪魔がアパートの周りを追いかけるか、明らかな問題なくすべてが正常に機能するかは未定義です. あるコンパイラーではこのようになり、別のコンパイラーでは変更される可能性があり、新しいコンパイラーのバージョンで変更されるか、新しいコンパイルごとに変更されるか、月の満ち欠け、気分、または最後の晴れた日にプロセッサを通過したニュートリノの数に応じて変更される午後。またはそうではないかもしれません。

これらすべてと、他の無限の可能性が 1 つの用語にまとめられます:未定義の動作:

近づかないでください。

特定のコンパイラ バージョンがその機能をどのように実装しているかを知っている場合は、これを機能させることができます。または、そうでないかもしれません。または、うまくいくと思うかもしれませんが、コンピューターの前に座っている人がヨーグルトを食べた場合にのみ壊れます.

とにかくこれを機能させたい理由はありますか?

仮想関数が宣言されている順序でコンパイラが仮想テーブルエントリを作成するため、機能する/機能しない/機能しないと思います。他の関数の間に仮想関数を配置して順序を台無しにすると、誰かが を呼び出すとother1()、代わりにsomeFuncC()が呼び出され、おそらく間違った引数で呼び出されますが、間違いなく間違ったタイミングで呼び出されます。(すぐにクラッシュすることを喜んでください。)
しかし、これは単なる推測であり、たとえそれが正しいものであっても、gcc バージョンにこれを説明するドキュメントが付属していない限り、明日このように動作するという保証はありません。同じコンパイラ バージョン

于 2010-05-18T18:19:01.187 に答える
2

この特定の再配置がまったく役立つことに少し驚いています。もちろん、動作が保証されているわけではありません。

上記のクラスは、通常、次の順序で何かに変換されます。

typedef void (*vfunc)(void);

struct __A__impl { 
     vfunc __vtable_ptr;
     int someVal;
};

__A__impl__init(__A__impl *object) { 
    static vfunc virtual_functions[] = { __A__dtor, __A__someFuncA, __A__someFuncB};
    object->__vtable__ptr = virtual_functions;    
}

を追加するsomeFuncC場合は、通常、クラスの仮想関数テーブルに別のエントリを追加する必要があります。コンパイラが他の関数の前にそれを配置すると、ある関数を呼び出そうとすると実際には別の関数が呼び出されるという問題が発生します。そのアドレスが仮想関数テーブルの最後にある限り、問題は解決しないはずです。ただし、C++は、vtableがどのように配置されるかについては何も保証しません (または、vtable が存在することさえ保証しません)。

public:通常のデータに関しては、(非静的) メンバーは、介在するアクセス指定子 ( 、protected:または)がない限り、昇順に配置する必要がありますprivate:

コンパイラが仮想関数宣言を vtable 位置にマッピングするときに同じ規則に従った場合、最初の試行は機能するはずですが、2 回目の試行は失敗する可能性があります。明らかに、それが保証されているわけではありません。一貫して動作する限り、コンパイラは任意の方法で vtable を配置できます。

于 2010-05-18T18:21:39.867 に答える
1

おそらく、G++ は「プライベート」仮想テーブルをパブリック テーブルとは別の場所に配置します。

いずれにせよ、何らかの方法でオブジェクトが実際とは異なるように見えるようにコンパイラーをだまして、そのオブジェクトを使用することができれば、鼻の悪魔を呼び出すことになります。彼らのすることに頼ることはできません。これは基本的にあなたがしていることです。

于 2010-05-18T18:10:24.003 に答える
0

クラスとのバイナリ互換性またはシリアル化に関連するものは避けることをお勧めします。これにより、ワームの缶が開きすぎて、それらをすべて食べるのに十分な魚がありません.

データを転送するときは、バイナリ プロトコルよりもフィールド定義を含む XML または ASCII ベースのプロトコルを優先してください。柔軟なプロトコルは、人々 (あなたを含む) がプロトコルをデバッグおよび維持するのに役立ちます。ASCII および XML プロトコルは、バイナリよりも可読性が高くなります。

クラス、オブジェクト、構造体などは、バイナリ イメージによって全体として比較することはできません。推奨される方法は、クラスごとに独自の比較演算子または関数を実装することです。結局のところ、クラスはそのデータ メンバーを比較する方法の専門家です。ポインターやコンテナーなどの一部のデータ メンバーは、バイナリ比較を実際に台無しにする可能性があります。

プロセッサは高速で、メモリは安価であるため、原則を省スペースから正確性と堅牢性に変えてください。プログラムにバグがなくなったら、速度またはスペースを最適化します。

スタック オーバーフローやニュースグループには、バイナリ プロトコルやオブジェクトのバイナリ比較 (および割り当て) に関連するホラー ストーリーがあまりにも多く投稿されています。既存のホラー ストーリーから学ぶことで、新しいホラー ストーリーの量を減らしましょう。

于 2010-05-18T18:22:15.687 に答える
-2

バイナリ互換性は悪いことです。XML などのプレーンテキスト ベースのシステムを使用する必要があります。

編集:質問の意味を少し間違えました。Windows には、GetProcAddress、__declspec(dllexport)、__declspec(dllimport) など、データを共有するための多くのネイティブな方法が用意されています。GCC も同様のものを提供する必要があります。バイナリシリアル化は悪いことです。

再編集: 実際、彼は投稿で実行可能ファイルについて言及していませんでした。まったく。彼がバイナリ互換性を利用しようとしていたものでもありません。

于 2010-05-18T18:26:11.013 に答える