null参照は可能ですか?


102

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

int &nullReference = *(int*)0;

g ++および打ち鳴らす++使用する場合でも、警告なしにそれをコンパイルし、両方の-Wall-Wextra-std=c++98-pedantic-Weffc++...

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

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

1
これが実際に役立つケースを教えてください。言い換えれば、これは単なる理論的な質問ですか?
cdhowie

さて、リファレンスはこれまでに不可欠ですか?代わりに、常にポインターを使用できます。このようなnull参照を使用すると、参照するオブジェクトがない場合にも参照を使用できます。それがどれほど汚いかわからないが、それを考える前に、その合法性に興味があった。
peoro


22
「確認できました」-いいえ、できません。if (false)とにかく参照をnullにすることはできないため、ステートメントをに変換してチェックを行わないコンパイラがあります。よりよくドキュメント化されたバージョンがLinuxカーネルに存在し、非常に類似したNULLチェックが最適化されました:isc.sans.edu/diary.html
storyid=

2
「ポインタの代わりに参照を使用する主な理由の1つは、それが有効なオブジェクトを参照しているかどうかを確認するためにテストしなければならない負担からあなたを解放することです」この回答は、デフォルトのリンクで、かなりいいですね!
ペオロ

回答:


75

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

8.3.2 / 1:

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

1.9 / 4:

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

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


ヌルポインターを逆参照し、それを参照する左辺値を取得するという単なる問題は、実際に参照をバインドすることとは異なることに気付いたので、回答を削除しました。左辺値もオブジェクトまたは関数を参照すると言われていますが(この点では、実際には参照バインディングとの違いはありません)、これら2つはまだ別々の問題です。単なる逆参照の行為については、ここにリンクがあります:open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1102
Johannes Schaub-litb

1
@MSalters(削除された回答に対するコメントへの返信。ここでは関連性があります)私は、そこに示されているロジックに特に同意できません。それはElideのために便利かもしれないが&*pとしてp普遍的に、それは(その性質により、「仕事に思える」こと)未定義の動作を排除しません。またtypeid、「逆参照されたnullポインター」のタイプを判別しようとする式が実際にnullポインターを逆参照することには同意しません。&a[size_of_array]信頼できないこと、信頼すべきでないことを人々が真剣に主張するのを見てきました。とにかく書くだけの方が簡単で安全a + size_of_arrayです。
Karl Knechtel、2010

[c ++]タグの@Default Standardsは高くする必要があります。私の答えは両方の行為が同じものであるように聞こえました:)「オブジェクトなし」を参照するあなたが渡さないあなたが渡さない左辺値を取得することは実行可能であるかもしれませんはるかに多くのコード。
Johannes Schaub-litb

@KarlはC ++ではよく、「逆参照」は値を読み取ることを意味しません。「逆参照」とは実際に格納された値にアクセスまたは変更することを意味すると考える人もいますが、それは正しくありません。その論理は、C ++が左辺値が「オブジェクトまたは関数」を参照すると言っているということです。そうである場合、問題は左辺値が何を*p参照するか、いつpnullポインタであるかです。C ++には現在、空の左辺値の概念がありません。これは、232号で紹介したかったものです。
Johannes Schaub-litb

typeidセマンティクスに基づくのではなく、構文に基づく作業における逆参照されたnullポインターの検出。つまり、あなたがしなければtypeid(0, *(ostream*)0)、あなたはやる未定義の動作を持っていない-何bad_typeidあなたが意味的にヌルポインタ参照の結果左辺値を渡していても、スローされることが保証されます。しかし、構文的にはトップレベルでは、逆参照ではなく、コンマ演算子式です。
Johannes Schaub-litb

26

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


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参照である有効な参照を返すと想定します。呼び出しは成功しますが、参照を使用しようとすると、プログラムがクラッシュします。うまくいけば。この標準では、ピンクの象の外観を含め、あらゆることが起こります。

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


では、null参照をチェックするのはどうでしょうか?あなたはラインを与えました

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

あなたの質問で。まあ、それは機能しません:標準によると、nullポインターを逆参照すると、未定義の動作が発生し、nullポインターを逆参照せずにnull参照を作成することはできないため、null参照は未定義の動作の領域内にのみ存在します。コンパイラーは、未定義の動作をトリガーしていないと想定する場合があるため、null参照などのものが存在しないと想定できますnull参照を生成するコードがすぐに出力される場合でも!)。そのため、if()条件を確認し、それが真ではないと結論付け、if()ステートメント全体を破棄します。リンク時の最適化の導入により、堅牢な方法でnull参照をチェックすることは明らかに不可能になりました。


TL; DR:

null参照はやや恐ろしい存在です。

それらの存在は(=標準では)不可能であるように見えます
が、存在します(=生成されたマシンコードによる)が、
存在するかどうかはわかりません(=試行は最適化されます)
とにかく、気づかないうちに殺される可能性があります(=あなたのプログラムは奇妙なポイントで、またはそれより悪いところでクラッシュします。
あなたの唯一の望みは、それらが存在しないことです(=それらを作成しないようにプログラムを記述してください)。

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


2
「ping象」とは正確には何ですか?
Pharap

2
@Pharap私には手掛かりがありません、それは単なるタイプミスでした。しかし、C ++標準では、ピンクと象のどちらが表示されるかは関係ありません;-)
cmaster-モニカを

9

シングルトンオブジェクトの列挙で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;
  }
}

それはゆっくりとだから
wandalen

6

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.
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.