内存地址的数字表示与对齐之间的关系?

Relation between numeric representation of memory address and alignment?

示例:

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++中为真标准? 如果不是,那么在实践中呢?

在C++标准中,

Objects declared as characters (char) shall be large enough to store any member of the implementation’s basic character set.

The fundamental storage unit in the C++ memory model is the byte. A byte is at least large enough to contain any member of the basic execution character set (2.3) and the eight-bit code units of the Unicode UTF-8 encoding form and is composed of a contiguous sequence of bits, the number of which is implementation defined.

Every byte has a unique address.

一个uint_8不一定是一个字节。而且一个字节不一定是8位

[1]&[2]的结果是否保证为0?

假设 Align8Type 有一个 8 字节对齐的地址:

[1] 是:根据前面假设的定义。

[2] 是的,即使字节大小可能大于 uint_8,假设 Align8Type 有一个地址 8 字节对齐,地址将是 8 的倍数。(uint_8 更小或等于一个字节)

[3]在c++标准中是否保证为真?

否:dist return 两个指针之间的 uint_8 距离,而不是地址距离。

已编辑:

编辑以回答重新定义的问题。

该标准不保证指针的表示 [注 1]。指针的值不一定直接映射到连续的整数,指向具有不同对齐方式的类型的指针也不一定具有相同的表示形式。所以以下任意一项都是可能:

  1. Segment/offset表示,其中段号占据指针表示的low-order位。

  2. Pre-aligned 表示,其中从表示中删除已知对齐的对象地址的 low-order 0。

  3. 标记表示,其中指向某些对象类型的指针的 low-order 位用于标识该类型的一个方面,不参与地址解析。 (这方面的一个例子是 hardware-assisted garbage-collection 架构,其中指向大到足以成为指针的类型的指针的低阶位被重新用作 GC 标志。)

  4. 子字寻址表示,其中底层硬件是 word-addressed(一个字比 8 位长得多),但是硬件或软件解决方案可用于字节寻址,其中一个字节指针由一对字地址/子字偏移组成。在这种情况下,字节指针将大于字指针,这是标准允许的。

我确定还有其他可能性。

一个对齐必须是 2 的幂,但不能保证存在多个对齐。所有类型都完全有可能对齐 1。因此很可能在给定的体系结构上不可能有意义地定义 Align8Type.

综上所述,我的解读是:

  1. std::uintptr_t(&align8) & 3 == 0

    错误。即使 Align8Type 是可定义的,也不能保证 Align8Type*std::uintptr_t 的转换是一个可以被 8 整除的数字。例如,在 32 位字寻址的机器上,底层硬件地址 mod 8 可以是 0、2、4 或 6。

  2. dist(nullptr, &align8) & 3 == 0

    错误。从指向对象的指针中减去 nullptr 是未定义的行为。 (§5.7/5:“除非两个指针都指向同一数组对象的元素,或者指向数组对象最后一个元素的指针,否则行为未定义。”)

  3. reinterpret_cast<Align8Type*>(static_cast<std::uint8_t*>(nullptr) + dist(nullptr, &align8)) == &align8

    错误。首先,根据 2.,dist 的调用是未定义的行为。其次,将该值添加到空指针是未定义的行为。

    Round-trip T1*T2* 并返回到 T1* 的转换是有保证的,前提是 T2 的对齐要求不如 T1 (§5.2.10/7)。在这种情况下,T1Align8TypeT2uint8_t,并且对齐限制可能成立,所以如果不是因为算术的未定义行为,这将工作。也就是说,您可以将 &align8 转换为 uint8_t*,然后将其转换回 Align8Type。您甚至可以将整数 0 添加到中间 uint8_t* 指针,但不能添加其他整数。


这些身份在实践中有效吗?他们可能在 8 位 byte-addressed 2 的补码机上使用 C++ 实现,这很常见(比上面提到的理论野兽更常见,从统计学上讲,它们与独角兽一样常见)。但从技术上讲,它们会渲染您的代码 non-portable。我不知道积极的优化会对第 2 点和第 3 点中提到的 UB 产生什么影响,所以我不建议在生产代码中冒险。


备注:

  1. §3.9.2/3:

    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 ]

    我转载了该注释,因为它很有趣:为了理解地址作为整数的表示,您必须了解底层机器的寻址结构(暗示它可能不像连续的一样简单整数序列)。