16
#include "iostream"
using namespace std;
class A
{
public:
    void mprint()
    {
        cout<<"\n TESTING NULL POINTER";
    }
};

int main()
{
    A *a = NULL;
    a->mprint();
    return 0;
}

「TESTINGNULLPOINTER」として出力されます。このプログラムがクラッシュするのではなく出力を出力する理由を誰かが説明できますか?Dev C ++で確認したところ、aCCコンパイラでも同じ結果が得られました。

4

6 に答える 6

26

のメンバー変数を使用していませんA-関数はAインスタンスから完全に独立しているため、生成されたコードには0を逆参照するものが含まれていません。これは未定義の動作です-一部のコンパイラで機能する可能性があります。未定義動作とは、「何でも起こり得る」ことを意味します。これには、プログラムがプログラマーの期待どおりに機能することも含まれます。

たとえばmprint仮想を作成すると、クラッシュが発生する可能性があります。または、コンパイラが実際にvtableを必要としないことを認識した場合、クラッシュが発生しない可能性があります。

メンバー変数をAに追加してこれを出力すると、クラッシュが発生します。

于 2011-03-25T10:51:38.893 に答える
7

C ++仕様によると、nullレシーバーでメンバー関数を呼び出しているため、このプログラムの動作は未定義です。

ただし、これが機能する理由は、非仮想メンバー関数は通常、「this」ポインターを暗黙の最初の引数として受け取る通常の関数として実装されるためです。したがって、nullポインターでメンバー関数を呼び出す場合、このポインターを使用しない限り、プログラムはクラッシュしません。もちろん、これに頼ることはできません。有効なC++コンパイラは、これをクラッシュさせる可能性があります。

ただし、実際に呼び出される関数は実行時に解決する必要があるため、仮想関数は別の話になります。これには通常、受信者の仮想関数テーブルの内省が含まれます。したがって、nullポインターで仮想メンバー関数を呼び出そうとすると、te関数がこれにアクセスしなくても、クラッシュが発生します。興味があればこれを試してみてください!

于 2011-03-25T10:55:13.177 に答える
5

オブジェクトへのnullポインターを使用してメンバー関数を呼び出した結果は、C ++では未定義の動作であるため、何でも実行できます。

この場合、関数が次のように書き直された可能性があります

void mprint(A* this);

そしてあなたの電話はこのように

mprint(0);

そのため、通常の関数であるかのように呼び出され、nullポインターがパラメーターとして渡されます。このパラメーターは、実際には使用されません。それがクラッシュしない理由を説明していますが、コンパイラはほとんど何でも自由に実行できます

于 2011-03-25T10:52:37.530 に答える
2

簡単な答え:mprint()クラスのメンバー変数を使用していないため

詳細な回答:クラスのメソッドが呼び出されると、クラスインスタンスが呼び出し先関数に渡されます(通常は最初の引数として、ただし、__ thiscallなどの一部の呼び出し規約では、これはレジスタで渡されます)。このクラスインスタンスは、呼び出し先メソッドで使用されるすべてのメンバー変数にアクセスするために使用されます。

この場合、このインスタンスはNULLですが、呼び出し先メソッドでメンバー変数が使用されていないため、これによる違いはありません。メソッド内のメンバー変数の値を出力するようにコードを変更してみてくださいmprint()。そうするとクラッシュします。

于 2011-03-25T10:50:40.380 に答える
1

無効なポインターで非仮想メンバー関数を呼び出すことができると、オブジェクトに関連付けられた情報をポインター自体にエンコードすることもできます。例えば:

#include <iostream>

class MagicInteger {

  public:

   static MagicInteger* fromInt (int x) {
     return reinterpret_cast<MagicInteger*>(x);
   }

   int getValue() {
     return static_cast<int>(reinterpret_cast<intptr_t>(this));
   }       

  private:
   // forbid messing around
   MagicInteger ();  
   MagicInteger (MagicInteger&);
   MagicInteger& operator=(const MagicInteger&);
};

int main (void) {
  MagicInteger* i = MagicInteger::fromInt(6);
  std::cout << "Value is " << i->getValue() << std::endl;
  return 0;
}

これは、タグ付きポインター、つまり、ポインターに関するメタ情報を含むポインターを実装するためにも使用できます。

これらの2つのイディオムは、GoogleChromeのjavascriptVMV8で31ビット整数を表すために使用されます

于 2011-03-26T20:19:20.637 に答える
0

これは完全に合法的な呼びかけです。

それがどのように機能するかを理解しましょう

新しいオブジェクトが作成されると、そのメンバー変数が作成されます。

メンバー関数はどうですか?メンバー関数にはニュースが割り当てられていません。すべてのメンバー関数のコピーが常に1つあります。デフォルトでは、メンバー変数は、オブジェクト自体を指しているこのポインターであるすべてのメンバー関数に追加されます。
オブジェクトポインタが存在しない場合、オブジェクトポインタはnull値になります。あなたがそれにアクセスしていないので、それは重要です。メソッド内のいずれかのメンバー変数のこのポインターを使用すると、問題が発生します。これは、nullポインタの場合、メンバー変数が無効であるためです。

MFCには、CWnd用のGetSafeHwnd()メソッドがあります。これは同じ原理で機能します。

于 2011-03-25T12:47:11.363 に答える