1

さまざまなプラットフォームで G++ (4.5.2) を使用すると、非常に奇妙な動作に遭遇しました。コードは次のとおりです。

class Class
{
private:
  std::string rString;

public:
  Class()
  {
    this->rString = "random string";
    std::cout << "Constructor of Class" << std::endl;
  }

  virtual ~Class()
  {
    std::cout << "Destructor of Class" << std::endl;
  }

  void          say() const
  {
    std::cout << "Just saying ..." << std::endl;
    if (this == NULL)
      std::cout << "Man that's really bad" << std::endl;
  }

  void          hello() const
  {
    std::cout << "Hello " << this->rString << std::endl;
  }

};


int     main()
{
  Class *c = NULL;

  /* Dereferencing a NULL pointer results
     in a successful call to the non-static method say()
     without constructing Class */
  (*c).say(); // or c->say()

  /* Dereferencing a NULL pointer and accessing a random
     memory area results in a successful call to say()
     as well */
  c[42000].say();

  /* Dereferencing a NULL pointer and accessing a
     method which needs explicit construction of Class
     results in a Segmentation fault */
  c->hello();

  return (0);
}

問題は、メイン関数の最初の 2 つのステートメントがプログラムをクラッシュさせないのはなぜですか? これは未定義の動作ですか、それともメソッド内で「this」ポインターを逆参照しないため、コンパイラーは Class::say() を静的であるかのように呼び出しているだけですか?

4

5 に答える 5

10

はい、それは未定義の動作です。nullポインタを使用してメンバー関数を呼び出すことはできません

実際には、最初の2つは実際に機能thisします。これは、逆参照されることがないため、メモリが実際に誤ってアクセスされる3番目の場合のように未定義の動作が現れる必要がないためです。

(すべての場合において、呼び出されるたびに少し内側で死ぬので、そうしないでください。)

于 2012-10-18T21:40:11.367 に答える
6

未定義の動作は、何が起こるかが定義されていないことを意味します。「クラッシュ」という意味ではありません。

未定義の振る舞いは、意図したとおりに動作することを含め、何でもできます。

于 2012-10-18T21:41:38.237 に答える
1

これは未定義の動作です。関数を「仮想」として使用していないため、これは関数を静的に呼び出すだけです。

于 2012-10-18T21:43:31.730 に答える
1

これは未定義の動作ですが、最初の2つのインスタンスでClass::say()は、オブジェクト自体のメンバー変数を使用せずに呼び出すように最適化されています(したがって、これは逆参照/使用されないため、sigservが発生します)が、3回目はそのメンバーにアクセスしようとします。同様に、VC++のような別のコンパイラで次のセグメンテーション違反が発生する可能性があります。

于 2012-10-18T21:43:32.237 に答える
0

this内部的には、コンパイラは、オブジェクト ポインターを追加のパラメーターとして渡すか、スタック上のすべての適切なパラメーターと共に渡すだけで、非静的、非仮想関数への呼び出しを実装します。

標準は、メンバー関数が、それぞれのクラスまたは構造体の実際のオブジェクトを保持するメモリ位置ではない何かで呼び出されたときに何が起こるかを義務付けていません。これを要求すると、noop 以外のランタイム チェックが必要になるため (可能であれば)、適合する実装に要求することは合理的ではありません。

型付けシステムは、間違った型のオブジェクトを使用してメンバー関数を呼び出す方法を既に確認していますが、ポインター型では意味のあるチェックNULLnullptr呼び出しをカバーすることはできません。
メンバー関数が null ポインターで呼び出されたときにバイナリをクラッシュさせたい場合は、メンバー関数を作成するvirtual必要があります。これは、コンパイラーがvptrを逆参照する必要がthisあり、オペレーティング システムが指を表示して応答するためです。

于 2012-10-19T02:13:25.847 に答える