小于void指针的比较

我想比较两个这样的void指针:

void foo(void* p1, void* p2) { if (p1 < p2) { void *tmp = p1; p1 = p2; p2 = tmp; } // do something with p1 and p2. } 

根据标准,这是正确的吗? 我的意思是void指针的比较是一个明确定义的行为?

如果有人能指出我记录的C标准,我将不胜感激。

正如Drew McGowen在评论中指出的那样,但我会在这里发布引文:

6.5.8关系运算符

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

这是C11标准。 C99是一样的。

C++11 ,它或多或少都是一样的。 关于指针转换的一些调整,如果你愿意,我可以将它全部粘贴。 更重要的是,行为是未指定的(正如Chris上面指出的那样)。

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

未指明的行为必须做一些含糊不清的事情。 编译器不必记录它,甚至不必为两个不同的程序做同样的事情,但它不会炸毁世界。 您的程序仍被视为有效。

因此,在您的特定情况下,编译为C ,用户可能会导致未定义的行为,具体取决于它们传递给函数的内容。 这似乎非常危险。

另外,与我对这个问题的评论相反,你不能只在两个任意的void* s上使用!=C11

6.5.9平等运营商

2下列之一应持有:

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

– 两个操作数都是兼容类型的限定或非限定版本的指针;

– 一个操作数是指向对象类型的指针,另一个是指向合格或非限定版本的void的指针; 要么

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

如果两个指针指向相同“对象”(结构,联合或数组)的部分(或者超过数组末尾的部分),则只能保证比较两个指针。

在实践中,这是因为存在分段存储器模型计算机,其中仅比较段偏移比比较段偏移和段id快得多。 如果所述段重叠,则具有相同段偏移的两个指针可以比较相等,即使它们指向不同的存储区域。

现在这种系统比20年前更不常见。

通过@drawmcgowen,这是在C11 6.5.8。

虽然比较指向不相关对象(不在同一structunion或数组中)的指针的结果是未定义的,但我不知道未定义行为超过“不按您认为的顺序进行比较”的平台。

如果您真的需要这个,并且愿意限制您的代码可以移植到哪个平台,那么您可以获得合理的保证。 但是请注意,由于这是未定义的行为,因此编译器的任何未来版本都可能使此代码无法正常运行。

更糟糕的是,一些编译器利用未定义的行为来优化机会。 例如,假设您有一个缓冲区,并且在缓冲区的开头有一个指针。

如果比较另一个指向它的指针,要么(A)它在缓冲区内,所以>=它,或者(B)它不在缓冲区内,所以结果是未定义的。

如果你可以certificate一个向量位于缓冲区的前面或者一个结尾处,那么一个简单的优化是放弃比较(如果>=表示前面,或<=表示后面)并用常量替换它。

您的编译器可以在代码优化的任何时候解决这个问题,任何一点发布。

它甚至可以说“这是指向堆分配对象的指针”,并certificate每个指针都等于指针或不相关 - 因此<> 总是未定义的行为,并且可以完全消除执行此操作的分支从你的代码。

依赖于未定义的行为意味着您现在必须审核代码生成的机器代码,以及将来的每次编译。


最初这个问题用c ++标记。 在C ++中, std::less()( lhs, rhs )保证能很好地排序所有指针。 (这是为了允许指针在各种std容器和算法中进行排序而添加的)如果您在混合C / C ++系统中工作,这可能会有用。

20.14.6 [比较] / 2

对于较少的模板,[...],任何指针类型的特化都会产生严格的总顺序,这些顺序在这些特化之间是一致的,并且与内置运算符强加的部分顺序一致<[...]对于模板如果调用操作符调用比较​​指针的内置运算符,则调用运算符会产生严格的总顺序,这些顺序在这些特化之间是一致的,并且与这些内置运算符强加的部分顺序一致。

继来自@BoBTFish …对于关系运算符(C11的6.5.8),事情进一步受到限制:

制约因素2

以下其中一项应持有:

– 两个操作数都有实际类型;

或 – 两个操作数都是指向兼容对象类型的限定或非限定版本的指针。

哪一个读取为排除 void*void不是一个类型,因此void*不是指向类型的指针)…所以如果你尝试< etc two void*指针或者一个“真实的”你的编译器可能会抱怨你“指针和void* 。 施放到(char *)通常可以完成工作! 但就像任何一个演员一样,如果它崩溃和烧伤,没有人会非常同情:-)

BTW,标准不是一个简单的阅读,但在60美元它是一个有用的添加到一个人的图书馆http://webstore.ansi.org/RecordDetail.aspx?sku=INCITS%2FISO%2FIEC+9899-2012