空引用可能吗?

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

int &nullReference = *(int*)0;

G + + 和 clang + + 在没有任何警告的情况下编译它,即使使用 -Wall-Wextra-std=c++98-pedantic-Weffc++..。

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

if( & nullReference == 0 ) // null reference
154171 次浏览

引用不是指针。

8.3.2/1:

引用应初始化为 指有效的对象或函数。 [注意: 特别是一个空引用 不能存在于一个定义明确的 程序,因为唯一的方法 创建这样一个引用将是 将其绑定到由 取消对空指针的引用,该指针 会引起未定义行为 9.6所说明,参考文献不能 直接绑定到位场。]

1.9/4:

描述了某些其他操作 在本国际标准中, 未定义的(例如 解除空指针的引用)

正如 Johannes 在一个被删除的回答中所说的,是否应该明确声明“解除空指针的引用”是未定义行为还存在一些疑问。但这并不是引起怀疑的情况之一,因为 null 指针当然不会指向“有效的对象或函数”,而且标准委员会内部也不希望引入 null 引用。

如果您的目的是找到一种在单例对象的枚举中表示 null 的方法,那么(de)引用 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;
}
}

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.

答案取决于你的观点:


如果你用 C + + 标准来判断,你不可能得到一个 null 引用,因为你首先得到的是未定义行为。在第一次发生未定义行为之后,标准允许任何事情发生。因此,如果你编写的是 *(int*)0,那么从语言标准的角度来看,你已经拥有了解除空指针引用的未定义行为。程序的其余部分是不相关的,一旦这个表达式被执行,你就出局了。


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

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

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

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

#include "converter.h"


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

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

你可能会问为什么这是相关的,毕竟,未定义行为已经在 toReference()内部被触发。答案是调试: 空引用可以像空指针一样传播和扩散。如果你没有意识到 null 引用可能存在,并且学会避免创建它们,你可能会花费相当长的时间试图弄清楚为什么你的成员函数似乎崩溃时,它只是试图读取一个普通的老的 int成员(答案: 实例在成员的调用是一个 null 引用,所以 this是一个空指针,你的成员被计算定位为地址8)。


那么检查空引用怎么样? 你给了这行

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

在你的问题里。根据标准,如果你取消空指针的引用,你就会有未定义行为,如果你不取消空指针的引用,你就不能创建一个空引用,所以空引用只存在于未定义行为的范围内。因为你的编译器可能假设你没有触发未定义行为,它可以假设没有空引用(即使它很容易发出生成空引用的代码!).因此,它看到 if()条件,得出结论认为它不可能为真,然后就抛弃了整个 if()语句。随着链路时间优化的引入,以健壮的方式检查 null 引用已经变得显而易见不可能了。


译者:

空引用的存在有点可怕:

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

我希望这不会困扰你!