使用 T[] 访问彼此相邻声明的两个 T 类型的对象是未定义的行为吗?
Is it undefined behaviour to access two objects of type T declared next to each other using T[]?
我最近观看了 Miro Kenjp 的 CppCon 演讲“不符合规范的 C++:委员会对你隐藏的秘密”。
在 28:30 (https://youtu.be/IAdLwUXRUvg?t=1710) 他说访问彼此相邻的双打是 UB,我不太明白为什么。有人可以解释为什么会这样吗?
如果是这种情况,那么它的 UB 肯定可以通过这种方式访问其他内容,例如:
int* twoInts = malloc(sizeof(int) * 2);
int secondInt = twoInts[1]; //Undefined behaviour?
不能保证变量会彼此相邻分配。
malloc
使用的内存区域可能与临时、局部、变量的内存区域不同。
局部变量可能没有存储在内存中。它们可以存储在寄存器中。
访问声明范围之外的数组元素是未定义的行为。
该数组可以在内存的末尾分配,因此在数组外部访问没有分配给它的内存。
局部变量可以定义在另一个内存段中,例如stack., 那跟动态分配的内存不一样
这是一个例子。
嵌入式系统具有片上存储器和“片上系统”(SOC)之外的存储器。片上内存速度更快,但数量更少。架构师将此内存分配给堆栈。 SOC外的内存比较慢,但是里面比较多,所以分配给了动态内存。
另一个例子:
操作系统支持虚拟内存。内存根据需要分页到硬盘驱动器上。操作系统为您的程序分配少量内存。少量内存会分配给栈,虚拟内存会分配给动态内存。
并非所有平台都是由连续内存构成的。内存位置也可以分配给硬件设备。
我想引用演示文稿中的一句话开始:
You are not programming against the CPU, you are programming against the abstract machine
你说:
He states that accessing doubles next to each other was UB
但是你的报价不完整。他具体说明了这个非常关键的事实:
... unless the objects are part of an array
malloc
是一个转移注意力的问题(和一袋另一套问题)。他的代码使用 new[]
所以 malloc
只是在给井下毒。
他在幻灯片中提到的具体问题是 buffer
上创建的 double
对象是由 std::uninitialized_default_construct_n
创建的,并且此方法不会创建双精度数组,而是创建多个在内存中连续的对象。他断言,在 C++ 标准(您正在编程的抽象机器)中,您不能将对象视为数组的一部分,除非您实际创建了对象数组。
作者试图提出的观点是 C++ 标准存在缺陷,并且没有严格符合标准的方法来创建灵活的数组(C++20 之前)。
参考代码(转载于图片后):
struct header
{
int size;
byte* buffer;
thing some;
};
constexpr size_t x = ...;
byte* buffer = new byte[x + n * sizeof(double)];
header* p = new (buffer) header{n, buffer};
uninitialized_default_construct_n(
reinterpret_cast<double*>(buffer + x), n);
double* data = reinterpret_cast<double*>(p->buffer + x);
data[0] = data[1] + data[2]; // <-- problem here
// because we never created an array of doubles
我最近观看了 Miro Kenjp 的 CppCon 演讲“不符合规范的 C++:委员会对你隐藏的秘密”。
在 28:30 (https://youtu.be/IAdLwUXRUvg?t=1710) 他说访问彼此相邻的双打是 UB,我不太明白为什么。有人可以解释为什么会这样吗?
如果是这种情况,那么它的 UB 肯定可以通过这种方式访问其他内容,例如:
int* twoInts = malloc(sizeof(int) * 2);
int secondInt = twoInts[1]; //Undefined behaviour?
不能保证变量会彼此相邻分配。
malloc
使用的内存区域可能与临时、局部、变量的内存区域不同。局部变量可能没有存储在内存中。它们可以存储在寄存器中。
访问声明范围之外的数组元素是未定义的行为。
该数组可以在内存的末尾分配,因此在数组外部访问没有分配给它的内存。局部变量可以定义在另一个内存段中,例如stack., 那跟动态分配的内存不一样
这是一个例子。
嵌入式系统具有片上存储器和“片上系统”(SOC)之外的存储器。片上内存速度更快,但数量更少。架构师将此内存分配给堆栈。 SOC外的内存比较慢,但是里面比较多,所以分配给了动态内存。
另一个例子:
操作系统支持虚拟内存。内存根据需要分页到硬盘驱动器上。操作系统为您的程序分配少量内存。少量内存会分配给栈,虚拟内存会分配给动态内存。
并非所有平台都是由连续内存构成的。内存位置也可以分配给硬件设备。
我想引用演示文稿中的一句话开始:
You are not programming against the CPU, you are programming against the abstract machine
你说:
He states that accessing doubles next to each other was UB
但是你的报价不完整。他具体说明了这个非常关键的事实:
... unless the objects are part of an array
malloc
是一个转移注意力的问题(和一袋另一套问题)。他的代码使用 new[]
所以 malloc
只是在给井下毒。
他在幻灯片中提到的具体问题是 buffer
上创建的 double
对象是由 std::uninitialized_default_construct_n
创建的,并且此方法不会创建双精度数组,而是创建多个在内存中连续的对象。他断言,在 C++ 标准(您正在编程的抽象机器)中,您不能将对象视为数组的一部分,除非您实际创建了对象数组。
作者试图提出的观点是 C++ 标准存在缺陷,并且没有严格符合标准的方法来创建灵活的数组(C++20 之前)。
参考代码(转载于图片后):
struct header
{
int size;
byte* buffer;
thing some;
};
constexpr size_t x = ...;
byte* buffer = new byte[x + n * sizeof(double)];
header* p = new (buffer) header{n, buffer};
uninitialized_default_construct_n(
reinterpret_cast<double*>(buffer + x), n);
double* data = reinterpret_cast<double*>(p->buffer + x);
data[0] = data[1] + data[2]; // <-- problem here
// because we never created an array of doubles