空引用是可能的吗?
这段代码是否有效(和定义的行为)?
int &nullReference = *(int*)0;
即使在使用-Wall
, -Wextra
, -std=c++98
, -pedantic
, -Weffc++
时,g ++和clang ++都可以毫不-Wextra
编译它。
当然,这个引用实际上并不是null,因为它不能被访问(这意味着解引用一个空指针),但是我们可以通过检查它的地址来检查它是否为null:
if( & nullReference == 0 ) // null reference
引用不是指针。
8.3.2 / 1:
引用应被初始化为引用一个有效的对象或函数。 [注意:特别是空引用不能存在于定义良好的程序中,因为创build这种引用的唯一方法是将其与通过解引用空指针而获得的“对象”绑定,这会导致未定义的行为。 如9.6所述,一个引用不能直接绑定到一个位域。 ]
1.9 / 4:
本标准中某些其他的操作被描述为未定义的(例如,取消引用空指针的效果)
正如约翰内斯在一个被删除的答案中所说的,“解除空指针”是否应该被断言是未定义的行为还有一些疑问。 但是,这不是一个引起怀疑的情况,因为空指针当然不是指向“有效的对象或函数”,标准委员会也没有希望引入空引用。
如果你的目的是find一个方法来表示一个单例对象的枚举null,那么(去)引用null(C ++ 11,nullptr)是一个坏主意。
为什么不声明静态单例对象,如下所示在类中表示NULL,并添加一个返回nullptr的强制转换指针运算符?
编辑:更正了几个错误types,并在main()中添加了if语句来testing实际工作的投递指针操作符(我忘记了..我的坏) – 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; } }
铿锵声++ 3.5甚至警告:
/tmp/aC: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 ++标准判断,你不能得到一个空引用,因为你首先得到未定义的行为。 在那个未定义行为的第一次发生之后,这个标准允许任何事情发生。 因此,如果您编写*(int*)0
,则从语言标准的angular度来看,您已经具有未定义的行为,请解除引用空指针。 程序的其余部分是无关紧要的,一旦这个expression式被执行,你就不在游戏中。
但是,在实践中,可以很容易地从空指针创build空引用,除非实际尝试访问空引用后面的值,否则您将不会注意到。 你的例子可能有点太简单了,因为任何好的优化编译器都会看到未定义的行为,只是优化依赖它的任何东西(甚至不会创build空引用,它将被优化)。
然而,优化离开依赖于编译器来certificate未定义的行为,这是不可能做到的。 考虑一个文件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()
。 答案是debugging:空指针可能会传播和扩散,就像空指针一样。 如果你不知道空引用可以存在,并学会避免创build它们,你可能会花费相当多的时间来弄清楚为什么你的成员函数似乎崩溃时,它只是试图读取一个普通的旧的int
成员(答案:实例在成员的调用中是一个空引用,所以this
是一个空指针,并且您的成员被计算为位于地址8)。
那么如何检查空引用? 你给了线
if( & nullReference == 0 ) // null reference
在你的问题。 那么,这是行不通的:根据标准,如果取消引用一个空指针,你就有未定义的行为,并且你不能创build一个空引用,而不需要引用一个空指针,所以空引用只存在于未定义行为的范围内。 由于您的编译器可能会假定您没有触发未定义的行为,因此可以假定不存在空引用 (即使它将轻松发出生成空引用的代码!)。 因此,它看到了if()
条件,得出结论,它不可能是真的,只是扔掉整个if()
语句。 随着链接时间优化的引入,以一种可靠的方式检查空引用已经变得不可能了。
TL; DR:
空引用有些可怕的存在:
他们的存在似乎是不可能的(按标准)
但它们存在(=由生成的机器码),
但是如果它们存在就不能看到它们(=你的尝试将被优化掉),
但是他们也许会不知不觉地杀了你(=你的程序在奇怪的地方崩溃了,或者更糟)。
你唯一的希望是他们不存在(=编写你的程序不创build它们)。
我希望不会来困扰你!