web-dev-qa-db-ja.com

ヌル参照は可能ですか?

このコードは有効ですか(および定義された動作)?

int &nullReference = *(int*)0;

G ++とclang ++は、-Wall-Wextra-std=c++98-pedantic-Weffc++...を使用している場合でも、警告なしでコンパイルします。

もちろん、参照は実際にはnullではありません。アクセスできないためです(nullポインターの逆参照を意味します)が、アドレスをチェックすることでnullかどうかを確認できます。

if( & nullReference == 0 ) // null reference
87
peoro

参照はポインターではありません。

8.3.2/1:

参照は、有効なオブジェクトまたは関数を参照するように初期化されます。 [注:特に、null参照は明確に定義されたプログラムには存在できません。そのような参照を作成する唯一の方法は、nullポインターを逆参照することによって取得される「オブジェクト」にバインドするため、未定義の動作が発生するためです。 9.6で説明したように、参照をビットフィールドに直接バインドすることはできません。 ]

1.9/4:

他の特定の操作は、この国際標準では未定義として説明されています(たとえば、nullポインターの逆参照の影響)

Johannesが削除された回答で述べているように、「nullポインターの逆参照」を未定義の動作であると明確に述べる必要があるかどうかには疑問があります。しかし、nullポインターは確かに「有効なオブジェクトまたは関数」を指していないため、これは疑念を引き起こすケースの1つではありません。また、null参照を導入したいという標準委員会内の希望はありません。

68
Steve Jessop

答えはあなたの視点に依存します:


C++標準で判断すると、未定義の動作が最初に発生するため、null参照を取得できません。未定義の動作が最初に発生した後、標準では何でも起こります。そのため、*(int*)0を記述した場合、言語標準の観点からは、nullポインターの逆参照を行う未定義の動作が既にあります。プログラムの残りの部分は無関係です。この式が実行されると、ゲームから除外されます。


ただし、実際には、nullポインタからnull参照を簡単に作成でき、null参照の背後の値に実際にアクセスしようとするまで気付かないでしょう。優れた最適化コンパイラーは未定義の動作を確認し、それに依存するものを単純に最適化します(null参照は作成されず、最適化されます)。

しかし、その最適化はコンパイラーに依存して未定義の動作を証明しますが、これは不可能な場合があります。ファイル_converter.cpp_内のこの単純な関数を考えてください:

_int& toReference(int* pointer) {
    return *pointer;
}
_

コンパイラーはこの関数を見ると、ポインターがNULLポインターであるかどうかを知りません。したがって、ポインタを対応する参照に変換するコードを生成するだけです。 (ところで:ポインターと参照はアセンブラーでまったく同じ獣なので、これは何もしません。)ここで、コードを持つ別のファイル_user.cpp_がある場合

_#include "converter.h"

void foo() {
    int& nullRef = toReference(nullptr);
    cout << nullRef;    //crash happens here
}
_

コンパイラーは、toReference()が渡されたポインターを逆参照することを認識せず、有効な参照を返すと想定しますが、実際にはnull参照になります。呼び出しは成功しますが、参照を使用しようとすると、プログラムがクラッシュします。うまくいけば。この標準では、ping象の出現を含め、あらゆることが可能です。

なぜこれが関連するのかと疑問に思うかもしれません。結局、未定義の動作はtoReference()内で既にトリガーされています。答えはデバッグです。nullポインターが行うように、null参照は伝播および増殖する可能性があります。 null参照が存在する可能性があることに気づかず、それらの作成を回避する方法を学習する場合、単純に古いintを読み取ろうとしているときにメンバー関数がクラッシュするように見える理由を理解しようとしてかなりの時間を費やすかもしれませんメンバー(回答:メンバーの呼び出しのインスタンスはnull参照であったため、thisはnullポインターであり、メンバーはアドレス8として配置されるように計算されます)。


それでは、null参照を確認するのはどうでしょうか?あなたはラインを与えました

_if( & nullReference == 0 ) // null reference
_

あなたの質問に。まあ、それは動作しません:標準によれば、nullポインターを逆参照すると、未定義の動作があり、nullポインターを逆参照せずにnull参照を作成できないため、null参照は未定義動作の領域内にのみ存在します。 コンパイラは未定義の動作をトリガーしていないと仮定するため、null参照などは存在しないと仮定できます(null参照を生成するコードを簡単に出力しますが!)そのため、if()条件を確認し、trueにはなり得ないと判断し、if()ステートメント全体を破棄します。リンク時間の最適化の導入により、null参照を堅牢な方法でチェックすることは不可能になりました。


TL; DR:

ヌル参照はやや恐ろしい存在です:

それらの存在は不可能と思われます(=標準では)、
しかしそれらは存在します(=生成されたマシンコードによる)、
しかし、それらが存在する場合は表示できません(=試行は最適化されます)、
しかし、彼らはとにかく気付かずにあなたを殺すかもしれません(=プログラムは奇妙なポイントでクラッシュするか、もっと悪いです)。
あなたの唯一の希望は、それらが存在しないことです(=それらを作成しないようにプログラムを書いてください)。

それがあなたを悩ませないことを願っています!

13
cmaster

シングルトンオブジェクトの列挙でnullを表す方法を見つけることを目的とした場合は、null(C++ 11、nullptr)を参照(参照)することはお勧めできません。

次のように、クラス内でNULLを表す静的シングルトンオブジェクトを宣言し、nullptrを返すポインターへのキャスト演算子を追加してみませんか?

編集:いくつかのミスタイプを修正し、main()にifステートメントを追加して、実際に動作しているポインターへのキャスト演算子をテストしました(忘れてしまいました。私の悪いことです)-2015年3月10日-

// Error.h
class Error {
public:
  static Error& NOT_FOUND;
  static Error& UNKNOWN;
  static Error& NONE; // singleton object that represents null

public:
  static vector<shared_ptr<Error>> _instances;
  static Error& NewInstance(const string& name, bool isNull = false);

private:
  bool _isNull;
  Error(const string& name, bool isNull = false) : _name(name), _isNull(isNull) {};
  Error() {};
  Error(const Error& src) {};
  Error& operator=(const Error& src) {};

public:
  operator Error*() { return _isNull ? nullptr : this; }
};

// Error.cpp
vector<shared_ptr<Error>> Error::_instances;
Error& Error::NewInstance(const string& name, bool isNull = false)
{
  shared_ptr<Error> pNewInst(new Error(name, isNull)).
  Error::_instances.Push_back(pNewInst);
  return *pNewInst.get();
}

Error& Error::NOT_FOUND = Error::NewInstance("NOT_FOUND");
//Error& Error::NOT_FOUND = Error::NewInstance("UNKNOWN"); Edit: fixed
//Error& Error::NOT_FOUND = Error::NewInstance("NONE", true); Edit: fixed
Error& Error::UNKNOWN = Error::NewInstance("UNKNOWN");
Error& Error::NONE = Error::NewInstance("NONE");

// Main.cpp
#include "Error.h"

Error& getError() {
  return Error::UNKNOWN;
}

// Edit: To see the overload of "Error*()" in Error.h actually working
Error& getErrorNone() {
  return Error::NONE;
}

int main(void) {
  if(getError() != Error::NONE) {
    return EXIT_FAILURE;
  }

  // Edit: To see the overload of "Error*()" in Error.h actually working
  if(getErrorNone() != nullptr) {
    return EXIT_FAILURE;
  }
}
8
David Lee

clang ++ 3.5でも警告されます:

/tmp/a.C:3:7: warning: reference cannot be bound to dereferenced null pointer in well-defined C++ code; comparison may be assumed to
      always evaluate to false [-Wtautological-undefined-compare]
if( & nullReference == 0 ) // null reference
      ^~~~~~~~~~~~~    ~
1 warning generated.
6
Jan Kratochvil