50

私は C++ を試していましたが、以下のコードが非常に奇妙であることがわかりました。

class Foo{
public:
    virtual void say_virtual_hi(){
        std::cout << "Virtual Hi";
    }

    void say_hi()
    {
        std::cout << "Hi";
    }
};

int main(int argc, char** argv)
{
    Foo* foo = 0;
    foo->say_hi(); // works well
    foo->say_virtual_hi(); // will crash the app
    return 0;
}

vtable ルックアップが必要であり、有効なオブジェクトでしか機能しないため、仮想メソッド呼び出しがクラッシュすることはわかっています。

次の質問があります

  1. say_hi非仮想メソッドは NULL ポインターでどのように機能しますか?
  2. オブジェクトはどこfooに割り当てられますか?

何かご意見は?

4

8 に答える 8

84

objectfooは type のローカル変数ですFoo*mainその変数は、他のローカル変数と同様に、関数のスタックに割り当てられる可能性があります。しかし、格納されているfooは null ポインターです。それはどこを指していません。Fooどこにも表現された型のインスタンスはありません。

仮想関数を呼び出すには、呼び出し元は関数が呼び出されているオブジェクトを知る必要があります。これは、オブジェクト自体が、実際にどの関数を呼び出す必要があるかを示しているためです。(これは、オブジェクトに vtable へのポインター、関数ポインターのリストを与えることによって実装されることが多く、呼び出し元は、そのポインターがどこを指すかを事前に知らなくても、リストの最初の関数を呼び出すことになっていることを知っているだけです。)

しかし、非仮想関数を呼び出すために、呼び出し元はそのすべてを知る必要はありません。コンパイラは、どの関数が呼び出されるかを正確に認識しているCALLため、目的の関数に直接移動するためのマシンコード命令を生成できます。関数が呼び出されたオブジェクトへのポインターを隠しパラメーターとして関数に渡すだけです。つまり、コンパイラは関数呼び出しを次のように変換します。

void Foo_say_hi(Foo* this);

Foo_say_hi(foo);

現在、その関数の実装は、そのthis引数が指すオブジェクトのメンバーを参照しないため、null ポインターを逆参照しないため、null ポインターを逆参照する弾丸を効果的に回避できます。

正式には、null ポインターで任意の関数 (非仮想関数であっても) を呼び出すことは、未定義の動作です。未定義の動作によって許容される結果の 1 つは、意図したとおりにコードが実行されているように見えることです。これに依存するべきではありませんが、コンパイラ ベンダーのライブラリがそれに依存している場合があります。ただし、コンパイラ ベンダーには、未定義の動作にさらに定義を追加できるという利点があります。自分でやらないでください。

于 2009-03-21T18:53:42.817 に答える
17

say_hi()メンバー関数は通常、コンパイラによって次のように実装されます。

void say_hi(Foo *this);

メンバーにアクセスしないため、呼び出しは成功します (標準に従って未定義の動作を入力している場合でも)。

Fooまったく割り当てられません。

于 2009-03-21T18:45:07.130 に答える
7

NULL ポインターを逆参照すると、「未定義の動作」が発生します。これは、何かが起こる可能性があることを意味します。コードが正しく動作しているように見えることさえあります。ただし、これに依存してはいけません。同じコードを別のプラットフォーム (または場合によっては同じプラットフォーム) で実行すると、クラッシュする可能性があります。

あなたのコードには Foo オブジェクトはなく、値 NULL で初期化されたポインタのみがあります。

于 2009-03-21T18:44:21.797 に答える
5

これは未定義の動作です。しかし、ほとんどのコンパイラは、メンバー変数と仮想テーブルにアクセスしない場合に、この状況を正しく処理する命令を作成しました。

何が起こるかを理解するためにビジュアルスタジオで逆アセンブルを見てみましょう

   Foo* foo = 0;
004114BE  mov         dword ptr [foo],0 
    foo->say_hi(); // works well
004114C5  mov         ecx,dword ptr [foo] 
004114C8  call        Foo::say_hi (411091h) 
    foo->say_virtual_hi(); // will crash the app
004114CD  mov         eax,dword ptr [foo] 
004114D0  mov         edx,dword ptr [eax] 
004114D2  mov         esi,esp 
004114D4  mov         ecx,dword ptr [foo] 
004114D7  mov         eax,dword ptr [edx] 
004114D9  call        eax  

ご覧のとおり、Foo:say_hi は通常の関数として呼び出されますが、これは ecx レジスタにあります。簡単にするために、これは例では決して使用しない暗黙のパラメーターとして渡されたと想定できます
しかし、2番目のケースでは、仮想テーブルによる関数のアドレスを計算します-fooアドレスによるもので、コアを取得します。

于 2009-03-21T19:00:08.387 に答える
2

どちらの呼び出しも未定義の動作を生成し、その動作が予期しない方法で現れる可能性があることを認識することが重要です。通話が機能しているように見えても、地雷原を敷設している可能性があります。

あなたの例にこの小さな変更を検討してください:

Foo* foo = 0;
foo->say_hi(); // appears to work
if (foo != 0)
    foo->say_virtual_hi(); // why does it still crash?

is null の場合の最初の呼び出しでfoo未定義の動作が有効になるため、コンパイラはそれが null ではないfooと自由に想定できるようになりました。これにより冗長になり、コンパイラーはそれを最適化できます! これは非常に無意味な最適化だと思うかもしれませんが、コンパイラの作成者は非常に積極的になり、実際のコードでこのようなことが起こっています。fooif (foo != 0)

于 2015-04-24T17:49:57.963 に答える
2

say_hi の呼び出しは静的にバインドされています。したがって、コンピューターは実際には、関数に対して標準的な呼び出しを行うだけです。関数はフィールドを使用しないため、問題はありません。

virtual_say_hi の呼び出しは動的にバインドされるため、プロセッサは仮想テーブルに移動します。そこには仮想テーブルがないため、ランダムな場所にジャンプしてプログラムをクラッシュさせます。

于 2009-03-21T18:53:28.623 に答える
2

a) 暗黙の「this」ポインターを介して何も逆参照しないため、機能します。あなたがそれをするとすぐに、ブーム。100% 確実ではありませんが、ヌル ポインターの逆参照はメモリ空間の最初の 1K を保護する RW によって行われると思います。次のように、非常に遠くに割り当てられます。

 class A {
     char foo[2048];
     int i;
 }

A が null の場合、a->i は捕捉されない可能性があります。

b) どこにも、main():s スタックに割り当てられたポインターのみを宣言しました。

于 2009-03-21T18:49:43.920 に答える
1

C++ の元の時代には、C++ コードは C に変換されました。オブジェクト メソッドは、次のように非オブジェクト メソッドに変換されます (この場合)。

foo_say_hi(Foo* thisPtr, /* other args */) 
{
}

もちろん、foo_say_hi という名前は単純化されています。詳細については、C++ 名マングリングを参照してください。

ご覧のとおり、thisPtr が逆参照されない場合、コードは問題なく成功します。あなたの場合、インスタンス変数や thisPtr に依存するものは使用されていません。

ただし、仮想関数は異なります。適切なオブジェクト ポインターがパラメーターとして関数に渡されることを確認するために、多くのオブジェクト ルックアップがあります。これにより、thisPtr が逆参照され、例外が発生します。

于 2009-03-21T18:59:31.323 に答える