【问题标题】:Why doesn't C provide struct comparison?为什么C不提供结构比较?
【发布时间】:2011-08-24 16:45:48
【问题描述】:

正如大多数 C 程序员所知,您不能直接比较两个结构。

考虑:

void isequal(MY_STRUCT a, MY_STRUCT b)
{
    if (a == b)
    {
        puts("equal");
    }
    else
    {
        puts("not equal");
    }
 }

a==b 比较将在任何合理的 C 编译器上引发 AFAIK 编译错误,因为 C 标准不允许内置结构比较。由于对齐、打包、位域等原因,使用 memcmp 的解决方法当然是一个坏主意,因此我们最终编写逐个元素的比较函数。

另一方面,它确实允许结构分配,例如a = b 完全合法。 很明显,编译器可以相当简单地处理这个问题,那么为什么不比较呢?

我唯一的想法是结构分配可能与 memcpy() 相当接近,因为对齐等导致的间隙无关紧要。另一方面,比较可能更复杂。或者这是我缺少的东西?

显然,我知道逐个元素进行简单的比较是不够的,例如如果结构包含指向字符串的指针,但在某些情况下它会很有用。

【问题讨论】:

  • 投票结束:我认为这是一个非常有趣的问题,但同时,我认为您不太可能得到任何不仅仅是猜测的答案。
  • 如果每个结构都通过两次不同的 malloc 调用分配内存,你将如何处理 void 指针
  • Void 指针可以单独比较是否相等。如果一个结构包含一个与另一个结构中的 void 指针不同的 void 指针,我希望它们比较为不相等。

标签: c


【解决方案1】:

正如其他人所提到的,这里是 Harbison 和 Steele 的 C: A Reference Manual 的摘录:

即使允许对这些类型进行赋值,也无法比较结构和联合的相等性。对齐限制导致的结构和联合中的间隙可能包含任意值,并且对此进行补偿将对相等比较或修改结构和联合类型的所有操作带来不可接受的开销。

【讨论】:

  • 在分配两个结构时,对齐结构中的潜在间隙会不会导致与比较它们时完全相同的性能问题?
  • 我个人不赞成这个论点,但我想原因如下:对于复制/分配结构,生成的代码相当简单,尽管可能很慢:它只需要复制整个结构的内存范围,并且可以包括间隙的未定义内容。在比较时,它必须从实际字段中辨别出差距(如果这是所需的行为),这将需要更复杂的代码。
【解决方案2】:

不支持比较,原因与memcmp 失败的原因相同。

由于填充字段,比较会以不可预知的方式失败,这对于大多数程序员来说是不可接受的。赋值改变了不可见的填充字段,但无论如何这些都是不可见的,所以没有什么意外的。

显然,您可能会问:那为什么不对所有的填充字段进行零填充呢?当然可以,但它也会让所有程序为他们可能不需要的东西付费。

编辑

Oli Charlesworth 在 cmets 中注明您可能会问:“为什么编译器不为逐个成员的比较生成代码”。如果是这样,我必须承认:我不知道 :-)。如果编译器只允许比较完整类型,它就会拥有所有需要的信息。

【讨论】:

  • OP 明确建议使用memcmp,而是让编译器自动生成代码以进行逐个成员的比较。
  • @Oli Charlesworth 也许你是对的,但我没有把它读成“为什么编译器不逐个成员地做”。我不认为有一个简单的答案,如果编译器只允许在完整类型之间进行比较,它肯定拥有它需要的所有信息。
  • 除了填充位之外,在某些实现中还可能存在正零和负零的问题。如果有两个相同的结构,除了一个具有正零而另一个具有负零之外,则可能存在应将它们视为相等的情况,而在其他情况下则不应视为相等。最好有语法要求编译器执行成员比较(然后在可行的情况下它可以用内存比较替换),但我不确定我是否喜欢 == 或 !=那个目的。
  • 但究竟什么是逐个成员比较?如果字符串匹配但不完全相同,两个包含字符串的结构是否不相等?我猜委员会想避免此类问题并让用户进行比较,因为用户最清楚要比较什么。
  • @Rudy Velthuis:最自然的方法是将其定义为使用相同的运算符(==!=)比较每个相应的成员。这意味着如果您的结构包含指向字符串的指针,则将比较指针(而不是它们指向的指针)是否相等。您还需要为数组类型定义==!=,大概是通过比较每个对应元素以类似的方式。它不存在的真正原因可能只是因为很少需要比较结构的严格相等性。
【解决方案3】:

我在 C 基本原理 (C99 rationale V5.10) 6.5.9 中发现了这一点:

C89 委员会不止一次考虑允许比较结构 为了平等。这样的提议在结构中的孔问题上失败了。逐字节 两个结构的比较需要确保将孔设置为零,以便所有 孔将比较相等,对于自动或动态分配的变量来说是一项艰巨的任务。

结构中联合类型元素的可能性引发了无法解决的问题 方法。如果不保证所有漏洞都设置为零,则实现将 准备将结构比较分解为任意数量的成员比较; 因此,一个看似简单的表达式可以扩展为大量代码,即 与C精神背道而驰

简而言之:由于结构/联合可能包含填充字节,并且委员会没有强制它们保存某些值,因此他们不会实现此功能。因为如果必须将所有填充字节设置为零,则需要额外的运行时开销。

【讨论】:

  • 我个人认为这个理由是无稽之谈,因为 C 标准已经有其他非常相似的“重资源”要求: - 静态存储持续时间的结构/联合必须将填充字节设置为零(C11 6.7 .9/10)。 - 部分初始化结构的所有未初始化成员必须设置为零 (C11 6.7.9/19)。
  • 不,这个理由是完全一致的。将静态事物初始化为零很便宜,并且只在启动时完成一次。这样做非常便宜,因为整个内存段都被清零(在其中一些被初始化之前),出于安全原因(托管系统)您无论如何都想要这样做,或者在启动时由您的硬件完成(嵌入式系统)。这与静态变量初始化为零而局部变量不初始化的原因相同 - 在运行时重复执行的事情的性能更为重要。
  • @WillW 不,.bss 在嵌入式系统上不会“由硬件初始化”......我关于部分初始化结构的零初始化的第一个论点适用于局部变量以及静态存储持续时间的变量- 这不一定由 .bss 复制完成,但也可能发生在运行时。
  • 虽然不像我所说的那样普遍,但大量嵌入式目标指定所有非寄存器内存地址在重置时都将为零。
【解决方案4】:

自动生成比较运算符是个坏主意。想象一下这种结构如何进行比较:

struct s1 {
   int len;
   char str[100];
};

这是最大长度为 100 的类似帕斯卡的字符串

另一种情况

struct s2 {
   char a[100];
}

编译器如何知道如何比较一个字段?如果这是一个以 NUL 结尾的字符串,编译器必须使用 strcmp 或 strncmp。如果这是 char 数组编译器必须使用 memcmp。

【讨论】:

  • 确实如此。即使数组应该包含字节,在 s1 中的比较也应该最多为 len 个字符。用户知道,但编译器不知道,因为它不知道len 的语义。
  • 如果使用 strncpy 之类的例程维护固定大小的字段(在许多情况下应该如此),相同的字符串将进行相同的比较。虽然在大多数情况下留下旧字符串的残留物是无害的,但在某些情况下可能会产生安全隐患;简单地零填充任何固定长度的缓冲区会更干净。
  • 嗯,不是很有说服力。没有人会期望比较遵循该类型的任何语义。只是组件明智的比较就可以了。令人信服的是,有一个简单的遗留语法问题不允许我们比较数组。所以其他包含数组的复合类型也无法比较。
  • 这个比较是微不足道的。无需了解字符串中包含的内容的语义即可进行比较。您通常需要这样做的唯一原因是您只传递了一个指向字符串开头的指针,不知道长度,因此您迭代直到看到 NULL。在这种情况下,编译器知道数据的大小,并且可以对所有元素进行适当的比较。这可能意味着 strcmp 相同的两个字符串比较不同,但这是用户应该注意的。
  • 但是将 char 数组解释为字符串不是问题,是吗?当您尝试比较包含 char[] 的结构时,没有人希望编译器将值 0x00 设为特殊。
【解决方案5】:

添加到现有的好答案:

struct foo {
    union {
        uint32_t i;
        float f;
    } u;
} a, b;
a.u.f = -0.0;
b.u.f = 0.0;
if (a==b) // what to do?!

问题本质上是由于工会无法存储/跟踪哪个成员是当前成员。

【讨论】:

  • 这只是一个问题,因为没有指定结构的比较开始(即,比较包含联合的结构(递归)需要由规范来解决,例如,通过声明它是 UB)但它是一个非常有趣的方面,谢谢。
  • 这是 IMO 的正确答案:联合是您无法为相等而不是运行时开销生成代码的真正原因。
猜你喜欢
  • 2019-02-24
  • 1970-01-01
  • 1970-01-01
  • 2016-02-15
  • 1970-01-01
  • 1970-01-01
  • 2010-12-25
  • 1970-01-01
  • 2023-03-09
相关资源
最近更新 更多