内存地址的数字表示与对齐之间的关系?
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]。指针的值不一定直接映射到连续的整数,指向具有不同对齐方式的类型的指针也不一定具有相同的表示形式。所以以下任意一项都是可能:
Segment/offset表示,其中段号占据指针表示的low-order位。
Pre-aligned 表示,其中从表示中删除已知对齐的对象地址的 low-order 0。
标记表示,其中指向某些对象类型的指针的 low-order 位用于标识该类型的一个方面,不参与地址解析。 (这方面的一个例子是 hardware-assisted garbage-collection 架构,其中指向大到足以成为指针的类型的指针的低阶位被重新用作 GC 标志。)
子字寻址表示,其中底层硬件是 word-addressed(一个字比 8 位长得多),但是硬件或软件解决方案可用于字节寻址,其中一个字节指针由一对字地址/子字偏移组成。在这种情况下,字节指针将大于字指针,这是标准允许的。
我确定还有其他可能性。
一个对齐必须是 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
的调用是未定义的行为。其次,将该值添加到空指针是未定义的行为。
Round-trip T1*
到 T2*
并返回到 T1*
的转换是有保证的,前提是 T2
的对齐要求不如 T1
(§5.2.10/7)。在这种情况下,T1
是 Align8Type
而 T2
是 uint8_t
,并且对齐限制可能成立,所以如果不是因为算术的未定义行为,这将工作。也就是说,您可以将 &align8
转换为 uint8_t*
,然后将其转换回 Align8Type
。您甚至可以将整数 0
添加到中间 uint8_t*
指针,但不能添加其他整数。
这些身份在实践中有效吗?他们可能在 8 位 byte-addressed 2 的补码机上使用 C++ 实现,这很常见(比上面提到的理论野兽更常见,从统计学上讲,它们与独角兽一样常见)。但从技术上讲,它们会渲染您的代码 non-portable。我不知道积极的优化会对第 2 点和第 3 点中提到的 UB 产生什么影响,所以我不建议在生产代码中冒险。
备注:
§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 ]
我转载了该注释,因为它很有趣:为了理解地址作为整数的表示,您必须了解底层机器的寻址结构(暗示它可能不像连续的一样简单整数序列)。
示例:
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]。指针的值不一定直接映射到连续的整数,指向具有不同对齐方式的类型的指针也不一定具有相同的表示形式。所以以下任意一项都是可能:
Segment/offset表示,其中段号占据指针表示的low-order位。
Pre-aligned 表示,其中从表示中删除已知对齐的对象地址的 low-order 0。
标记表示,其中指向某些对象类型的指针的 low-order 位用于标识该类型的一个方面,不参与地址解析。 (这方面的一个例子是 hardware-assisted garbage-collection 架构,其中指向大到足以成为指针的类型的指针的低阶位被重新用作 GC 标志。)
子字寻址表示,其中底层硬件是 word-addressed(一个字比 8 位长得多),但是硬件或软件解决方案可用于字节寻址,其中一个字节指针由一对字地址/子字偏移组成。在这种情况下,字节指针将大于字指针,这是标准允许的。
我确定还有其他可能性。
一个对齐必须是 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
的调用是未定义的行为。其次,将该值添加到空指针是未定义的行为。Round-trip
T1*
到T2*
并返回到T1*
的转换是有保证的,前提是T2
的对齐要求不如T1
(§5.2.10/7)。在这种情况下,T1
是Align8Type
而T2
是uint8_t
,并且对齐限制可能成立,所以如果不是因为算术的未定义行为,这将工作。也就是说,您可以将&align8
转换为uint8_t*
,然后将其转换回Align8Type
。您甚至可以将整数0
添加到中间uint8_t*
指针,但不能添加其他整数。
这些身份在实践中有效吗?他们可能在 8 位 byte-addressed 2 的补码机上使用 C++ 实现,这很常见(比上面提到的理论野兽更常见,从统计学上讲,它们与独角兽一样常见)。但从技术上讲,它们会渲染您的代码 non-portable。我不知道积极的优化会对第 2 点和第 3 点中提到的 UB 产生什么影响,所以我不建议在生产代码中冒险。
备注:
§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 ]
我转载了该注释,因为它很有趣:为了理解地址作为整数的表示,您必须了解底层机器的寻址结构(暗示它可能不像连续的一样简单整数序列)。