如何在C语言中比较结构体的相等性?

255

如何在标准C中比较两个结构体实例的相等性?

11个回答

237

C语言没有提供相关的语言特性来实现此功能 - 你需要自己逐个比较每个结构体成员。


23
如果这两个结构体变量是使用calloc初始化的,或者它们被memset设置为0,那么您可以使用memcmp比较这两个结构体,而不必担心结构体中的垃圾数据,这将节省您的时间。 - MOHAMED
26
使用memcmp()比较浮点数字段时,与0.0、-0.0和NaN进行比较会存在问题。二进制表示不同的指针可能指向同一位置(例如DOS:seg:offset),因此它们是相等的。某些系统具有多个空指针,这些指针比较相等。类似地,对于带有负零和冗余编码的晦涩整数以及Intel长双精度、decimal64等冗余编码的浮点类型,也存在相同的问题。这些问题无论是否使用calloc()或填充都没有影响。 - chux - Reinstate Monica
4
在我所知道的任何现代32位或64位系统上,唯一的问题是浮点数。 - Demi
5
如果您想知道为什么 == 无法与结构体一起使用(就像我一样),请参见 https://dev59.com/babja4cB1Zd3GeqPelRC。 - stefanct
4
@Demi: 今天,C程序员的第十戒是“你应该发誓、放弃和抵制那些声称‘全世界都是VAX’的邪说……”。将其替换为“全世界都是PC”并不是一种改进。 - Martin Bonner supports Monica
显示剩余4条评论

133
您可能会想使用memcmp(&a, &b, sizeof(struct foo)),但在某些情况下可能无法正常工作。编译器可能会为结构添加对齐缓冲区空间,并且位于缓冲区空间中的内存位置上的值不能保证是任何特定的值。
但是,如果在使用它们之前使用callocmemset分配结构体的完整大小,那么您可以使用memcmp进行浅层比较(如果您的结构包含指针,则仅当指针所指向的地址相同时才匹配)。

21
因为它可以在“几乎所有”的编译器上工作,但并非所有。请查看C90中的6.2.1.6.4:“两个具有相同对象表示(除NaN之外)的值相等,但是相等的值可能具有不同的对象表示。” - Steve Jessop
26
考虑一个“BOOL”字段。在相等性方面,任何非零BOOL都等于任何非零的BOOL值。因此,虽然1和2都可能是TRUE并且相等,但是使用memcmp函数会失败。 - ajs410
4
可能对你来说更容易,但对编译器和CPU来说则更难,因此也会更慢。你认为编译器为什么要添加填充?肯定不是为了无谓地浪费内存;) - Mecki
5
例如,任何IEEE浮点实现中,正零和负零的浮点值相等,但它们的对象表示不同。因此,实际上我不应该说它适用于“几乎所有编译器”,在任何允许存储负零的实现中,它将失败。我可能当时想到了有趣的整数表示。 - Steve Jessop
5
@Demetri: 但是很多结构体包含浮点数,提问者问的是“如何比较结构体”,而不是“如何比较不包含浮点数的结构体”。这个回答说你可以用memcmp进行浅层比较,前提是先清除内存。这个回答接近于正确,但并不完全正确。当然,问题也没有定义“相等”,所以如果你认为“相等”意味着“对象表示的逐字节相等”,那么memcmp正好做到了这一点(无论内存是否被清除)。 - Steve Jessop
显示剩余9条评论

25

如果您经常这样做,我建议编写一个比较两个结构的函数。这样,如果您改变了结构,您只需要在一个地方更改比较函数。

至于如何完成它... 您需要逐个比较每个元素。


2
即使我只使用一次,我也会编写一个单独的函数。 - Sam

21

你不能使用memcmp函数来比较结构体的相等性,因为在结构体字段之间可能存在随机填充字符。

  // bad
  memcmp(&struct1, &struct2, sizeof(struct1));

对于这样的结构体,上述方法将失败:

typedef struct Foo {
  char a;
  /* padding */
  double d;
  /* padding */
  char e;
  /* padding */
  int f;
} Foo ;

你必须使用成员逐一比较才能确保安全。


28
双精度浮点数后不太可能有填充;字符会在双精度浮点数之后得到足够的对齐。 - Jonathan Leffler

11

@Greg 正确地指出在一般情况下需要编写明确的比较函数。

如果满足以下条件,则可以使用memcmp

  • 结构体不包含可能是NaN的浮点字段。
  • 结构体不包含填充字段(使用clang的-Wpadded检查)或者结构体在初始化时显式使用memset进行了初始化。
  • 没有成员类型(例如Windows的BOOL),其具有不同但等效的值。

除非您正在为嵌入式系统编程(或编写可能在其中使用的库),否则我不会担心C标准中的某些边角情况。在任何32位或64位设备上都不存在近指针与远指针区别。我所知道的没有非嵌入式系统拥有多个NULL指针。

另一个选择是自动生成相等函数。如果以简单方式布置您的结构定义,那么可以使用简单的文本处理来处理简单的结构定义。对于一般情况,可以使用libclang - 因为它使用与Clang相同的前端,所以它能正确处理所有边角情况(除了错误)。

我还没有看到这样的代码生成库。但是,它似乎相对简单。

然而,这样生成的相等函数在应用程序级别上经常做错事。例如,在Windows中,应该浅层还是深层比较两个UNICODE_STRING结构?


2
使用memset等显式初始化结构体并不能保证在进一步写入结构体元素后填充位的值,参见:https://dev59.com/5q7la4cB1Zd3GeqPlOoA。 - gengkev

4

1
“{0,}”会清零填充字节吗? - Alnitak
GCC 至少会对部分初始化的结构体进行零填充,正如上面链接所示。而 https://dev59.com/zmcs5IYBdhLWcg3wMhCz 中详细说明了 C11 规定了这种行为。 - pixelbeat
2
一般而言并不是很有用,因为在分配给任何成员后所有填充均变得不确定。 - M.M

3
这取决于你所问的问题是什么:
1.这两个结构体是否为同一对象? 2.它们是否具有相同的值?
要查找它们是否为同一对象,请比较两个结构体的指针是否相等。如果您想在一般情况下找出它们是否具有相同的值,则需要进行深度比较。这涉及到比较所有成员。如果成员是指向其他结构体的指针,则还需要递归到这些结构体中。
在结构体不包含指针的特殊情况下,您可以执行memcmp来执行每个结构体中包含的数据的按位比较,而无需知道数据的含义。
确保您知道每个成员的“相等”意味着什么-对于整数来说很明显,但对于浮点值或用户定义的类型则更加微妙。

2

memcmp函数不会比较结构体本身,而是比较它们的二进制数据。由于结构体中可能存在垃圾数据,因此使用memcmp函数进行比较时往往会得到错误的结果。

逐个比较结构体元素可以保证比较的准确性和安全性。


1
如果这2个结构体变量是用calloc初始化的,或者通过memset设置为0,那么你可以使用memcmp比较这2个结构体,而不用担心结构体的垃圾值,这将使您节省时间。 - MOHAMED
1
calloc或memset都无法帮助您,因为每个赋值都会返回填充字节到不确定的值。 - Remember Monica
1
不,不总是会有垃圾数据。只有在需要时才进行填充。一些结构可以使用memcmp进行安全比较。 - grzegorz

0
如果结构体只包含基本类型,或者您对严格相等感兴趣,那么您可以像这样做:
int my_struct_cmp(const struct my_struct * lhs, const struct my_struct * rhs)
{
    return memcmp(lhs, rsh, sizeof(struct my_struct));
}
然而,如果您的结构体包含指向其他结构体或联合体的指针,则需要编写一个函数来正确比较基本类型,并根据需要对其他结构体进行比较调用。
请注意,您应该在ADT初始化的一部分中使用memset(&a, sizeof(struct my_struct), 1)来清零结构体的内存范围。

-2
如果两个结构体变量是使用calloc初始化的,或者使用memset设置为0,那么您可以使用memcmp比较这两个结构体,而不必担心结构体垃圾数据的问题,这将节省您的时间。

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