通过重用的非`const'名称修改动态分配的`const`对象是否合法吗? [英] Is it legal to modify a dynamically-allocated `const` object through a re-used non-`const` name?

查看:159
本文介绍了通过重用的非`const'名称修改动态分配的`const`对象是否合法吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

请考虑以下程序:

  #include< iostream> 

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

编译GCC 4.8 (并产生预期输出),但我怀疑它是完全UB,因为动态对象类型 const int 仍保留在类型中)。但是,如果是这样,为什么编译器不会阻止我违反 const -correctness?

方案

tl; dr:是的,它是未定义的行为。






通常,编译器不会有时不能)诊断UB。 const -correctness违反的更明显的例子实际上是错误的

  #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:在函数'int main()':
// main.cpp:6:6:错误:只读变量'x'
// x = 1;
// ^

但除此之外,不会阻止您执行明显违反 const -correctness 的行为:

  #include< iostream> 

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

//输出:1

回到你的代码片断,我不会期望太多的编译器诊断的方式。



仍然,你的代码 invoke undefined行为。让我们检查一下:

  #include< iostream> 

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

以下是发生的情况:


  1. 使用自动存储持续时间创建 int ,初始化为 0 。 / li>
  2. x 是指此物件。

  3. A const int 是使用动态存储持续时间创建的,并重新使用 int 的存储。

  4. code> int 的生命周期结束 1,2

  5. x 现在指的是 const int 3

  6. 虽然名称 x 有类型 int ,它现在指的是 const int

    这是一个有趣的漏洞,你可以用来绕过 const -correctness,只要原来的 int 不住在只读内存中,它可能不会会导致崩溃。



    但是,它仍然未定义,虽然我不能看到可能执行的可能会中断分配和后续读取的优化,你绝对开放各种意想不到的怠惰,如你的后花园里的自发火山或所有辛苦赚来的代表被转换成英镑并存入我的银行帐户(感谢!)。






    脚注1




    :3.8 / 1]: [..] 类型 T 的对象的生命周期结束:




    • 如果 T


    • / ul>



    脚注2



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


    [C ++ 11:3.8 / 4]:程序可以通过重用对象占用的存储器来结束任何对象的生命周期,或者通过显式调用类类型的对象的析构函数,微不足道的析构函数。 对于具有非平凡析构函数的类类型的对象,程序不需要在对象占用的存储之前显式调用析构函数或释放; 但是,如果没有显式调用析构函数,或者如果没有使用delete-expression(5.3.5)来释放存储,则析构函数不应被隐式调用,任何程序




    脚注3


    b


    [C ++ 11:3.8 / 7]: 已经结束,并且在重新使用或释放​​对象所占用的存储之前,在原始对象占用的存储位置创建新对象,指向原始对象的指针,引用原始对象的引用对象或,原始对象的名称将自动引用新对象,并且一旦新对象的生命周期开始,即可用于操作新对象




    • 新对象的存储空间正好覆盖原始对象占用的存储位置,

    • 新对象与原始对象的类型相同(忽略顶级cv限定符),

    • 原始对象的类型不是const限定的,如果类类型不包含类型为const限定或引用类型的任何非静态数据成员,并且

    • 原始对象是类型 T 的最多派生对象(1.8),而新对象是类型 T (也就是说,它们不是基类子对象)。 [..]




    脚注4




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


    (类似于但不完全相同的示例as,your code snippet。)


    Consider the following program:

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

    It compiles under GCC 4.8 (and produces the "expected" output), but I suspect it's entirely UB because the dynamic object has type const int (which remains part of the type). But, then, if so why isn't the compiler stopping me from violating const-correctness?

    解决方案

    tl;dr: Yes, it's undefined behavior. No, the compiler doesn't diagnose it.


    In general, the compiler won't (and sometimes can't) diagnose UB. More obvious examples of const-correctness violation are in fact ill-formed and 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;
    //       ^
    

    But, other than that, it 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
    

    So, going back to your code snippet, I wouldn't expect too much in the way of compiler diagnostics there.

    Still, your code does invoke undefined behaviour. Let's examine it:

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

    Here's what happens:

    1. An int is created with automatic storage duration, initialised to 0.
    2. The name x refers to this object.
    3. A const int is created with dynamic storage duration, re-using the int's storage.
    4. The int's lifetime ends1, 2.
    5. x now refers to the const int3.
    6. Although the name x has type int, it's now referring to a const int, so the assignment is undefined4.

    This is an interesting loophole you can use to "get around" const-correctness and, as long as the original int didn't live in read-only memory, it probably won't even result in a crash.

    However, it's still undefined and although I can't see what optimisations may be performed that could break the assignment and subsequent read, you're definitely open to all sorts of unexpected nastiness, such as spontaneous volcanoes in your back garden or all your hard-earned rep being transformed into Pounds Sterling and deposited in my bank account (thanks!).


    Footnote 1

    [C++11: 3.8/1]: [..] The lifetime of an object of type T ends when:

    • if T is a class type with a non-trivial destructor (12.4), the destructor call starts, or
    • the storage which the object occupies is reused or released.

    Footnote 2

    Note that I did not have to explicitly call the "destructor" on the int object. This is mostly because such objects do not have a destructor, but even if I'd picked a simple class T rather than int, I may not have needed an explicit destructor call:

    [C++11: 3.8/4]: A program may end the lifetime of any object by reusing the storage which the object occupies or by explicitly calling the destructor for an object of a class type with a non-trivial destructor. For an object of a class type with a non-trivial destructor, the program is not required to call the destructor explicitly before the storage which the object occupies is reused or released; however, if there is no explicit call to the destructor or if a delete-expression (5.3.5) is not used to release the storage, the destructor shall not be implicitly called and any program that depends on the side effects produced by the destructor has undefined behavior.

    Footnote 3

    [C++11: 3.8/7]: If, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, a new object is created at the storage location which the original object occupied, a pointer that pointed to the original object, a reference that referred to the original object, or the name of the original object will automatically refer to the new object and, once the lifetime of the new object has started, can be used to manipulate the new object, if:

    • the storage for the new object exactly overlays the storage location which the original object occupied, and
    • the new object is of the same type as the original object (ignoring the top-level cv-qualifiers), and
    • the type of the original object is not const-qualified, and, if a class type, does not contain any non-static data member whose type is const-qualified or a reference type, and
    • the original object was a most derived object (1.8) of type T and the new object is a most derived object of type T (that is, they are not base class subobjects). [..]

    Footnote 4

    [C++11: 7.1.6.1/4]: Except that any class member declared mutable (7.1.1) can be modified, any attempt to modify a const object during its lifetime (3.8) results in undefined behavior. [..]

    (Examples follow that are similar to, but not quite the same as, your code snippet.)

    这篇关于通过重用的非`const'名称修改动态分配的`const`对象是否合法吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆