我的目标是分配单个内存块,然后将其划分为不同类型的较小数组。我对此处编写的代码有几个问题:
#include <iostream>
#include <cstdint>
#include <cstdlib>
int main() {
constexpr std::size_t array_count = 5;
constexpr std::size_t elements_size = sizeof(std::uint32_t) + sizeof(std::uint16_t);
void* const pv = std::calloc(array_count, elements_size);
//Partition the memory. p32 array starts at pv, p16 array starts after the 20 byte buffer for the p32 array.
std::uint32_t* const p32 = (std::uint32_t *) pv;
std::uint16_t* const p16 = (std::uint16_t *)((char *) pv + sizeof(std::uint32_t) * array_count);
//Initialize values.
for(std::size_t i = 0; i < array_count; ++i) {
p32[i] = i;
p16[i] = i * 2;
}
//Read them back.
for(std::size_t i = 0; i < array_count; ++i) {
std::cout << p32[i] << std::endl;
std::cout << p16[i] << std::endl;
std::cout << std::endl;
}
std::free(pv);
}
- 此代码是否违反了 c++ 的严格别名规则?从 malloc 或 calloc 调用中转换指针时,我无法找到有关别名的资源。 p32 和 p16 指针不应重叠。
- 如果我颠倒两个数组的位置,其中 p16 从 pv 开始,而 p32 与 pv 有 10 字节的偏移量,这可能会导致段错误,因为 uint32_t 与 4 字节边界对齐 pv + 10 可能在 2 字节上边界,对吗?
- 这个程序是否不安全,或者引入了我通常遗漏的任何未定义行为?我在本地机器上得到了预期的输出,但这当然并不意味着我的代码是正确的。
最佳答案
是的,程序是UB。当您这样做时:
for(std::size_t i = 0; i < array_count; ++i) {
p32[i] = i;
p16[i] = i * 2;
}
p32
或 p16
没有指向 uint32_t
或 uint16_t
对象。 calloc
只是给你字节,而不是对象。您不能只是将 reinterpret_cast
对象变成存在。最重要的是,索引仅为数组定义,p32
不指向数组。
要使其定义明确,您必须创建一个数组对象。但是,数组的 placement-new 是 broken ,因此您需要手动初始化一堆 uint32_t
,例如:
auto p32 = reinterpret_cast<uint32_t*>(pv);
for (int i = 0; i < array_count; ++i) {
new (p32+i) uint32_t; // NB: this does no initialization, but it does satisfy
// [intro.object] in actually creating an object
}
这会遇到一个单独的问题:CWG 2182 .现在我们有 array_count
uint32_t
,但是我们没有 uint32_t[array_count]
所以索引仍然是 UB。基本上,完全按照标准的 C++ 来编写它是不可能的。另见 my similar question on the topic .
也就是说,在野外执行此操作的代码量是巨大的,并且每个实现都将允许您执行此操作。
关于c++ - 分区内存时的严格别名和对齐问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45617140/