通过重用的非`constst`名称修改动态分配的`const`对象是否合法?

时间:2014-04-02 10:08:12

标签: c++ c++11 const language-lawyer const-correctness

考虑以下计划:

#include <iostream>

int main()
{
   int x = 0;
   const int* px = new (&x) const int(0);
   x = 1;
   std::cout << *px;  // 1?
}

compiles under GCC 4.8(并产生“预期”输出),但我怀疑它完全是UB,因为动态对象的类型为const intwhich remains part of the type)。但是,如果是这样,为什么编译器不能阻止我违反const - 正确性?

1 个答案:

答案 0 :(得分:6)

tl; dr:是的,它是未定义的行为。不,编译器没有诊断它。


通常,编译器不会(并且有时无法)诊断UB。更明显的const示例 - 正确性违规事实上格式错误can be diagnosed

#include <iostream>

int main()
{
   const int x = 0;
   x = 1;
   std::cout << x;
}

// g++-4.8 -std=c++11 -O2 -Wall -pedantic -pthread main.cpp && ./a.out
// main.cpp: In function 'int main()':
// main.cpp:6:6: error: assignment of read-only variable 'x'
//     x = 1;
//       ^

但除此之外,won't stop you from performing obvious violations of const-correctness

#include <iostream>

int main()
{
    const int x = 0;
    *const_cast<int*>(&x) = 1;
    std::cout << x;
}

// Output: 1

所以,回到你的代码片段,我不会对那里的编译器诊断有太多期待。

但是,您的代码 会调用未定义的行为。我们来看一下:

#include <iostream>

int main()
{
   int x = 0;
   const int* px = new (&x) const int(0);
   x = 1;
   std::cout << *px;  // 1?
}

以下是发生的事情:

  1. 使用自动存储持续时间创建int,初始化为0
  2. 名称x引用此对象。
  3. 使用动态存储时长创建const int,重新使用int存储空间。
  4. int的生命周期结束 1,2
  5. x现在引用const int 3
  6. 虽然名称x的类型为int,但它现在指的是const int,因此分配未定义 4
  7. 这是一个有趣的漏洞,你可以用它来绕过&#34; const - 正确性,只要原始int没有生活在只读内存中,它可能甚至不会导致崩溃。

    然而,它仍然未定义,虽然我无法看到可能会执行哪些优化可能会破坏作业和后续阅读,但您肯定会对各种意外的肮脏行为持开放态度,例如:你后花园里的自发火山或你所有来之不易的代表被转化为英镑并存入我的银行账户(谢谢!)。


    脚注1

      

    [C++11: 3.8/1]: [..] 类型为T的对象的生命周期在以下时间结束:

         
        
    • 如果T是具有非平凡析构函数(12.4)的类类型,则析构函数调用将启动,或者
    •   
    • 对象占用的存储空间被重用或释放。
    •   

    脚注2

    请注意,我没有必要明确调用&#34;析构函数&#34;在int对象上。这主要是因为这些对象没有析构函数,但即使我选择了一个简单的类T而不是int,我可能也不需要显式析构函数调用:

      

    [C++11: 3.8/4]:程序可以通过重用对象占用的存储或通过使用非平凡的析构函数显式调用类类型的对象的析构函数来结束任何对象的生命周期。 对于具有非平凡析构函数的类类型的对象,程序不需要在对象占用的存储被重用或释放之前显式调用析构函数; 但是,如果没有对析构函数的显式调用,或者如果没有使用delete-expression(5.3.5)来释放存储,那么不应该隐式调用析构函数和任何程序这取决于析构函数产生的副作用具有未定义的行为。

    脚注3

      

    [C++11: 3.8/7]: 如果在对象的生命周期结束之后,在重用或释放对象占用的存储之前,将在原始对象占用的存储位置创建一个新对象,< / strong>指向原始对象的指针,引用原始对象的引用,或原始对象的名称将自动引用新对象,并且一旦新对象的生命周期开始,可以用来操纵新对象,如果

         
        
    • 新对象的存储空间恰好覆盖原始对象占用的存储位置
    •   
    • 新对象与原始对象的类型相同(忽略顶级cv限定符),
    •   
    • 原始对象的类型不是const限定的,如果是类类型,则不包含任何类型为const限定的非静态数据成员或引用类型,并且
    •   
    • 原始对象是类型T的派生程度最高的对象(1.8),新对象是类型T的派生程度最高的对象(即,它们不是基类子对象)。 [..]
    •   

    脚注4

      

    [C++11: 7.1.6.1/4]:除了可以修改声明为mutable(7.1.1)的任何类成员之外,任何在其生命周期内修改const对象的尝试(3.8)都会导致未定义的行为。 [..]

    (示例与您的代码段类似,但不完全相同。)