例子:
std::ptrdiff_t dist(void* a, void* b)
{
return static_cast<std::uint8_t*>(b) - static_cast<std::uint8_t*>(a);
}
Align8Type align8; // alignof(Align8Type) == 8
std::uintptr_t(&align8) & 3; // [1]
dist(nullptr, &align8) & 3; // [2]
Align8Type* p = reinterpret_cast<Align8Type*>(static_cast<std::uint8_t*>(nullptr) + dist(nullptr, &align8));
assert(&align8 == p); // [3]
假设
std::uint8_t
支持 , [1] & [2] 的结果是否保证为 0 并且 [3] 在 c++ 标准中保证为真?如果不是,在实践中呢?
最佳答案
该标准不保证指针的表示 [注 1]。指针的值不一定直接映射为连续的整数,也不一定指向具有不同对齐方式的类型的指针具有相同的表示。因此,以下任何一种都是可能的:
我相信还有其他的可能性。
对齐必须是 2 的幂,但不能保证存在多个对齐。所有类型都有对齐方式 1 是完全可能的。所以很可能在给定的架构上不可能有意义地定义
Align8Type
.鉴于以上所有,我的解释:
std::uintptr_t(&align8) & 3 == 0
错误的。即使 Align8Type
是可定义的,不能保证Align8Type*
的转换至 std::uintptr_t
是一个可被 8 整除的数。例如,在 32 位字寻址机器上,底层硬件地址 mod 8 可以是 0、2、4 或 6。dist(nullptr, &align8) & 3 == 0
错误的。 nullptr
的减法从指向对象的指针是未定义行为。 (第 5.7/5 节:“除非两个指针都指向同一数组对象的元素,或者指向数组对象最后一个元素之后的元素,否则行为未定义。”)reinterpret_cast<Align8Type*>(static_cast<std::uint8_t*>(nullptr) + dist(nullptr, &align8)) == &align8
错误的。首先,按照 2.,调用 dist
是未定义的行为。其次,将该值添加到空指针是未定义行为。T1*
的往返转换至 T2*
并返回 T1*
只要满足 T2
的对齐要求,就可以保证不如 T1
严格(第 5.2.10/7 节)。在这种情况下,T1
是 Align8Type
和 T2
是 uint8_t
,并且对齐限制可能成立,因此如果不是由于算术的未定义行为,这将起作用。也就是说,你可以投 &align8
至 uint8_t*
然后将其转换回 Align8Type
.您甚至可以添加整数 0
到中间 uint8_t*
指针,但没有其他整数。这些身份在实践中是否有效?他们可能在 8 位字节寻址的 2 的补码机器上使用 C++ 实现,这很常见(比上面提到的理论野兽更常见,从统计上讲,它们与 unicorn 一样常见)。但从技术上讲,它们使您的代码不可移植。我不知道对第 2 点和第 3 点中提到的 UB 进行哪些积极的优化,所以我不建议在生产代码中冒险。
笔记:
The value representation of pointer types is implementation-defined.
第 5.2.10/4 节:
A pointer can be explicitly converted to any integral type large enough to hold it. The mapping function is implementation-defined. [ Note: It is intended to be unsurprising to those who know the addressing structure of the underlying machine. —end note ]
我复制了这个注释,因为它很有趣:为了理解地址作为整数的表示,你必须理解底层机器的寻址结构(这意味着它可能不像连续的整数序列那么简单)。
关于c++ - 内存地址的数字表示与对齐之间的关系?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34737737/