0

重複の可能性:
NULLクラスポインタを介してクラスメソッドを呼び出す

私はインタビューでこの質問をされました誰かがそれに答えることができますか?

#include<string>
#include<iostream>
#include <stdio.h>

using namespace std;

class A
{
int k;
public:
     void f1()
    {

     int i;
     printf("1");

    }

     void f2()
    {

     k = 3;
     printf("3");

    }

};
class B
{
int i;
public:
    virtual void f1()
    {
    printf("2");
    scanf("%d",&i);
    }

};


int main()
{
    A* a = NULL;
    B* b = NULL;

    a->f1(); // works why?(non polymorphic)
    b->f1(); // fails why?(polymorphic)
            a->f2(); //fails why?
}

最後の2つのケースは、ポリモーフィッククラスです。最初のケースは通常のクラスです。Aのf1でiにアクセスすると、実行時例外が再び発生することを理解しています。しかし、なぜそれが起こるのかわかりません

4

5 に答える 5

2
a->f1();
b->f1();
a->f2();

3つのケースすべてで、NULLを指すポインター、つまりオブジェクトを指さないポインターを延期しています。これは未定義の動作を構成します。それらは偶然に機能するかもしれませんが、あなたはそれに頼ることはできません。また、1つのバージョンが機能する可能性がある理由を理解しようとしても意味がありません。未定義の動作は、何かが起こる可能性があることを意味します。

于 2012-10-08T14:51:57.317 に答える
2

これは未定義の動作であるという他の投稿に同意します。つまり、「正しいことをする」ことを含め、プログラムの実行時に何かが発生する可能性があります。

それでは、呼び出しがどのように実装されているかを見てみましょう。

a->f1()通常のメソッド呼び出しです(非仮想)。ほとんどのコンパイラは、次のコードと同様の方法でこれをコンパイルします。

    class A { int i; }
    void f1(A* a) { int i; printf("1"); }

つまり、このポインターは実際には関数のパラメーターのように処理されます(実際には、このポインターの処理方法についていくつかの最適化が頻繁に行われますが、ここでは関係ありません)。さて、f1はthisポインターを使用しないので、それがnullであるという事実はクラッシュを引き起こしません。

a->f2()thisポインタを使用しているため、実際にはクラッシュします。更新しますthis->k

toの呼び出しb->f1()は仮想関数呼び出しであり、これは通常、仮想テーブルルックアップを使用して実装されb->vtable[0]()ます。bがnullであるため、仮想テーブルを読み取るための逆参照がクラッシュします。

于 2012-10-08T15:06:55.540 に答える
1

技術的には、これはすべて未定義の動作です。したがって、背景(コンパイラ、使用済み設定)がなければ、これが正解です。

私はこれが彼らが聞くことを期待したものであるとは思わない。

通常、メンバー関数呼び出しはこの方法で内部的に変換されます(意図的に簡略化されています)。

class A {
    void foo(int x) {}  // compiler creates function void A_foo(A* this, int x) {}
};

A a;
a.foo(5); // compiler calls A_foo(&a, 5);

ただし、仮想関数の場合は状況が異なります。ここでは仮想ディスパッチの原理については説明しませんが、簡単にするために、最終的に呼び出される関数は、オブジェクトの動的タイプに依存します。オブジェクトが存在しない場合、プログラムはどの関数を呼び出すかを知ることができません。

a->f2()あなたが失敗する理由について。関数を想像してみてくださいA_f2(A* this)。内部でAのメンバーにアクセスしますk。これは、私の簡略化されたコンパイラではに変換されthis->k = 3ます。しかし、実際の呼び出しthisにはnullポインターがあります。

于 2012-10-08T14:59:49.833 に答える
0

ある意味では、3つのケースのどちらも「機能」しません。しかし、別の方法では、3つのケースすべてが「機能」します。

それらはすべてnullポインターを介して間接参照を実行するため、すべて未定義の動作をします。

Aのf1でiにアクセスすると、ランタイム例外が再び発生することを理解しています

多分そうでないかもしれません。未定義の動作は未定義であるため、何が起こる可能性もあります。

于 2012-10-08T14:50:55.210 に答える
0

3つの例はすべて未定義の動作になるため、これは実装固有であり、すべてのコンパイラで同じ動作が保証されるわけではありません。

仮想関数を実装する一般的な方法は、クラスの先頭にある関数ポインターのテーブルにポインターを追加することです。仮想関数が呼び出されるたびに、プログラムはこのポインターに従い、テーブルを調べて呼び出す関数を決定します。nullポインターの例では、このポインターの無効なアドレスを調べているため、実行時エラーが発生します。

非仮想関数を呼び出す場合、コンパイラーは呼び出す関数を正確に認識しているため、この関数への呼び出しを直接挿入できます。呼び出す関数を決定するために、オブジェクトにアクセスする必要はありません。したがって、関数自体がオブジェクトにアクセスしない場合、関数呼び出しによってnullポインターを介したアクセスが発生することはないため、ランタイムエラーが発生することはありません。

于 2012-10-08T15:06:09.343 に答える