指针相等是否意味着整数相等?

6
对于 int *a, int *ba == b 是否意味着 (intptr_t)a == (intptr_t)b?我知道在现代X86 CPU上是正确的,但C标准、POSIX或其他标准是否保证了这一点?

@AlexG 仅暗示整数相等是不够的。如果指针指向同一对象,则“==”对指针为真。但如果我们有同一对象的两个不同内存映射,则可能会有不同的解释。 - Eugene Sh.
1
在使用分段内存模型的旧Intel 80286处理器上,这是不成立的。例如,(int *)0x00010001 == (int *)0x00000011 将会是真的。 - Lee Daniel Crocker
1
@AlexG,EugeneSh。是的,“整数相等性”标题术语有点令人困惑,但从帖子中应该清楚我指的是转换为整数的指针的相等性。 - Manuel Jacob
@LeeDanielCrocker 嗯,那很奇怪。即使考虑到我的第一条评论,我也不认为它在现实中可能存在... - Eugene Sh.
1
当时还没有 uintptr_t,那是最近才有的东西。我们只有 intlong。我不记得我的编译器如何处理远指针到长整型的转换。随着年龄的增长,记忆也在逐渐消逝... - Lee Daniel Crocker
显示剩余13条评论
2个回答

10

这并不由C标准保证。(本回答未涉及POSIX或其他标准中关于intptr_t的说明。)C标准(2011年,草案N1570)对于intptr_t的说明如下:

7.20.1.4 1 下列类型指定了一种带有以下特性的有符号整数类型:任何有效的指向void的指针都可以转换为此类型,然后再转换回指向void的指针,结果将与原始指针比较相等:intptr_t

作为一个理论证明,一个反例是一个有24位地址的系统,其中高8位未使用,但可用的整数类型是8位、16位和32位。在这种情况下,C语言实现可以将 intptr_t 定义为32位整数,并通过将24位地址复制到32位整数中并忽略高8位来将指针转换为 intptr_t。这些位可能是以前遗留下来的。当将 intptr_t 值转换回指针时,编译器会丢弃高8位,从而得到原始地址。在这个系统中,当对指针 ab 进行指针比较 a == b 时,编译器只比较地址的24位。因此,如果 ab 指向同一个对象,则表达式 a == b 将为真,但 (intptr_t) a == (intptr_t) b 可能由于被忽略的高位而导致求值为假。(注意,严格来说,ab 应该是指向 void 的指针或在转换为 intptr_t 之前应该先转换为指向 void 的指针。)

另一个例子是使用一些基址和偏移地址的系统。在这个系统中,指针可能由16位组成,其中前16位指定了某个基址,后16位指定了一个偏移量。基址可能是64字节的倍数,因此由 baseoffset 表示的实际地址为 base•64 + offset。在这种系统中,如果指针 a 的基址为2,偏移为10,则表示与指针 b 基址为1,偏移为74的指针指向同一地址。比较指针时,编译器会计算每个指针的 base•64 + offset 并比较结果,因此,a == b 为 true。但是,当转换为 intptr_t 时,编译器可能只需复制位,从而为 (intptr_t) a 生成131,082(2•65536 + 10),为 (intptr_t) b 生成65,610(1•65536 + 74)。然后,(intptr_t) a == (intptr_t) b 为 false。但是将 intptr_t 转换回指针类型仍然保持原样,因为编译器将再次简单地复制位。


1
“将intptr_t转换回指针类型仍会产生原始指针”的规则的详细信息 - 嗯,C规范不是说往返指针-整数-指针只会产生“结果将与原始指针相等”,甚至在这个例子中也不一定是相同的原始位模式吗? - chux - Reinstate Monica
1
关于您的第二个例子,Intel 8086的编译器通常有两种32位指针,它们表示物理地址seg*16 + offset,每种指针都有一个限制:所有标识同一对象部分的“大”指针必须指定相同的段(单个对象的大小限制为65,520字节),而所有“巨大”的指针必须在0-15范围内具有偏移量。尽管如此,如果您没有先我一步,您对于指针比uintptr_t更窄的系统的示例与我的想法相符。 - supercat
1
@chux:对于第二个例子,如果编译器将两个具有不同位模式的指针视为相等,并且如果转换为/从整数类型保留了位模式,则将具有不同位模式的两个相等指针进行转换将产生不同的整数,但是将这些不同的整数重新转换为指针将产生与原始指针相同但比较相等的不同位模式。 - supercat
@supercat True,但是如果“转换为/从整数类型保留位模式”的方式只是“到整数”,并且从整数返回指针的转换缺乏相同的位模式(尽管这听起来像独角兽),则结果指针仍将比较相等,但不同于原始指针。在两个转换中,_相同的位模式_或_not属性_是独立于实现的。这个答案似乎暗示了最后一句话。 - chux - Reinstate Monica
1
@supercat: 指针之间使用==不能保证所有非NULL操作数的结果为真。指向不同对象的指针必须视为不同(除了指向数组结尾后一个位置的指针可能与指向其紧随其后的对象的指针相等)。 - Eric Postpischil
显示剩余7条评论

1
与其试图指定常见平台上应当遵守的所有保证,标准更倾向于避免强制执行任何可能在任何可想象的平台上都很昂贵或有问题的保证,除非它们非常有价值,以至于可以证明任何可能的成本都可以承受。作者(当时合理地)预期,能够提供更强保证的平台上的优质编译器将这样做,因此没有必要明确规定编译器无论如何都会执行的事情。
如果看一下标准实际提供的保证,那就太弱了。它规定将void*转换为uintptr_t,然后再转回void*将产生一个指针,该指针可以与原始指针进行比较,并且比较将报告两个指针相等。它没有说明如果代码对往返转换后的指针做其他任何操作会发生什么。符合规范的实现可以按照忽略整数值(除非它是空指针常量)的方式执行整数到指针的转换,并产生一些任意位模式,不匹配任何有效或空指针,然后让其指针相等运算符在任一操作数持有该特殊位模式时报告“相等”。当然,没有优质实现会以这种方式行事,但标准中没有任何禁止它的内容。
在没有优化的情况下,可以合理地假设,在任何使用与 uintptr_t 相同大小的“线性”指针的平台上,质量编译器将处理指针到 uintptr_t 的转换,以使相等的指针转换为相同的数值,并且如果给定 uintptr_t u;,则如果 u==(uintptr)&someObject,则可以使用 *(typeOfObject*)u 来访问 someObject,至少在 someObject 的地址被转换为 uintptr_t 的时间和下一次通过其他方式访问 someObject 之间,而不考虑 u 如何获得其值。不幸的是,有些编译器太原始,无法识别将地址转换为 uintptr_t 将暗示从 uintptr_t 形成的指针可能能够识别相同的对象。

如果 u == (uintptr_t) &someObject,那么 * (typeOfObject *) u 必然等于 &someObject,因此可以使用它来访问 someObject,这是由于往返转换规则的原因。(uintptr_t) &someObject 的结果是一个值,而不是 lvalue 或对象。如果 u 等于它,则 u 的值与该值相同——一个值仅由其自身特征化;它并不因为它是某个对象的值而有所区别。因此 (typeOfObject *) u(typeOfObject *) (uintptr_t) &SomeObject 相同,并且往返转换规则表明它等于 &SomeObject - Eric Postpischil
@EricPostpischil:两个指针比较相等并不意味着两者都可以用于访问同一个对象。指针-整数-指针的往返转换可能会产生一些不是指向对象的指针(例如,如果原始指向内存中最后一个对象的正好之后)。如果原始指向对象的指针,则转换的结果也应该是指向对象的指针,但我在标准中没有看到实际说明这一点。 - supercat
根据6.5.6 8中的最后一句,禁止使用((int volatile*)(uintptr_t)&y)[1],因为((int volatile*)(uintptr_t)&y)是指向y的指针(更糟糕的是,它是指向y之前的数组中的最后一个元素的指针),所以表达式评估为y[1],即*(y+1),这是被禁止的评估。 - Eric Postpischil
@EricPostpischil:我的观点是标准没有指定任何情况下整数到指针的转换将保证产生一个指向对象的指针。标准语言在“对象”和地址的定义上有些笨拙。例如,给定struct s1 {int x1[2][1],y1;}; struct s2 { int x2[1][2],y2;}; union { struct s1 v1; struct s2 v2;} u;u.v1.y1u.v1.y2是“同一对象”吗? - supercat
是的,它们是同一个对象(假设它们降落在同一个位置)。根据3.15,对象是存储区域。我不同意;往返要求指定从指针转换而来的intptr_t必须产生一个等于原始指针的指针,这必然是指向同一个对象的指针(除了最后一个元素之外的问题)。 - Eric Postpischil
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接