133

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

int &nullReference = *(int*)0;

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

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

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

4 回答 4

85

引用不是指针。

8.3.2/1:

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

1.9/4:

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

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

于 2010-12-06T08:46:53.770 回答
47

答案取决于你的观点:


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


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

然而,优化依赖于编译器来证明未定义的行为,这可能是不可能的。考虑文件中的这个简单函数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()。答案是调试:空引用可以像空指针一样传播和增殖。如果您不知道可能存在空引用,并学会避免创建它们,您可能会花费相当长的时间试图弄清楚为什么您的成员函数在尝试读取普通旧成员时似乎崩溃int(答案:实例在成员的调用中是空引用,this空指针也是如此,并且您的成员被计算为位于地址 8)。


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

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

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


TL;博士:

空引用有点可怕:

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

我希望这不会困扰你!

于 2017-02-28T22:36:05.373 回答
9

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.
于 2014-12-20T18:32:09.047 回答
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;
  }
}
于 2012-11-22T02:30:07.757 回答