C语言中的指针比较,它们是有符号的还是无符号的?

18

嗨,我相信这一定是一个常见的问题,但我搜索时找不到答案。我的问题基本上涉及两个指针。我想比较它们的地址,并确定哪一个更大。我期望在比较过程中所有地址都是无符号的。这是真的吗?在C89、C99和C++之间是否有所不同?当我使用gcc编译时,比较是无符号的。

如果我要像这样比较两个指针:

char *a = (char *) 0x80000000; //-2147483648 or 2147483648 ?  
char *b = (char *) 0x1; 

那么a是否更大?这是标准保证的吗?

编辑以更新我正在尝试做什么。我有一个情况,我想确定如果有算术错误,它不会导致指针越界。现在我有数组的起始地址和结束地址。如果有错误并且指针计算错误,并且超出了数组内存的有效地址范围,我想确保不会发生访问冲突。我相信我可以通过比较由另一个函数返回的可疑指针,并确定它是否在数组的可接受范围内来防止这种情况发生。正负地址的问题与我在原始问题中讨论的内容有关。

我很感激迄今为止的答案。根据我的编辑,您会说我在gcc和msvc中所做的事情是未定义的行为吗?这是仅在Microsoft Windows上运行的程序。

这是一个过于简化的示例:

char letters[26];  
char *do_not_read = &letters[26];  
char *suspect = somefunction_i_dont_control(letters,26);  
if( (suspect >= letters) && (suspect < do_not_read) )  
    printf("%c", suspect);  
另外修改,阅读了AndreyT的答案后,发现它是正确的。因此我将按照以下方式做:
char letters[26];  
uintptr_t begin = letters;  
uintptr_t toofar = begin + sizeof(letters);  
char *suspect = somefunction_i_dont_control(letters,26);  
if( ((uintptr_t)suspect >= begin) && ((uintptr_t)suspect < toofar ) )
    printf("%c", suspect);  


谢谢大家!

5个回答

27

指针比较不能使用有符号或无符号。指针不是整数。

C语言(以及C++)仅对指向同一聚合体(结构体或数组)的指针定义相对指针比较。排序是自然的:指向数组中索引较小元素的指针较小。指向先前声明的结构体成员的指针较小。就是这样。

在C/C++中,你不能合法地比较任意指针。这种比较的结果未定义。如果您想比较指针中存储的地址的数值,那么您需要先手动将指针转换为整数值,这是您的责任。在这种情况下,您将不得不决定使用有符号或无符号整数类型(intptr_tuintptr_t)。根据您选择的类型,比较将是“带符号”还是“无符号”。


所以...这并没有考虑当你取两个指针的差值时,这显然是一个整数。 - Matt Joiner
2
@R.: 在空指针上进行加减运算无效的原因是由于操作符中的约束条件要求指针必须指向对象类型。这个限制对于关系运算符并不适用;例如,对于给定的type x[2];,表达式(void *)x < (void *)(x+1)是成立的。 - caf
指针之间的比较在数组和平凡可复制对象中定义。在数组内(以及超出末尾1个位置)也允许进行减法,其中它们被定义为返回操作数之间的元素数量。不幸的是,至少在形式上,在同一对象内减去指针 - 即使成员和/或指针是unsigned char *以使它们有意义 - 没有定义。在我找到的标准中,这是标准定义的不足之处:许多其他段落_暗示_这将被允许,但从未正式编码为定义行为... - underscore_d
当然,在C++中有std::less<>,它将该偏序扩展为完全序。 - Deduplicator
@supercat 有关于平凡复制/memcpy()的规则谈论到对象好像是一个"unsigned char数组",但我认为这只是对这样的复制做类比。我找不到问题号,但有一个核心问题寻求澄清它是否意味着一般隐喻。但现在,我看不到减去指针的任何规定,所以唯一可以称之为相当清楚的就是它没有定义...虽然在那个时候,我希望我们能够定义它。我当时非常担心这个问题,但我不得不改变自己的设计,使用实际的数组而不是对象。 - underscore_d
显示剩余13条评论

8

整数到指针的转换完全由实现定义,因此它取决于您正在使用的实现。

话虽如此,您只能关系地比较指向同一对象部分(基本上是同一结构体的子对象或同一数组的元素)的指针。您不允许比较指向任意完全不相关对象的两个指针。


2
@Matt: C99 6.3.2.3/5规定整数到指针的转换是实现定义的。C99 6.5.8/5规定了可以进行关系比较的指针的限制。 - James McNellis
事实上,在gcc上,char *a = "ABC"; int i = 10; if (a < (char *) &i) printf("Greater\n"); else printf("Smaller\n");甚至没有警告。 - shinkou
@James。顺便说一下,我认为比较部分不是OP关心的问题。 - shinkou
1
标准的草案在这里:http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1124.pdf,与詹姆斯的答案一致。 - andrewdski
1
@shinkou:这适用于“未定义行为”的术语。编译器实现者可以自由决定要做什么。 - ChrisWue
@ChrisWue。没错,但这并不是任何限制的意思。 - shinkou

4
根据C++标准草案5.9节所述,如果两个指针p和q指向不是同一对象的不同对象(或者不同函数),或者只有其中一个为空,或者它们指向的是同一数组中的不同元素,则pq、p<=q和p>=q的结果是未指定的。因此,如果将数字强制转换为指针并进行比较,则C++会给出未指定的结果。如果您获取可以有效比较的元素的地址,则比较操作的结果与指针类型的符号无关。请注意,未指定并非未定义:完全可以比较不在同一结构或数组中的相同类型的不同对象的指针,并且您可以期望某些自洽的结果(否则将无法使用这些指针作为树中的键,或对这些指针的vector进行排序、二分搜索等,需要一个一致的直观的整体<排序)。
请注意,在非常旧的C ++标准中,行为是未定义的 - 就像2005年WG14 / N1124草案和andrewdski在James McNellis的{{link2:答案}}下链接的那样 -

“pq的结果”这句话是什么意思?这里肯定缺少了一个运算符。它是什么?我猜是“==”,但这里好像还缺少了“<”和“>”。 - underscore_d
@underscore_d:网站吃掉了 <> - 对此很抱歉。请注意,== 不在列表中,也不应该在列表中。谢谢。 - Tony Delroy

1
为了补充其他答案,指向不同对象的指针之间的比较取决于标准。
在C99(ISO/IEC 9899:1999(E))中,§6.5.8:
5 [...] 在所有其他情况下,行为是未定义的。
在C++03(ISO/IEC 14882:2003(E))中,§5.9:
-其他指针比较是未指定的。

0

我知道这里有几个答案说除非它们指向同一结构内部,否则您不能比较指针,但这是一个红鲱鱼,我会尝试解释为什么。你的其中一个指针指向数组的开头,另一个指向结尾,因此它们指向同一结构。语言律师可能会说,如果第三个指针指向对象外部,则比较未定义,因此x >= array.start对于所有x可能是true。但这不是问题,因为在比较点时,C++无法知道数组是否嵌入在更大的结构中。此外,如果您的地址空间是线性的,就像现在绑定的那样,您的指针比较将被实现为(无)符号整数比较,因为任何其他实现都会更慢。即使在段和偏移量的时代,(远程)指针比较也是通过首先规范化指针,然后将它们作为整数进行比较来实现的。

那么,这一切归结为,如果您的编译器可以正常工作,而且您只关心指针是否指向数组内部,那么比较指针而不必担心符号应该可以工作,因为编译器应该根据C++对象可能跨越的两个边界中的哪一个使指针有符号或无符号。

不同的平台在此方面的行为不同,这就是为什么C++必须把它留给平台处理的原因。甚至有一些平台在进程启动时既不能映射地址0附近的内存,也不能映射80..00h附近的内存。在这种情况下,只要你保持一致就没关系。

有时候这会引起兼容性问题。例如,在Win32中指针是无符号的。以前,4GB的地址空间中只有下半部分(更准确地说是10000h … 7FFFFFFFh,因为NULL指针分配分区)可供应用程序使用;高地址仅可供内核使用。这导致一些人将地址放入有符号变量中,他们的程序会继续工作,因为高位始终为0。但随后出现了/3GB开关,使得近3GB的地址空间(更准确地说是10000h … BFFFFFFFh)可供应用程序使用,此时应用程序将崩溃或者表现出异常行为。

你明确表示你的程序只能在Windows上运行,使用无符号指针。然而,也许将来你会改变主意,使用 intptr_tuintptr_t 对于可移植性不好。我也想知道你是否应该这样做...如果你正在索引一个数组,比较索引可能更安全。例如,假设你有一个1GB的数组,位于1500000h...41500000h,由每个64KB的16384个元素组成。假设你意外地查找了索引80000-显然超出范围。指针计算将产生39D00000h,所以你的指针检查将允许它,尽管它不应该。


我知道这里有几个答案说,除非它们指向同一结构体,否则您不能比较指针,但这是一个误导,我会尝试解释为什么。你的其中一个指针指向数组的开头,另一个指向结尾,所以它们指向同一个结构体。整个答案都是一个误导。对于数组,比较和减法是被明确允许的,因此费力地说“听着,数组就是一个结构体”既(A)是错误的,也(B)完全没有必要!而且这个答案的其余部分只是离题的实现定义的唠叨。 - underscore_d
是否有任何方法让编译器在比较指针时知道两个可能不相关的指针不能标识由外部代码创建的某个较大对象的部分,一些编译器编写者 - 他们显然认为“聪明”和“愚蠢”是反义词 - 尝试让他们的编译器找出并利用这样的事情。 - supercat
假设您正在编写一个通用函数,该函数将以void*指针形式接受数组、元素大小为size_t和索引。数组大小或元素大小可能很大,因此您需要执行类似于element = *(array + size * index)的操作,那么您会将哪个索引转换为uintptr_t或ptr_t?溢出行为可能取决于平台。 - TakeMeAsAGuest
回答我自己的问题,可能是ptrdiff_t是有符号的。 - TakeMeAsAGuest

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