空指针的小于比较

5
我希望您能够帮忙翻译以下内容:

我想要比较两个空指针,代码如下:

void foo(void* p1, void* p2) {

  if (p1 < p2) {
    void *tmp = p1;
    p1 = p2;
    p2 = tmp;
  }

  // do something with p1 and p2.
}

根据标准,这样做是正确的吗?我的意思是,比较空指针是否属于定义行为?

如果有人能指出记录了这一点的C标准,我会非常感激。


2
当然,这是正确的。不过我想知道它除了交换错误实现之外还有什么用途。 - Quentin
1
C 还是 C++?当然,它们在这方面可能是相同的,我还没有检查过,但它们确实是两种非常不同的语言。我的感觉是只有在类或数组中才允许这样使用,否则必须使用 !=,但我会尝试确认一下。 - BoBTFish
请注意,仅在某些情况下指定了数组的顺序。 - Jarod42
8
只有当空指针指向同一数组或内存块时,比较空指针的行为才是定义良好的。(根据C11 6.5.8) - Drew McGowen
@BoBTFish,据我记得,在这方面它们是相同的(除了可能是未定义 vs 未指定)。 - chris
显示剩余2条评论
3个回答

11

正如Drew McGowen在评论中指出的那样,但我将在此发布引用:

6.5.8 关系运算符

5 当比较两个指针时,结果取决于指向对象的地址空间中的相对位置。如果两个指向对象类型的指针都指向同一对象或都指向同一数组对象的最后一个元素之后,它们将相等。如果所指的对象是同一聚合对象的成员,则后声明的结构成员的指针与先声明的结构成员的指针进行比较,具有更大的值,并且带有更大下标值的数组元素的指针与相同数组的较低下标值的元素的指针进行比较,具有更大的值。所有指向同一联合对象的成员的指针将相等。如果表达式P指向数组对象的一个元素,并且表达式Q指向同一数组对象的最后一个元素,则指针表达式Q + 1将比P大。 在所有其他情况下,行为未定义

这是来自C11标准的内容。 C99也一样。

C++11开始,它基本上是相同的。一些关于指针转换的微调,如果您想要的话,我可以全部粘贴。更重要的是,行为是未确定的(正如Chris在上面指出的那样)。

请注意,未定义的行为是致命的。如果您比较两个不相关的指针,则可能会导致您的机器着火,发射核导弹,使恶魔从您的鼻子中飞出等。

未指定的行为必须做出某种含糊有余的事情。编译器不必对其进行记录,甚至不必为两个不同的程序执行相同的操作,但它不能摧毁世界。您的程序仍然被视为有效。

因此,在编译为C的特定情况下,用户可能会根据将传递给函数的参数造成未定义的行为。这似乎非常危险。

另外,与我在问题评论中所说的相反,您不能仅对两个任意的void*使用!=:

6.5.9 相等运算符

2 必须满足以下一种情况:

— 两个操作数都具有算术类型;

— 两个操作数是限定或不限定版本的兼容类型的指针;

— 一个操作数是对象类型的指针,另一个是限定或不限定版本的void指针;或者

— 一个操作数是指针,另一个是空指针常量。


感谢您的参考。 - A. K.
关于你最后一段的问题:void 不是和 void 兼容吗? - mafso
void 不是一个对象类型,我认为。 - BoBTFish
6.2.5/19 void类型包含一组空值;它是一个不完整的对象类型,无法完成。现在我完全不确定了。 - BoBTFish
我似乎在思考“C ++”:“3.9/5未完全定义的对象类型和void类型是不完整的类型(3.9.1)。”和“/ 8对象类型是指不是函数类型,不是引用类型,也不是void类型的(可能是cv限定的)类型。” - BoBTFish

8
比较两个指针仅在它们指向同一“对象”(结构体、联合体或数组)的部分时才能保证是明智的(或者对于数组而言,一个越过结束位置的指针)。
实际上,这是因为分段存储器计算机的存在,仅比较段偏移量比同时比较段偏移量和段标识符更快。如果这些段重叠,两个具有相同段偏移量的指针可能会相等,即使它们指向不同的内存区域。
这样的系统现在比20年前要少见得多。
根据@drawmcgowen,在C11 6.5.8中提到。
虽然比较指向不相关对象(不在同一个structunion或数组中)的指针的结果是未定义的,但我不知道有哪个平台的未定义行为比“不按您认为应该的顺序进行比较”更多。
如果你真的需要这个,并愿意限制你的代码可移植到哪些平台,你可能可以得到合理的保证。但请注意,由于这是未定义的行为,你的编译器的任何未来版本都可能导致此代码不正确地运行。
更糟糕的是,一些编译器利用未定义行为进行优化。例如,假设您有一个缓冲区,并且一个指针位于缓冲区的开头。
如果您将另一个指针与其进行比较,则(A)它在缓冲区内,因此>=,或者(B)它不在缓冲区内,因此结果是未定义的。
如果您可以证明向量位于缓冲区的前面或结束位置,则可以进行简单的优化,即删除比较(如果是前面,则为>=,如果是后面,则为<=),并将其替换为常量。
您的编译器可以在代码优化的任何时候使用任何版本发布来解决此问题。
它甚至可以说“这是指向堆分配对象的指针”,并证明每个指针要么等于指针,要么无关 - 因此<>始终是未定义的行为,并且执行此操作的分支可以从代码中完全消除。
依赖未定义的行为意味着您现在必须审核由您的代码生成的机器代码,以及将来的每次编译。

最初这个问题标记为。在C ++中,std :: less<void *>()(lhs,rhs)保证对所有指针进行良好排序。(这是为了允许将指针排序在各种std 容器和算法中使用)。如果您在混合的C / C ++系统中工作,则可能会有用。

20.14.6 [comparisons] / 2

对于模板less,[...]任何指针类型的特化都产生严格的全序,并且在那些特化之间是一致的,并且与由内置运算符< [...]强制实施的偏序也是一致的。 对于模板专业化较少[...],如果调用运算符调用比较指针的内置运算符,则调用运算符产生严格的总序,该序列在这些专业化之间是一致的,并且还与由那些内置的操作符强制实施的偏序一致。


1
“does not compare in the order you think it should”这句话是什么意思? - herohuyongtao
1
@herohuyongtao 指向两个相等的不同对象的指针将是最常见和问题最多的情况。技术上,未定义的行为允许编译器执行“点燃计算机”指令:我只是指出在这种情况下,未定义的行为大多是“未指定的结果”。我相当有信心,< 对指针施加的顺序会随时间保持一致(假设比较段偏移量是错误的原因),但我不会拿你的生命来打赌。 - Yakk - Adam Nevraumont
1
@herohuyongtao 另一个例子可以从这里看到 -- 哈佛模型,其中数据和程序指针在不同的地址空间中。同样的问题是,尽管它们不相关,但指向函数指针的void*和指向intvoid*可能会相等。 - Yakk - Adam Nevraumont
3
一些极新型的编译器,如果能确定某个条件X为真时会导致未定义行为,就会推断X为假,并省略只有在X为真时才相关的代码。这种行为的影响超出了“不按你想象中的顺序比较”的范畴。不幸的是,除非标准定义一些规范行为和代码需求方式,否则这种行为将变得越来越常见。 - supercat
1
@0xC0DEFACE "20.14.6 [比较]/2 对于模板 less,[...] 任何指针类型的特化都会产生一个严格的全序,这个全序在这些特化中是一致的,并且与内置运算符所施加的偏序也是一致的。 [...] 对于模板特化 less<void> [...] 如果调用运算符调用了比较指针的内置运算符,则调用运算符会产生一个严格的全序,这个全序在这些特化中是一致的,并且与这些内置运算符所施加的偏序也是一致的。" - Yakk - Adam Nevraumont
显示剩余5条评论

1

继 @BoBTFish 之后,对于关系运算符 (C11 的 6.5.8),一些限制进一步减少:

限制2

以下两者之一必须成立:

— 两个操作数都具有实数类型;

或 — 两个操作数是指向相容对象类型的限定或未限定版本的指针。

其中一个是排除void* (void不是一种类型,因此void*不是指向类型的指针)...因此如果您尝试将两个void*指针或一个 "real" 指针和一个 void* 进行<等比较,则您的编译器可能会发出警告。通常将其转换为(char*)即可完成工作!但像任何转换一样,如果崩溃和错误发生,没有人会很同情 :-)

顺便说一句,这个标准不是易读的,但以60美元的价格购买它是你图书馆中有用的补充http://webstore.ansi.org/RecordDetail.aspx?sku=INCITS%2FISO%2FIEC+9899-2012


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