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

时间:2016-01-12 07:25:35

标签: c++ pointers memory memory-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]&amp;的结果[2]保证为0并且[3]保证在c ++标准中为真? 如果不是,那么在实践中呢?

2 个答案:

答案 0 :(得分:4)

标准不保证指针的表示[注1]。指针的值不一定直接映射到连续的整数,也不一定指向具有不同对齐的类型的指针具有相同的表示。因此,以下任何一项可能

  1. 段/偏移表示,其中段号占用指针表示的低位。

  2. 预对齐表示,其中从表示中删除具有已知对齐的对象的地址的低位0。

  3. 标记表示,其中指向某些对象类型的指针的低位用于标识类型的一个方面,并且不参与地址解析。 (这方面的一个例子是硬件辅助垃圾收集架构,其中指向大到足以成为指针的类型的指针的低位比特被重新用作GC标志。)

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

  5. 我确信还有其他可能性。

    对齐必须是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的调用是未定义的行为。其次,将该值添加到空指针是Undefined Behavior。

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

    4. 这些身份在实践中 吗?他们可能在8位字节寻址2的补码机器上进行C ++实现,这很常见(比上面提到的理论上更常见,从统计学上讲,它们与独角兽一样常见)。但从技术上讲,它们使您的代码不可移植。我不知道积极优化可能会对第2点和第3点中提到的UB做什么,所以我不建议在生产代码中冒险。

      注意:

      1. §3.9.2/ 3:

          

        指针类型的值表示是实现定义的。

        §5.2.10/ 4:

          

        指针可以显式转换为足以容纳它的任何整数类型。映射功能是   实现定义。 [注意:对于那些了解底层机器的寻址结构的人来说,这并不奇怪。 - 后注]

        我重现了这个注释,因为它很有意思:为了理解一个地址作为整数的表示,你必须理解底层机器的寻址结构(暗示,它可能不像连续的序列那样简单。整数)。

答案 1 :(得分:0)

在C ++标准中,

  

声明为字符(char)的对象应足够大以存储   实现的基本字符集的任何成员。

     

C ++内存模型中的基本存储单元是字节。一个   byte至少足以包含basic的任何成员   执行字符集(2.3)和8位代码单元   Unicode UTF-8编码形式,由一个连续的序列组成   位数,其数量是实现定义的。

     

每个字节都有一个唯一的地址。

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

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

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

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

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

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

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

编辑:

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