9

クラスがあるとしましょう

class A
{
    int x;
public:
    void sayHi()
    {
        cout<<"Hi";
    }
};

int main()
{
    A *a=NULL;
    a->sayHi();
}

上記のコードは、Turbo C (私がテストした場所) でコンパイルされHi、出力として出力されます。

aであるため、クラッシュを予期していましたNULL。さらに、sayHi()関数を仮想化すると、次のように表示されます

Abnormal temination(Segmentation fault in gcc) 

その多くが実装に依存していることは知っていますが、誰かが実装に光を当てたり、概要を説明したりできれば、それは本当に素晴らしいことです.

4

4 に答える 4

7

明らかに、コードには未定義の動作があります。つまり、得られるものはすべて偶然です。そうは言っても、システムは非仮想メンバー関数を呼び出すときにオブジェクトについて知る必要はありません。署名に基づいて呼び出すことができます。さらに、メンバー関数がメンバーにアクセスする必要がない場合、実際にはオブジェクトを必要とせず、実行するだけで済みます。これは、コードが何らかの出力を出力したときに観察したものです。しかし、これがシステムの実装方法であるかどうかは定義されていません。

仮想関数型システムを呼び出すと、オブジェクトに関連付けられた型情報レコードの参照が開始されます。ポインターで仮想関数を呼び出す場合NULL、そのような情報は存在せず、それにアクセスしようとすると、おそらく何らかのクラッシュが発生します。それでも、そうする必要はありませんが、ほとんどのシステムではそうです。

ところで、main() 常に戻りますint

于 2012-09-30T11:26:22.510 に答える
6

C++ では、クラスのメソッドはそのクラスのインスタンス内に格納されません。thisそれらは、プログラマーによって指定された引数に加えて、ポインターを透過的に受け入れるいくつかの「特別な」関数です。

あなたの場合、sayHi()メソッドはどのクラス フィールドも参照していないため、thisポインター (これは ですNULL) は追跡されません。

ただし、これはまだ定義されていない動作です。これを呼び出すと、プログラムが連絡先リストに厄介な電子メールを送信することを選択する場合があります。この特定の例では、最悪のことを行いますが、機能しているように見えます。

質問に回答してからメソッドのvirtualケースが追加されましたが、他の人の回答に含まれているため、回答を絞り込むことはしません。

于 2012-09-30T11:25:54.450 に答える
4

一般化すると、スーパー クラスと仮想関数を持たないクラスからインスタンス化されたオブジェクトのレイアウトは次のようになります。

* - v_ptr  ---> *  pTypeInfo
|               |- pVirtualFuncA
|               |- pVirtualFuncB
|- MemberVariableA
|- MemberVariableB

v_ptr仮想関数のアドレスとオブジェクトの RTTI データを含む v-table へのポインタです。仮想関数を持たないクラスには v テーブルがありません。

上記の例でclass Aは、仮想メソッドがないため、v-table がありません。これは、sayHi()to call の実装がコンパイル時に決定でき、不変であることを意味します。

コンパイラは、暗黙的なthisポインターを に設定aし、その先頭にジャンプするコードを生成しますsayHi()。実装はオブジェクトの内容を必要としないため、ポインターが のときに機能するという事実NULLは、偶然の一致です。

仮想にする場合sayHi()、コンパイラはコンパイラ時に呼び出す実装を決定できないため、代わりに v テーブル内の関数のアドレスを検索して呼び出すコードを生成します。あなたの例 where aisNULLでは、コンパイラは address の内容を読み取り、0中止を引き起こします。

于 2012-09-30T11:42:16.383 に答える
1

クラスの非仮想メソッドを呼び出す場合、コンパイラにとっては、関数が属するクラスを知るだけで十分であり、メソッドを呼び出すクラスへのポインターを逆参照することにより (NULL ではありますが)、コンパイラーはその情報を取得します。sayHi ()メソッドは、クラス インスタンスへのポインタを隠しパラメータとして受け取る関数にすぎません。このポインターは NULL ですが、メソッドで属性を参照しなければ問題ありません。

このメソッドを仮想化した瞬間、状況が変わります。コンパイラは、コンパイル時にどのコードがメソッドに関連付けられているかを認識していないため、実行時にそれを把握する必要があります。それが行うことは、基本的にすべての仮想メソッドの関数ポインターを含むテーブルを調べることです。このテーブルはクラス インスタンスに関連付けられているため、NULL ポインターに関連するメモリの一部を参照するため、この場合はクラッシュします。

于 2012-09-30T11:26:48.900 に答える