3

として、タイトルは言う:

削除されたポインタで非仮想メンバー関数を呼び出すことが未定義の動作になるのはなぜですか?

質問は、それが未定義の動作であるかどうかを尋ねるのではなく、なぜそれが未定義の動作であるのかを尋ねることに注意してください。


次のプログラムを検討してください。

#include<iostream>
class Myclass
{
    //int i
    public:
      void doSomething()
      {
          std::cout<<"Inside doSomething";
          //i = 10;
      }
};

int main()
{
    Myclass *ptr = new Myclass;
    delete ptr;

    ptr->doSomething();

    return 0;
}

this上記のコードでは、コンパイラはメンバー関数の呼び出し中に実際には逆参照しませんdoSomething()。関数は仮想関数ではなく、コンパイラーはこれを最初のパラメーターとして関数に渡すことにより、メンバー関数呼び出しを通常の関数呼び出しに変換することに注意してください(これは実装定義です)。コンパイラはコンパイル時に呼び出す関数を正確に決定できるため、これを行うことができます。したがって、実際には、削除されたポインターを介してメンバー関数を呼び出しても、は逆参照されませんthis。関数本体内でメンバーがアクセスされた場合にのみ、thisが逆参照されます(つまり、アクセスする上記の例のコメント解除コードi
関数内でメンバーにアクセスしない場合、上記のコードが実際に未定義の動作を呼び出す必要はありません。

では、なぜ標準では、削除されたポインターを介して非仮想メンバー関数を呼び出すことが未定義の動作であると義務付けられているのに、実際には、間接参照thisが未定義の動作を引き起こすステートメントであると確実に言えるのでしょうか。標準が単にそれを一般化するのは、言語のユーザーにとって単純にするためだけですか、それともこのマンデートに関係するより深いセマンティクスがありますか?

おそらく、コンパイラがメンバー関数を呼び出す方法が実装で定義されているため、標準がUBが発生する実際のポイントを強制できない理由であると私は感じています。

誰かが確認できますか?

4

4 に答える 4

9

それが信頼できるかもしれないケースの数はとても少ないので、それをすることはまだ非効率的に愚かな考えです。動作を定義することに利点はありません。

于 2012-12-23T16:14:48.557 に答える
6

では、削除されたポインターを介して非仮想メンバー関数を呼び出すことが未定義の動作であることが標準で義務付けられているのはなぜですか?実際、これを逆参照することは未定義の動作を引き起こすステートメントであると確実に言えるのに?

[expr.ref]段落2は、などのメンバー関数呼び出し、非静的メンバー関数を呼び出すことptr->doSomething()と同等であると述べています。ポインタが無効な場合、それは未定義の動作です。(*ptr).doSomething()

生成されたコードが実際に特定の場合にポインターを逆参照する必要があるかどうかは関係ありませんが、コンパイラーがモデル化する抽象マシン原則として逆参照を行います。

言語を複雑にして、メンバーにアクセスしない限り許可されるケースを正確に定義しても、ほとんどメリットはありません。関数定義が表示されない場合、関数が使用されているかどうかがわからないため、関数を呼び出しても安全かどうかわかりthisません。

ただそれをしないでください、そうする正当な理由はありません、そしてそれは言語がそれを禁じているのは良いことです。

于 2012-12-31T15:43:10.587 に答える
5

C ++言語(C ++ 03による)では、無効なポインターの値を使用しようとすると、未定義の動作がすでに発生しています。UBを実行するために間接参照する必要はありません。ポインタ値を読み取るだけで十分です。単にその値を読み取ろうとしたときにUBを引き起こす「無効な値」の概念は、実際には、ポインターだけでなく、ほとんどすべてのスカラー型に拡張されます。

ポインタがその特定の意味で一般的に無効になった後delete、つまり、「削除」されたばかりの何かを指していると思われるポインタを読み取ると、未定義の動作が発生します。

int *p = new int();
delete p;
int *p1 = p; // <- undefined behavior

無効なポインタを介してメンバー関数を呼び出すことは、上記の特定のケースにすぎません。ポインタは、暗黙パラメータの引数として使用されますthis。ポインタを渡すことは非参照引数であり、それを読み取る行為です。そのため、この例では動作が定義されていません。

したがって、あなたの質問は、無効なポインタ値を読み取ると未定義の動作が発生する理由に要約されます。

それには、プラットフォーム固有の理由がたくさんある可能性があります。たとえば、一部のプラットフォームでは、ポインタを読み取る動作により、ポインタ値が専用のアドレス固有のレジスタにロードされる場合があります。ポインタが無効な場合、ハードウェア/ OSがすぐにポインタを検出し、プログラム障害を引き起こす可能性があります。実際、これは私たちの人気のあるx86プラットフォームがセグメントレジスタに関してどのように機能するかです。それについてあまり耳にしない唯一の理由は、人気のあるOSが単にセグメントレジスタを積極的に使用しないフラットメモリモデルに固執していることです。


C ++ 11は、無効なポインター値を逆参照すると未定義の動作が発生するのに対し、無効なポインター値を他のすべての方法で使用すると実装定義の動作が発生すると実際に述べています。また、「無効なポインタをコピーする」場合の実装定義の動作は、「システム生成のランタイム障害」につながる可能性があることにも注意してください。したがって、実際には、C ++ 11仕様の迷路を注意深く操作し、無効なポインターを介して非仮想メソッドを呼び出すと、上記の実装定義の動作が発生するという結論に達する可能性があります。いずれにせよ、「システムによって生成されたランタイム障害」の可能性は常に存在します。

于 2012-12-23T16:44:38.750 に答える
2

この場合の間接参照はthis、事実上、実装の詳細です。ポインタが標準で定義されていないということでthisはありませんが、意味的に抽象化された観点から、破壊されたオブジェクトの使用を許可する目的は何ですか。それが「安全」になる練習をしますか?なし。そうではありません。オブジェクトが存在しないため、そのオブジェクトで関数を呼び出すことはできません。

于 2013-01-06T14:57:12.037 に答える