C++ 是否可以空引用?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4364536/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-28 15:14:02  来源:igfitidea点击:

Is null reference possible?

c++referencenulllanguage-lawyer

提问by peoro

Is this piece of code valid (and defined behavior)?

这段代码是否有效(和定义的行为)?

int &nullReference = *(int*)0;

Both g++ and clang++ compile it without any warning, even when using -Wall, -Wextra, -std=c++98, -pedantic, -Weffc++...

即使使用-Wall, -Wextra, -std=c++98, -pedantic, -Weffc++...

Of course the reference is not actually null, since it cannot be accessed (it would mean dereferencing a null pointer), but we could check whether it's null or not by checking its address:

当然,引用实际上不是空的,因为它不能被访问(这意味着取消引用空指针),但我们可以通过检查它的地址来检查它是否为空:

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

采纳答案by Steve Jessop

References are not pointers.

引用不是指针。

8.3.2/1:

8.3.2/1:

A reference shall be initialized to refer to a valid object or function. [Note: in particular, a null reference cannot exist in a well-defined program, because the only way to create such a reference would be to bind it to the “object” obtained by dereferencing a null pointer, which causes undefined behavior. As described in 9.6, a reference cannot be bound directly to a bit-field. ]

应初始化引用以引用有效的对象或函数。[注意:特别是,在定义良好的程序中不能存在空引用,因为创建这种引用的唯一方法是将其绑定到通过取消引用空指针获得的“对象”,这会导致未定义的行为。如 9.6 中所述,引用不能直接绑定到位域。]

1.9/4:

1.9/4:

Certain other operations are described in this International Standard as undefined (for example, the effect of dereferencing the null pointer)

本国际标准中将某些其他操作描述为未定义(例如,取消引用空指针的效果)

As Johannes says in a deleted answer, there's some doubt whether "dereferencing a null pointer" should be categorically stated to be undefined behavior. But this isn't one of the cases that raise doubts, since a null pointer certainly does not point to a "valid object or function", and there is no desire within the standards committee to introduce null references.

正如约翰内斯在已删除的答案中所说的那样,是否应该将“取消引用空指针”明确地声明为未定义的行为存在一些疑问。但这不是引起怀疑的情况之一,因为空指针肯定不指向“有效对象或函数”,并且标准委员会内不希望引入空引用。

回答by cmaster - reinstate monica

The answer depends on your view point:

答案取决于您的观点:



If you judge by the C++ standard, you cannot get a null reference because you get undefined behavior first. After that first incidence of undefined behavior, the standard allows anything to happen. So, if you write *(int*)0, you already have undefined behavior as you are, from a language standard point of view, dereferencing a null pointer. The rest of the program is irrelevant, once this expression is executed, you are out of the game.

如果按照 C++ 标准判断,是无法得到空引用的,因为首先得到的是未定义行为。在第一次发生未定义行为之后,标准允许任何事情发生。因此,如果您编写*(int*)0,那么从语言标准的角度来看,您已经具有未定义的行为,即取消引用空指针。程序的其余部分无关紧要,一旦执行此表达式,您就退出了游戏。



However, in practice, null references can easily be created from null pointers, and you won't notice until you actually try to access the value behind the null reference. Your example may be a bit too simple, as any good optimizing compiler will see the undefined behavior, and simply optimize away anything that depends on it (the null reference won't even be created, it will be optimized away).

然而,在实践中,可以很容易地从空指针创建空引用,并且在您实际尝试访问空引用背后的值之前您不会注意到。你的例子可能有点太简单了,因为任何好的优化编译器都会看到未定义的行为,并简单地优化掉任何依赖它的东西(甚至不会创建空引用,它会被优化掉)。

Yet, that optimizing away depends on the compiler to prove the undefined behavior, which may not be possible to do. Consider this simple function inside a file converter.cpp:

然而,优化取决于编译器来证明未定义的行为,这可能是不可能的。考虑文件中的这个简单函数converter.cpp

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

When the compiler sees this function, it does not know whether the pointer is a null pointer or not. So it just generates code that turns any pointer into the corresponding reference. (Btw: This is a noop since pointers and references are the exact same beast in assembler.) Now, if you have another file user.cppwith the code

当编译器看到这个函数时,它不知道该指针是否为空指针。所以它只是生成将任何指针转换为相应引用的代码。(顺便说一句:这是一个 noop,因为指针和引用在汇编程序中是完全相同的。)现在,如果你有另一个user.cpp包含代码的文件

#include "converter.h"

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

the compiler does not know that toReference()will dereference the passed pointer, and assume that it returns a valid reference, which will happen to be a null reference in practice. The call succeeds, but when you try to use the reference, the program crashes. Hopefully. The standard allows for anything to happen, including the appearance of pink elephants.

编译器不知道这toReference()将取消对传递的指针的引用,并假设它返回一个有效的引用,实际上它恰好是一个空引用。调用成功,但当您尝试使用引用时,程序崩溃。希望。该标准允许任何事情发生,包括粉红色大象的出现。

You may ask why this is relevant, after all, the undefined behavior was already triggered inside toReference(). The answer is debugging: Null references may propagate and proliferate just as null pointers do. If you are not aware that null references can exist, and learn to avoid creating them, you may spend quite some time trying to figure out why your member function seems to crash when it's just trying to read a plain old intmember (answer: the instance in the call of the member was a null reference, so thisis a null pointer, and your member is computed to be located as address 8).

你可能会问为什么这是相关的,毕竟 undefined 行为已经在toReference(). 答案是调试:空引用可能会像空指针一样传播和增殖。如果您不知道可以存在空引用,并学会避免创建它们,那么您可能会花费相当多的时间试图弄清楚为什么您的成员函数在尝试读取一个普通的旧int成员时似乎会崩溃(答案:实例在成员的调用中是一个空引用,this一个空指针也是一个空指针,并且您的成员被计算为位于地址 8)。



So how about checking for null references? You gave the line

那么如何检查空引用呢?你给了这条线

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

in your question. Well, that won't work: According to the standard, you have undefined behavior if you dereference a null pointer, and you cannot create a null reference without dereferencing a null pointer, so null references exist only inside the realm of undefined behavior. Since your compiler may assume that you are not triggering undefined behavior, it can assume that there is no such thing as a null reference(even though it will readily emit code that generates null references!). As such, it sees the if()condition, concludes that it cannot be true, and just throw away the entire if()statement. With the introduction of link time optimizations, it has become plain impossible to check for null references in a robust way.

在你的问题中。好吧,那是行不通的:根据标准,如果您取消引用空指针,则会出现未定义的行为,并且您无法在不取消引用空指针的情况下创建空引用,因此空引用仅存在于未定义行为的范围内。由于您的编译器可能会假设您没有触发未定义的行为,因此它可以假设不存在空引用之类的东西(即使它很容易发出生成空引用的代码!)。因此,它看到if()条件,断定它不可能为真,然后丢弃整个if()语句。随着链接时间优化的引入,以健壮的方式检查空引用变得明显不可能。



TL;DR:

特尔;博士:

Null references are somewhat of a ghastly existence:

空引用有点可怕:

Their existence seems impossible (= by the standard),
but they exist (= by the generated machine code),
but you cannot see them if they exist (= your attempts will be optimized away),
but they may kill you unaware anyway (= your program crashes at weird points, or worse).
Your only hope is that they don't exist (= write your program to not create them).

它们的存在似乎是不可能的(= 根据标准),
但它们存在(= 通过生成的机器代码),
但是如果它们存在,您将看不到它们(= 您的尝试将被优化掉),
但无论如何它们可能会在不知不觉中杀死您(=您的程序在奇怪的地方崩溃,或者更糟)。
您唯一的希望是它们不存在(= 编写您的程序以不创建它们)。

I do hope that will not come to haunt you!

我希望这不会困扰你!

回答by David Lee

If your intention was to find a way to represent null in an enumeration of singleton objects, then it's a bad idea to (de)reference null (it C++11, nullptr).

如果您的意图是在单例对象的枚举中找到一种表示 null 的方法,那么(取消)引用 null(它是 C++11,nullptr)是一个坏主意。

Why not declare static singleton object that represents NULL within the class as follows and add a cast-to-pointer operator that returns nullptr ?

为什么不在类中声明表示 NULL 的静态单例对象,如下所示,并添加一个返回 nullptr 的强制转换为指针的运算符?

Edit: Corrected several mistypes and added if-statement in main() to test for the cast-to-pointer operator actually working (which I forgot to.. my bad) - March 10 2015 -

编辑:更正了几个错误类型并在 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;
  }
}

回答by Jan Kratochvil

clang++ 3.5 even warns on it:

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.