C 我可以对指针数组进行排序以删除重复的指针吗?

C 我可以对指针数组进行排序以删除重复的指针吗?,c,pointers,undefined-behavior,C,Pointers,Undefined Behavior,假设我定义了一个结构并创建了几个实例。我有一个指向这些实例的指针数组,但有些指针指向我的结构的相同实例。我想删除数组中的重复项,因此我按qsort()函数对其进行排序。这是我的比较函数: int cmp(const void *a, const void *b) { struct foo **s1 = (struct foo**)a; struct foo **s2 = (struct foo**)b; return *s1 - *s2; } 问题是,我真的可以使用这样的比较进行排

假设我定义了一个结构并创建了几个实例。我有一个指向这些实例的指针数组,但有些指针指向我的结构的相同实例。我想删除数组中的重复项,因此我按
qsort()
函数对其进行排序。这是我的比较函数:

int cmp(const void *a, const void *b)
{
  struct foo **s1 = (struct foo**)a;
  struct foo **s2 = (struct foo**)b;
  return *s1 - *s2;
}
问题是,我真的可以使用这样的比较进行排序吗?我知道在这种情况下,子结构指针是未定义的,但我只关心指针的整数值,因为我只希望指向同一个
foo
实例的指针彼此相邻

也许我应该使用这样的函数:

int cmp(const void *a, const void *b)
{
  struct foo **s1 = (struct foo**)a;
  struct foo **s2 = (struct foo**)b;
  if (*s1 > *s2)
    return 1;
  else if (*s1 == *s2)
    return 0;
  else 
    return -1;
}

使用它们有什么区别吗?

如果您有
intptr\t
(这是一种整数类型),那么您可以将
void*
指针强制转换为该类型,然后您可以比较任意值
intptr\t
不是必需的类型,但在实践中非常常见

从另一个
intptr\t
中减去一个可能仍然会溢出,因此它不是一个严格可移植的解决方案。但比较是好的。如果使用
uintptr\u t
来避免溢出,则差值永远不会为负值。这是使用
a-b
实现qsort风格比较函数的一个常见问题

减去或比较不指向同一对象的指针是未定义的行为。因此,问题中提出的两种解决方案均无效:

§6.5.6第9段(添加剂操作员):

当减去两个指针时,两个指针都应指向同一数组对象的元素,或指向数组对象最后一个元素之后的元素

§6.5.8第5段提供了一个可能的有效比较列表,这比减法的限制要宽松一些,因为您可以比较指向相同
结构
联合
的两个成员的指针,前提是这些成员本身属于同一类型。但不相关的对象不属于此列表中的任何一个。它以一句话结尾:“在所有其他情况下,行为是未定义的。”


如果您想要一个真正的可移植解决方案,它不依赖于
intptr\t
,那么您可以memcmp指针。但是,实际上,我认为这只是理论上的问题。

如果要删除重复项,则可以使用嵌套for循环遍历每个指针,如下所示:

int duplicates=0;
for (int i=0;i<count;++i;)
    for (int j=i+1;j<count;++j)
        if (data [i]==data [j])
            ++duplicates;
int duplicates=0;

对于(int i=0;i您认为减法指针为什么会调用UB?如果很可能插入重复的指针,您应该首先考虑避免它们。@haccks我不确定减法两个指针是否总是得到相同符号的结果。使用
uintpttr\t
的方法,如果存在并合理定义了此类类型,则不会产生任何结果未定义行为中的t(尽管我不认为标准中的任何内容会禁止存在不能保存所有指针值的
uintpttr\u t
)如果一个带有16位内存总线的系统使用了48位指针,但是
uintpttr\t
是64位,那么
uintpttr\t x=p;
p
存储到
x
的下48位,并在未初始化位的情况下让前16位保留任意值是完全合法的如果合理地定义了
uintpttr\u t
,那么相同类型的两个指针产生相同的
uintpttr\u t
值这一事实应该意味着它们将进行相等的比较,并且在大多数系统上,反向比较也将成立,但C标准中的任何内容都不会排除这种存在一种系统,它故意使代码无法通过任何方式在指向未知对象的指针之间定义任何形式的排序,甚至使用位级黑客。@supercat:是的,它是实现定义的。理论上,来自void*->intptr\t的映射可能不是双射的。显然,给定的
void*p,*q
,(uintpr\t)p==(uintpttr_t)q意味着p==q,因为
(void*)(uintpttr_t)p
必须等于
p
,但正如您所说,理论上有可能
(uintpttr_t)在C++中,这是整个问题都可以避免的,因为<代码> STD::保证是一个总的顺序。但如果没有C语言的保证,我希望C标准委员会会认识到如果99%的平台能够很容易地支持一个行为,那么有一个定义的方法就有很大的价值。通过哪种代码可以询问它是否受支持。例如,如果编译器可以保证关系运算符将产生0或1,但没有做出超出此范围的承诺,则使用标准宏
\uuuuu STDC\u UNRELATED\u PTR\u COMPARE
,该宏将为1,并且更高的数字将表示各种更强的保证,直到保证所有指针我知道,允许不相关的指针比较产生未定义的行为,使得C在不支持它们的平台上可用,我对此没有问题。然而,我发现一些罕见的平台无法支持这样的行为的想法是荒谬的该特性构成了一个很好的理由,禁止在永远不需要在如此罕见的平台上运行的程序中使用该特性。如果通用平台的旧编译器允许进行此类比较,但较新的优化器会将指针之间的关系比较视为证明它们标识相同的假设是正确的。。。