我想比较两个这样的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标准,我会很感激。
答案 0 :(得分:8)
正如Drew McGowen在评论中指出的那样,但我会在这里发布引文:
6.5.8关系运算符
5比较两个指针时,结果取决于地址空间中的相对位置 对象指向。 如果两个指向对象类型的指针都指向 相同的对象,或者两者都指向同一个对象的最后一个元素 数组对象,他们比较相等。如果指向的对象是 同一聚合对象的成员,指向稍后声明的结构成员的指针比指定成员的指针要大 在结构的早期,以及指向更大的数组元素的指针 下标值比指向同一元素的指针大 具有较低下标值的数组。指向成员的所有指针 相同的联合对象比较等于。如果表达式P指向a 数组对象的元素和表达式Q指向最后一个 同一个数组对象的元素,指针表达式Q + 1进行比较 大于P. 在所有其他情况下,行为未定义。
这是C11
标准。 C99
是一样的。
来自C++11
,它或多或少相同。关于指针转换的一些调整,如果你愿意,我可以将它全部粘贴。更重要的是,行为是未指定的(正如Chris上面指出的那样)。
请注意,未定义的行为是致命的。如果你比较两个不相关的指针,你的机器可能会着火,发射核导弹,让恶魔飞出你的鼻子等等。
未指明行为必须做一些含糊不清的事情。编译器不必记录它,甚至不必为两个不同的程序做同样的事情,但它不能炸毁世界。您的计划仍然有效。
因此,在您的特定情况下,编译为C
,用户可能会导致未定义的行为,具体取决于它们传递给函数的内容。这似乎很危险。
另外,与我对该问题的评论相反,您 只能在两个!=
上使用void*
:
C11
6.5.9平等运营商
2以下其中一项应持有:
- 两个操作数都有算术类型;
- 两个操作数都是指向兼容类型的限定或非限定版本的指针;
- 一个操作数是指向对象类型的指针,另一个是指向a的指针 无效的合格或不合格的版本;或
- 一个操作数是指针,另一个是空指针常量。
答案 1 :(得分:6)
如果两个指针指向同一个“对象”(结构,联合或数组)的部分(或者超过数组末尾的部分),则只能保证比较两个指针。
实际上,这是因为存在分段存储器模型计算机,其中仅比较段偏移比比较段偏移和段id快得多。如果所述段重叠,则具有相同段偏移的两个指针可以比较相等,即使它们指向不同的存储区域。
此类系统现在不像20年前那么常见。
通过@drawmcgowen,这是在C11 6.5.8。
虽然比较指向不相关对象(不在同一struct
,union
或数组中)的指针的结果是未定义的,但我不知道未定义行为超过“不是”的平台按照你认为的顺序进行比较“。
如果您真的需要这个,并且愿意限制您的代码可移植到哪个平台,您可以获得合理的保证。但是请注意,由于这是未定义的行为,因此编译器的任何未来版本都可能使此代码无法正常运行。
更糟糕的是,某些编译器会利用未定义的行为来优化机会。例如,假设您有一个缓冲区,并且在缓冲区的开头有一个指针。
如果比较另一个指向它的指针,要么(A)它在缓冲区内,所以>=
都在它之内,或者(B)它不在缓冲区内,所以结果是未定义的。 / p>
如果你可以证明一个向量位于缓冲区的前面或者一个结尾处,那么简单的优化就是放弃比较(前面是>=
,后面是<=
)并用常数替换它。
您的编译器可以在代码优化的任何时候解决这个问题,任何一点发布。
它甚至可以说“这是指向堆分配对象的指针”,并证明每个指针都等于指针或不相关 - 因此<
和>
是始终未定义的行为,并且可以从代码中完全消除执行此操作的分支。
依赖于未定义的行为意味着您现在必须审核代码生成的机器代码,以及将来的每次编译。
最初这个问题标有c++。在C ++中,std::less<void*>()( lhs, rhs )
可以很好地命令所有指针。 (这是为了允许指针在各种std
容器和算法中进行排序而添加的)如果您在混合C / C ++系统中工作,这可能会有用。
对于较少的模板,[...],任何指针类型的特化产生严格的总顺序,这些顺序在这些特化之间是一致的,并且也与内置运算符强加的部分顺序一致&lt; [...]对于模板特化,如果调用操作符调用比较指针的内置运算符,则调用运算符产生一个严格的总顺序,这些顺序在这些特化之间是一致的,并且与所施加的部分顺序一致由那些内置的运营商。
答案 2 :(得分:0)
从@BoBTFish开始......对于关系运算符(C11的6.5.8),事情进一步受到限制:
约束2
以下其中一项应成立:
- 两个操作数都有实际类型;
或 - 两个操作数都是指向兼容对象类型的限定或非限定版本的指针。
哪一个读为不包括 void*
(void
不是类型,因此void*
不是指向某个类型的指针)...所以你的如果您尝试<
等两个void*
指针或一个“真实”指针和void*
,编译器可能会抱怨你。施放到(char *)通常可以完成工作!但是就像任何一个演员一样,如果它崩溃和烧伤,没有人会非常同情: - )