1. ホーム
  2. c++

[解決済み] NULLインスタンスに対してメンバ関数を呼び出すと、どのような場合に未定義の動作となるのですか?

2022-08-03 18:31:36

質問

以下のコードを考えてみましょう。

#include <iostream>

struct foo
{
    // (a):
    void bar() { std::cout << "gman was here" << std::endl; }

    // (b):
    void baz() { x = 5; }

    int x;
};

int main()
{
    foo* f = 0;

    f->bar(); // (a)
    f->baz(); // (b)
}

私たちは (b) がクラッシュすることを期待します。なぜなら、対応するメンバ x に対応するメンバがないからです。実際には (a) はクラッシュしないので this のポインタは決して使われないので、クラッシュしません。

なぜなら (b)this ポインタ ( (*this).x = 5; )、および this が null の場合、null を参照することは常に未定義の動作であると言われているため、 プログラムは未定義の動作に入ります。

(a) は未定義の動作になるのでしょうか?両方の関数(と x が静的である場合はどうでしょうか?

どのように解決するのですか?

どちらも (a)(b) は未定義の動作になります。nullポインタを介してメンバ関数を呼び出すのは常に未定義の動作です。関数が静的な場合、技術的には同様に未定義ですが、いくつかの論争があります。


最初に理解すべきことは、NULLポインタを参照解除することがなぜ未定義の動作であるのかということです。C++03 では、実際にはここに少し曖昧さがあります。

とはいえ "ヌルポインターを再参照すると、未定義の動作になります" は、§1.9/4 と§8.3.2/4 の両方のノートで言及されていますが、決して明示的に述べられていません。(注釈は非規範的なものです)。

しかし、§3.10/2から推論してみることは可能です。

lvalueはオブジェクトや関数を指します。

デリファレンスする場合、結果はlvalueになります。nullポインタ ではありません。 はオブジェクトを参照しないので、lvalueを使用すると未定義の動作になります。問題は、前の文が述べられていないことです。では、lvalue を "use" するとはどういうことでしょうか?それとも、lvalue から rvalue への変換を実行するという、より正式な意味で使用するのでしょうか?

とにかく、それは間違いなくrvalueに変換することはできません(§4.1/1)。

lvalueが参照するオブジェクトがT型のオブジェクトでなく、Tから派生した型のオブジェクトでもない場合、またはオブジェクトが初期化されていない場合、この変換を必要とするプログラムは未定義の動作をします。

ここでは間違いなく未定義の動作です。

曖昧なのは、deferenceに対するundefined behaviorかどうかという点です。 を使用せず を使用しない(つまり、l 値を取得するが r 値に変換しない)ことが、未定義の動作であるかどうかに起因しています。もしそうでなければ int *i = 0; *i; &(*i); はうまく定義されています。これは アクティブイシュー .

つまり、strict "dereference a null pointer, get undefined behavior" ビューと weak "use a dereferenced null pointer, get undefined behavior" ビューを持っているということです。

さて、質問を考えてみましょう。


はい。 (a) は未定義の動作をもたらします。実際、もし this がNULLの場合 の内容に関わらず、関数 の場合、結果は未定義です。

これは§5.2.5/3から続いています。

もし E1 が "pointer to class X" という型を持っているならば、この式は E1->E2 は等価な形式に変換されます。 (*(E1)).E2;

*(E1) は厳密な解釈では未定義の動作となり .E2 はrvalueに変換するため、弱解釈では未定義の動作になります。

また、(§9.3.1/1)から直接未定義な動作であることがわかります。

クラスXの非静的メンバ関数が、X型でもXから派生した型でもないオブジェクトに対して呼び出された場合、その動作は未定義である。


静的関数では、厳密な解釈と弱い解釈が違いを生みます。厳密には未定義です。

静的メンバはクラスメンバアクセス構文を用いて参照することができ、その場合、オブジェクト式が評価されます。

つまり、非静的であるかのように評価され、再びNULLポインタの参照を解除して (*(E1)).E2 .

しかし E1 は静的メンバ関数呼び出しでは使われないので、弱解釈を使えば呼び出しはうまく定義されます。 *(E1) が lvalue を生成した場合、静的関数は解決されます。 *(E1) は破棄され、関数が呼び出されます。lvalueからrvalueへの変換がないため、未定義の動作はありません。

C++0x では、n3126 の時点で、曖昧さが残っています。今のところ、安全であること:厳密な解釈を使用してください。