通过重复使用的非“const”名称修改动态分配的“const”对象是否合法
Is it legal to modify a dynamically-allocated `const` object through a re-used non-`const` name?
请考虑以下程序:
#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
正确性呢?
dr:是的,这是未定义的行为。否,编译器不会对其进行诊断。
通常,编译器不会(有时不能)诊断 UB。const
正确性违规的更明显的例子实际上是格式不正确的,可以诊断:
#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;
// ^
但是,除此之外,它不会阻止您执行明显违反const
正确性的行为:
#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?
}
以下是发生的情况:
- 创建具有自动存储持续时间的
int
,初始化为0
。 - 名称
x
引用此对象。 - 创建具有动态存储持续时间的
const int
,重新使用int
的存储。 int
的寿命结束于1,2。-
x
现在指的是const int
3。 - 虽然名字
x
的类型是int
,但它现在指的是一个const int
,所以赋值是未定义的4。
这是一个有趣的漏洞,你可以用它来"绕过"const
正确性,只要原始int
不在只读内存中,它甚至可能不会导致崩溃。
然而,它仍然没有定义,虽然我看不出可以执行哪些优化来破坏作业和随后的阅读,但你肯定对各种意想不到的肮脏持开放态度,比如你后花园的自发火山或你辛苦赚来的所有代表都被转换成英镑并存入我的银行账户(谢谢!
脚注1
[C++11: 3.8/1]:
[..]类型T
对象的生存期在以下时间结束:
- 如果
T
是具有非平凡析构函数 (12.4) 的类类型,则析构函数调用将启动,或者- 对象占用的存储被重用或释放。
脚注2
请注意,我不必在int
对象上显式调用"析构函数"。这主要是因为这样的对象没有析构函数,但即使我选择了一个简单的类T
而不是int
,我可能也不需要显式析构函数调用:
[C++11: 3.8/4]:
程序可以通过重用对象占用的存储或使用非平凡析构函数显式调用类类型的对象的析构函数来结束任何对象的生存期。对于具有非平凡析构函数的类类型的对象,程序不需要在重用或释放对象占用的存储之前显式调用析构函数;但是,如果没有显式调用析构函数,或者未使用 delete-expression (5.3.5) 来释放存储,则不应隐式调用析构函数,并且依赖于析构函数产生的副作用的任何程序都具有未定义的行为。
脚注3
[C++11: 3.8/7]:
如果在对象的生存期结束后,在重新使用或释放对象占用的存储之前,在原始对象占用的存储位置创建一个新对象、指向原始对象的指针、引用原始对象的引用或原始对象的名称将自动引用新对象,并且, 一旦新对象的生存期开始,可用于操作新对象,如果:
- 新对象的存储完全覆盖原始对象占用的存储位置,并且
- 新对象与原始对象的类型相同(忽略顶级 CV 限定符),并且
原始对象的类型不是常量限定的- ,如果是类类型,则不包含任何类型为常量限定或引用类型的非静态数据成员,并且
原始对象是类型T
的最派生对象 (- 1.8),新对象是类型
T
的最派生对象(即,它们不是基类子对象)。[..]
脚注4
[C++11: 7.1.6.1/4]:
除了可以修改任何声明为 MUTABLE (7.1.1) 的类成员之外,任何在const
对象的生存期 (3.8) 期间修改其尝试都会导致未定义的行为。[..]
(以下示例与您的代码片段相似,但不完全相同。
- 如果C++类在类方法中具有动态分配,但没有构造函数/析构函数或任何非静态成员,那么它仍然是POD类型吗
- 使用动态分配的数组会导致代码分析发出虚假的C6386缓冲区溢出警告
- 在c++中使用动态分配的问题
- 使用递归模板动态分配的多维数组
- 对具有动态分配的内存和析构函数的类对象的引用
- 我有一个对象,它将在整个程序的持续时间内实例化,但一个类成员不会,我应该动态分配它吗?
- 访问动态分配列表中的元素
- 为什么 std::equal_to会导致动态分配?
- 调用析构函数以释放动态分配的内存
- 动态分配Q_Property变量
- 在 C++ 中搜索动态分配的数组中的出现次数
- 动态分配的聊天数组打印缺失的数据和空
- 在对象指针上调用 Delete 是否会递归删除其动态分配的成员
- 如果 const 不分配内存,为什么我可以获取 const 的地址?
- 如何为const char double指针(使用新的)动态分配内存
- 如何在不违反 const 限制的情况下动态分配模板类中的空间
- 使用malloc()动态分配const char字符串的内存
- 如何创建动态分配的const对象数组,但要为其赋值
- 通过重复使用的非“const”名称修改动态分配的“const”对象是否合法
- 从动态分配的解引用指针默认初始化非const引用函数参数是否会造成内存泄漏?