空引用是否可行?

时间:2010-12-06 08:37:30

标签: c++ reference null language-lawyer

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

int &nullReference = *(int*)0;

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

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

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

5 个答案:

答案 0 :(得分:65)

引用不是指针。

8.3.2 / 1:

  

引用应初始化为   引用有效的对象或功能。   [注意:特别是空引用   不能存在于明确的定义中   程序,因为唯一的方法   创造这样的参考将是   将它绑定到由...获得的“对象”   取消引用空指针   导致未定义的行为。如   在9.6中描述,参考不能   直接绑定到一个位域。 ]

1.9 / 4:

  

描述了某些其他操作   在本国际标准中   undefined(例如,。的效果)   取消引用空指针)

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

答案 1 :(得分:9)

答案取决于你的观点:

如果您根据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()将取消引用传递的指针,并假设它返回一个有效的引用,这在实践中恰好是一个空引用。调用成功,但是当您尝试使用该引用时,程序崩溃。希望。该标准允许任何事情发生,包括ping大象的出现。

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

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

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

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

TL; DR:

空引用有点可怕:

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

我希望这不会困扰你!

答案 2 :(得分:8)

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

为什么不在类中声明表示NULL的静态单例对象,如下所示并添加一个返回nullptr的cast-to-pointer运算符?

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

答案 3 :(得分: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.

答案 4 :(得分:0)

是:

#include <iostream>
#include <functional>

struct null_ref_t {
   template <typename T>
   operator T&() {
      union TypeSafetyBreaker {
         T *ptr;

         // see https://stackoverflow.com/questions/38691282/use-of-union-with-reference
         std::reference_wrapper<T> ref; 
      };

      TypeSafetyBreaker ptr = {.ptr = nullptr};

      // unwrap the reference
      return ptr.ref.get();
   }
};

null_ref_t nullref;


int main() {
   int &a = nullref;

   // Segmentation fault
   a = 4;
   return 0;
}

(与https://stackoverflow.com/a/42520025/11714860不同,该实现是内联的,并且实际上是优化器的,并且不需要函数调用)