现在我们知道,越界 - 指针算术具有未定义的行为,如SO question中所述。
我的问题是:我们可以通过转换为std :: uintptr_t进行算术运算然后转换回指针来解决此类限制吗?是保证有效吗?
例如:
char a[5];
auto u = reinterpret_cast<std::uintptr_t>(a) - 1;
auto p = reinterpret_cast<char*>(u + 1); // OK?
真实世界的用法是优化偏移内存访问 - 而不是p[n + offset]
,我想做offset_p[n]
。
编辑要使问题更明确:
给定char数组的基指针p
,如果p + n
是有效指针,那么reinterpret_cast<char*>(reinterpret_cast<std::uintptr_t>(p) + n)
是否会保证产生相同的有效指针?
答案 0 :(得分:12)
不,uintptr_t
不能有意义用于避免在执行指针运算时出现未定义的行为。
首先,至少在C中,不能保证uintptr_t
甚至存在。要求是类型void*
的任何值都可以转换为uintptr_t
然后再转换回来,从而产生原始值而不会丢失信息。原则上,可能没有任何无符号整数类型足以容纳所有指针值。 (我认为这同样适用于C ++,因为C ++继承了大部分C标准库,并通过引用C标准来定义它。)
即使uintptr_t
确实存在,也无法保证uintptr_t
值上的给定算术运算与指针值上的相应操作完全相同。
例如,我曾在系统(Cray矢量系统,T90和SV1)上工作,字节指针在软件中实现。本地地址是64位地址,指的是64位字;字节寻址没有硬件支持。 char*
或void*
指针由一个字指针组成,其中3位偏移存储在未使用的高位中。整数和指针之间的转换只是复制位。因此,递增char*
会使其前进到指向内存中的下一个8位字节;递增通过转换uintptr_t
获得的char*
会使其前进到指向下一个64位字。
这只是一个例子。更一般地,指针和整数之间的转换是实现定义的,并且语言标准不保证这些转换的语义(在某些情况下,转换回指针除外)。
所以是的,您可以将指针值转换为uintptr_t
(如果该类型存在)并对其执行算术而不会有未定义的行为风险 - 但结果可能有意义,也可能没有意义。
在大多数系统上,指针和整数之间的映射更简单,你可能可以逃脱这种游戏。但是你最好直接使用指针算法,并且要小心避免任何无效操作。
答案 1 :(得分:4)
是的,这是合法的,但您必须 reinterpret_cast
完全相同的uintptr_t
值回char*
。
(因此,你打算做什么是非法的;也就是说,将不同的值转换回指针。)
5.2.10重新解释投射
4。指针可以显式转换为足以容纳它的任何整数类型。映射功能是 实施德音响定义
5。可以将整数类型或枚举类型的值显式转换为指针。转换的指针 到一个足够大的整数(如果在实现上存在任何这样的大小)并返回到相同的指针类型 将具有其原始价值;
(请注意,通常情况下,编译器无法知道您减去了一个,然后将其添加回来。)