c++ - 将基本类型数组中的内存重用于不同(但仍然是基本)类型数组是否合法
问题描述
这是关于内存重用的另一个问题的后续。由于最初的问题是关于特定实现的,因此答案与该特定实现有关。
所以我想知道,在符合要求的实现中,为提供的不同类型的数组重新使用基本类型数组的内存是否合法:
- 两种类型都是基本类型,因此具有平凡的 dtor 和默认 ctor
- 两种类型具有相同的尺寸和对齐要求
我以以下示例代码结束:
#include <iostream>
constexpr int Size = 10;
void *allocate_buffer() {
void * buffer = operator new(Size * sizeof(int), std::align_val_t{alignof(int)});
int *in = reinterpret_cast<int *>(buffer); // Defined behaviour because alignment is ok
for (int i=0; i<Size; i++) in[i] = i; // Defined behaviour because int is a fundamental type:
// lifetime starts when is receives a value
return buffer;
}
int main() {
void *buffer = allocate_buffer(); // Ok, defined behaviour
int *in = static_cast<int *>(buffer); // Defined behaviour since the underlying type is int *
for(int i=0; i<Size; i++) {
std::cout << in[i] << " ";
}
std::cout << std::endl;
static_assert(sizeof(int) == sizeof(float), "Non matching type sizes");
static_assert(alignof(int) == alignof(float), "Non matching alignments");
float *out = static_cast<float *>(buffer); // (question here) Declares a dynamic float array starting at buffer
// std::cout << out[0]; // UB! object at &out[0] is an int and not a float
for(int i=0; i<Size; i++) {
out[i] = static_cast<float>(in[i]) / 2; // Defined behaviour, after execution buffer will contain floats
// because float is a fundamental type and memory is re-used.
}
// std::cout << in[0]; // UB! lifetime has ended because memory has been reused
for(int i=0; i<Size; i++) {
std::cout << out[i] << " "; // Defined behaviour since the actual object type is float *
}
std::cout << std::endl;
return 0;
}
我添加了注释,解释了为什么我认为这段代码应该有定义的行为。恕我直言,一切都很好,并且符合 AFAIK 标准,但我无法找到此处标记的问题是否有效。
浮点对象确实重用了 int 对象的内存,因此 int 的生命周期在浮点数的生命周期开始时结束,因此 stric-aliasing 规则应该不是问题。数组是动态分配的,因此对象( int和 floats)实际上都是在operator new
. 所以我认为一切都应该没问题。
但由于它允许在现代 C++ 中通常不赞成的低级对象替换,我必须承认我有一个疑问......
所以问题是:上面的代码是否调用了 UB,如果是,在哪里以及为什么?
免责声明:我建议不要在可移植代码库中使用此代码,这确实是一个语言律师问题。
解决方案
int *in = reinterpret_cast<int *>(buffer); // Defined behaviour because alignment is ok
正确的。但可能不是你所期望的。[expr.static.cast]
“pointer to
cv1 void
”类型的纯右值可以转换为“pointer tocv2 T
”类型的纯右值,其中T
是一个对象类型,cv2
与 cv-qualification 相同或大于 cv-qualificationcv1
。如果原始指针值表示A
内存中某个字节的地址,并且A
不满足 的对齐要求T
,则生成的指针值未指定。否则,如果原始指针值指向一个 objecta
,并且有一个b
类型的对象T
(忽略 cv 限定)可以与 进行指针互转换a
,则结果是指向 的指针b
。否则,指针值不会因转换而改变。
在 处没有int
也没有任何指针可相互转换的对象buffer
,因此指针值不变。in
是指向int*
原始内存区域的类型的指针。
for (int i=0; i<Size; i++) in[i] = i; // Defined behaviour because int is a fundamental type: // lifetime starts when is receives a value
是不正确的。[介绍对象]
当隐式更改联合的活动成员或创建临时对象时,对象由定义、new 表达式创建。
明显缺席的是作业。没有int
创建。事实上,通过消除,in
是一个无效的指针,而取消引用它是 UB。
后面的float*
也都跟随为UB。
new (pointer) Type{i};
即使没有通过正确使用创建对象的所有上述UB,也不存在数组对象。(不相关的)对象恰好在内存中并排。这意味着与结果指针的指针算术也是UB。[expr.add]
当具有整数类型的表达式被添加到指针或从指针中减去时,结果具有指针操作数的类型。如果表达式
P
指向具有元素x[i]
的数组对象x
的n
元素,则表达式P + J
和J + P
(其中 J 的值为 j)指向(可能是假设的)元素x[i+j] if 0 ≤ i+j ≤ n;
,否则,行为未定义。同样,表达式P - J
指向(可能是假设的)元素x[i−j] if 0 ≤ i−j ≤ n;
,否则,行为未定义。
假设元素是指一个过去的(假设的)元素。请注意,指向恰好与另一个对象位于相同地址位置的一个结束元素的指针并不指向该另一个对象。
推荐阅读
- python - 简化 groupby 流程
- variables - 为什么不能使用变量代替按钮名称来使用按钮的属性?
- python - 将 Pandas 数据框附加到 csv 文件顶部而不加载 csv 文件内容
- python - 如何从python中的数据表中选择除一(或两)之外的所有列
- python - 使用输入命令选择网站地址
- python - 如何修复不存在的在线 Jython 错误
- javascript - 使用事件时对此进行分类
- excel - 基于条件的Excel条件格式
- javascript - 数据验证错误“无法读取未定义的属性‘拆分’”
- r - R中的Plot()函数:如何将默认箱线图更改为点图?