引用未初始化的内存。未定义的行为?

时间:2019-06-08 04:57:16

标签: c++ language-lawyer

允许我在开始发言时说,出于明显的原因,我不建议您采用以下任何一种做法。但是,我今天对此进行了讨论,有些人坚持使用这样的引用作为未定义的行为。

这是一个测试用例:

#include <string>

struct my_object {
   int a          = 1;
   int b          = 2;
   std::string hi = "hello";
};

// Using union purely to reserve uninitialized memory for a class.
union my_object_storage {
   char dummy;
   my_object memory;
   // C++ will yell at you for doing this without some constructors.
   my_object_storage() {}
   ~my_object_storage() {}
} my_object_storage_instance;

// This is so we can easily access the storage memory through "I"
constexpr my_object &I = my_object_storage_instance.memory;

//-------------------------------------------------------------
int main() {
   // Initialize the object.
   new (&I) my_object();
   // Use the reference.
   I.a = 1;
   // Destroy the object (typically this should be done using RAII).
   I.~my_object();

   // Phase two, REINITIALIZE an object with the SAME reference.
   // We still have the memory allocated which is static, so why not?
   new (&I) my_object();
   // Use the reference.
   I.a = 1;  
   // Destroy the object again. 
   I.~my_object();
}

https://wandbox.org/permlink/YEp9aQUcWdA9YiBI

基本上,该代码将为结构保留静态内存,然后在main()中对其进行初始化。你为什么想这么做?它不是非常有用,您应该只使用一个指针,但这是一个问题:

给出此声明

constexpr my_object &I = my_object_storage_instance.memory;

是否正在定义对未初始化的内存未定义行为的引用?其他人告诉过我,但我正在尝试具体弄清是否是这种情况。在C ++标准中,我们看到以下段落:

  

应将引用初始化为引用有效的对象或函数。 [注意:特别是,空引用不能存在于定义良好的程序中,因为创建此类引用的唯一方法是将其绑定到通过解引用空指针而获得的“对象”,这会导致未定义的行为。 / p>

特别是“有效对象” ,可能会归结为:是一个尚未将其构造函数称为“有效”的对象吗?是什么使它会导致不确定的行为呢?真的会出现真正的副作用吗?

我的论据用于,被标记为未定义的行为是:

  • 编译器可能可以自由地将其视为有效对象,因为标准规定应将其视为有效对象,尤其是在分配过程中,尤其是如果插入了用于诊断的隐藏调试指令时, ,这肯定会导致不确定的行为。

我的论据反对这是未定义的行为,是:

  • 这没有取消引用的任何内容-该段指出,在引用的初始化期间,取消引用nullptr是未定义的。如果没有任何取消引用,它没有具体说明未定义的行为。
  • 悬挂引用是一回事,在许多情况下,在正常程序中都会出现。如果使用它们,它们只会导致未定义的行为。这类似于从悬挂参考开始。

同样,在实践中并不是很有用,因为有更好的方法来度过您的时间,但是比stackoverflow哪个地方更适合提出奇怪的问题和专家意见? :)

1 个答案:

答案 0 :(得分:6)

很好,您对引用的使用属于要求有活动对象的规则的明确例外。在[basic.life]中:

  

类似地,在对象的生存期开始之前但已分配了该对象将占用的存储空间之后,或者在对象的生存期结束之后以及重新使用或释放​​该对象占用的存储空间之前,所有glvalue可以使用指向原始对象的符号,但只能以有限的方式使用。

     

有关正在构造或破坏的对象,请参见[class.cdtor]。否则,此类glvalue会引用已分配的存储([basic.stc.dynamic.allocation]),并且使用不依赖于glvalue的值的属性是明确定义的。该程序在以下情况下具有未定义的行为:

     
      
  • glvalue用于访问对象,或
  •   
  • glvalue用于调用对象的非静态成员函数,或者
  •   
  • glvalue绑定到对虚拟基类([dcl.init.ref])的引用,或者
  •   
  • glvalue用作dynamic_­cast([expr.dynamic.cast])的操作数或typeid的操作数。
  •   
     

如果在对象的生存期结束之后且在重新使用或释放​​该对象所占用的存储之前,在原始对象所占用的存储位置上创建了一个指向原始对象的指针的新对象,引用原始对象的引用或原始对象的名称将自动引用新对象,并且一旦开始新对象的生存期,就可以使用它来操作新对象,如果:

     
      
  • 新对象的存储空间正好覆盖了原始对象所占据的存储位置,并且
  •   
  • 新对象的类型与原始对象相同(忽略顶级cv限定词),并且
  •   
  • 原始对象的类型不是const限定的,并且,如果是类类型,则不包含任何类型为const限定的非静态数据成员或引用类型,并且
  •   
  • 原始对象和新对象都不是潜在重叠的子对象([intro.object])。
  •   

因此,您的引用有效地指向分配的存储,这正是执行新放置并激活工会成员所需要的。

由于您创建的对象的动态(运行时)类型与您持有的引用的静态类型完全匹配,因此可以在放置新对象(第一个或第二个)之后使用它来访问新对象。