¿Cómo se comparan las estructuras de igualdad en C?

216

¿Cómo se comparan dos instancias de estructuras para la igualdad en el estándar C?

Hans Sjunnesson
fuente

Respuestas:

196

C no proporciona facilidades de lenguaje para hacer esto; debe hacerlo usted mismo y comparar cada estructura miembro por miembro.

Greg Hewgill
fuente
19
si las variables de 2 estructuras se inicializan con calloc o si se configuran con 0 por memset para que pueda comparar sus 2 estructuras con memcmp y no se preocupe por la basura de la estructura y esto le permitirá ganar tiempo
MOHAMED
21
@MOHAMED Comparar campos de punto flotante con 0.0, -0.0 NaNes un problema con memcmp(). Los punteros que difieren en la representación binaria pueden apuntar a la misma ubicación (por ejemplo, DOS: seg: offset) y, por lo tanto, son iguales. Algunos sistemas tienen múltiples punteros nulos que se comparan por igual. Lo mismo para los oscuros intcon -0 y los tipos de coma flotante con codificaciones redundantes. (Intel long double, decimal64, etc.) Estos problemas no hacen diferencia si se calloc()usa o no o el relleno.
chux
2
@chux En cualquier sistema moderno de 32 o 64 bits que conozco, el único problema es con el punto flotante.
Demi
2
En caso de que se pregunte por qué ==no funciona con estructuras (como yo), consulte stackoverflow.com/questions/46995631/…
stefanct el
44
@Demi: Hoy. El décimo mandamiento para los programadores de C es: "Debes renunciar, renunciar y abjurar de la herejía vil que dice que" Todo el mundo es un VAX "...". Reemplazar esto con "Todo el mundo es una PC" no es una mejora.
Martin Bonner apoya a Monica el
110

Puede tener la tentación de usar memcmp(&a, &b, sizeof(struct foo)), pero puede no funcionar en todas las situaciones. El compilador puede agregar espacio de búfer de alineación a una estructura, y no se garantiza que los valores encontrados en las ubicaciones de memoria que se encuentran en el espacio de búfer sean de ningún valor en particular.

Pero, si usa calloco memsetel tamaño completo de las estructuras antes de usarlas, puede hacer una comparación superficial con memcmp(si su estructura contiene punteros, solo coincidirá si la dirección a la que apuntan los punteros es la misma).

Sufí
fuente
19
Cerrar, porque funciona en "casi todos" los compiladores, pero no del todo. Consulte 6.2.1.6.4 en C90: "Dos valores (distintos de NaN) con la misma representación de objeto se comparan igual, pero los valores que se comparan igual pueden tener diferentes representaciones de objeto".
Steve Jessop
22
Considere un campo "BOOL". En términos de igualdad, cualquier BOOL distinto de cero es igual a cada valor BOOL distinto de cero. Entonces, mientras que 1 y 2 pueden ser VERDADEROS y, por lo tanto, iguales, memcmp fallará.
ajs410
44
@JSalazar Más fácil para ti, quizás, pero mucho más difícil para el compilador y la CPU y, por lo tanto, también mucho más lento. ¿Por qué crees que el compilador agrega relleno en primer lugar? Ciertamente no desperdiciar memoria por nada;)
Mecki
44
@Demetri: por ejemplo, los valores flotantes positivo y negativo cero se comparan igual en cualquier implementación flotante IEEE, pero no tienen la misma representación de objeto. Entonces, en realidad no debería haber dicho que funciona en "casi todos los compiladores", fallará en cualquier implementación que le permita almacenar un cero negativo. Probablemente estaba pensando en representaciones enteras divertidas cuando hice el comentario.
Steve Jessop
44
@Demetri: pero muchos contienen flotantes, y el interlocutor pregunta "¿cómo se comparan las estructuras", no "cómo se comparan las estructuras que no contienen flotantes". Esta respuesta dice que puede hacer una comparación superficial memcmpsiempre que la memoria se haya borrado primero. Lo cual está cerca de funcionar pero no es correcto. Por lo general, la pregunta tampoco define "igualdad", por lo que si lo toma como "igualdad de bytes de la representación del objeto", entonces memcmphace exactamente eso (ya sea que la memoria se borre o no).
Steve Jessop
22

Si lo haces mucho, te sugiero que escribas una función que compare las dos estructuras. De esa manera, si alguna vez cambia la estructura, solo necesita cambiar la comparación en un solo lugar.

En cuanto a cómo hacerlo ... Necesita comparar cada elemento individualmente

Ben
fuente
1
Escribiría una función separada incluso si la usara solo una vez.
Sam
18

No puede usar memcmp para comparar estructuras para la igualdad debido a posibles caracteres de relleno aleatorio entre campos en estructuras.

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

Lo anterior fallaría para una estructura como esta:

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

Debe usar la comparación entre miembros para estar seguro.


fuente
25
Es poco probable que se rellene después del doble; el carbón estará perfectamente alineado inmediatamente después del doble.
Jonathan Leffler
7

@Greg tiene razón en que uno debe escribir funciones de comparación explícitas en el caso general.

Es posible usar memcmpsi:

  • las estructuras no contienen campos de punto flotante que posiblemente sean NaN.
  • las estructuras no contienen relleno (use -Wpaddedcon clang para verificar esto) O las estructuras se inicializan explícitamente con memseten la inicialización.
  • no hay tipos de miembros (como Windows BOOL) que tengan valores distintos pero equivalentes.

A menos que esté programando para sistemas integrados (o escribiendo una biblioteca que podría usarse en ellos), no me preocuparía por algunos de los casos de esquina en el estándar C. La distinción de puntero cercano versus lejano no existe en ningún dispositivo de 32 o 64 bits. Ningún sistema no embebido que conozco tiene múltiples NULLpunteros.

Otra opción es generar automáticamente las funciones de igualdad. Si presenta sus definiciones de estructura de una manera simple, es posible utilizar un procesamiento de texto simple para manejar definiciones de estructura simples. Puede usar libclang para el caso general, ya que usa la misma interfaz que Clang, maneja todos los casos de esquina correctamente (salvo errores).

No he visto una biblioteca de generación de código de este tipo. Sin embargo, parece relativamente simple.

Sin embargo, también es el caso de que tales funciones de igualdad generadas a menudo harían algo incorrecto a nivel de aplicación. Por ejemplo, ¿deberían dos UNICODE_STRINGestructuras en Windows compararse superficial o profundamente?

Demi
fuente
2
Inicializar explícitamente las estructuras con memset, etc., no garantiza el valor de los bits de relleno después de escribir más en un elemento de estructura, consulte: stackoverflow.com/q/52684192/689161
gengkev
4

Tenga en cuenta que puede usar memcmp () en estructuras no estáticas sin preocuparse por el relleno, siempre que no inicialice todos los miembros (a la vez). Esto está definido por C90:

http://www.pixelbeat.org/programming/gcc/auto_init.html

pixelbeat
fuente
1
¿Se especifica realmente que {0, }también pondrá a cero los bytes de relleno?
Alnitak
GCC al menos pone a cero los bytes de relleno para estructuras parcialmente inicializadas como se demuestra en el enlace anterior, y stackoverflow.com/questions/13056364/… detalla que C11 especifica ese comportamiento.
pixelbeat
1
No es muy útil en general, porque todo el relleno se vuelve indeterminado al asignarlo a cualquier miembro
MM
2

Depende de si la pregunta que hace es:

  1. ¿Son estas dos estructuras el mismo objeto?
  2. ¿Tienen el mismo valor?

Para saber si son el mismo objeto, compare los punteros con las dos estructuras para la igualdad. Si desea averiguar en general si tienen el mismo valor, debe hacer una comparación profunda. Esto implica comparar a todos los miembros. Si los miembros son punteros a otras estructuras, también debe recurrir a esas estructuras.

En el caso especial donde las estructuras no contienen punteros, puede hacer un memcmp para realizar una comparación bit a bit de los datos contenidos en cada uno sin tener que saber qué significan los datos.

Asegúrese de saber lo que significa "igual" para cada miembro: es obvio para los ints pero más sutil cuando se trata de valores de punto flotante o tipos definidos por el usuario.

domgblackwell
fuente
2

memcmpno compara la estructura, memcmpcompara el binario, y siempre hay basura en la estructura, por lo tanto, siempre sale Falso en comparación.

Compare elemento por elemento es seguro y no falla.

sergio
fuente
1
si las variables de 2 estructuras se inicializan con calloc o si se configuran con 0 por memset para que pueda comparar sus 2 estructuras con memcmp y no se preocupe por la basura de la estructura y esto le permitirá ganar tiempo
MOHAMED
1

Si las estructuras solo contienen primitivas o si está interesado en la igualdad estricta, puede hacer algo como esto:

int my_struct_cmp (const struct my_struct * lhs, const struct my_struct * rhs)
{
    return memcmp (lhs, rsh, sizeof (struct my_struct));
}

Sin embargo, si sus estructuras contienen punteros a otras estructuras o uniones, deberá escribir una función que compare las primitivas correctamente y realizar llamadas de comparación contra las otras estructuras, según corresponda.

Sin embargo, tenga en cuenta que debería haber utilizado memset (& a, sizeof (struct my_struct), 1) para poner a cero el rango de memoria de las estructuras como parte de su inicialización ADT.

Kevin S.
fuente
-1

si las variables de 2 estructuras se inicializan con calloc o se establecen con 0 por memset para que pueda comparar sus 2 estructuras con memcmp y no se preocupe por la basura de la estructura y esto le permitirá ganar tiempo

MOHAMED
fuente
-2

Este ejemplo compatible utiliza la extensión del compilador #pragma pack de Microsoft Visual Studio para garantizar que los miembros de la estructura estén empaquetados de la manera más ajustada posible:

#include <string.h>

#pragma pack(push, 1)
struct s {
  char c;
  int i;
  char buffer[13];
};
#pragma pack(pop)

void compare(const struct s *left, const struct s *right) { 
  if (0 == memcmp(left, right, sizeof(struct s))) {
    /* ... */
  }
}
Hesham Eraqi
fuente
1
Eso es realmente correcto. ¡Pero en la mayoría de los casos no desea que sus estructuras estén empaquetadas! Muchas instrucciones y punteros requieren que los datos de entrada estén alineados con palabras. Si no es así, entonces el compilador necesita agregar instrucciones adicionales para copiar y realinear los datos antes de que se pueda ejecutar la instrucción real. Si el compilador no realinearía los datos, la CPU lanzará una excepción.
Ruud Althuizen