Comment comparez-vous les structures d'égalité en C?

216

Comment comparez-vous deux instances de structures pour l'égalité dans le standard C?

Hans Sjunnesson
la source

Réponses:

196

C ne fournit aucun équipement linguistique pour ce faire - vous devez le faire vous-même et comparer chaque membre de la structure membre par membre.

Greg Hewgill
la source
19
si la variable 2 structures est initialisée avec calloc ou si elle est définie sur 0 par memset, vous pouvez comparer vos 2 structures avec memcmp et vous n'avez pas à vous soucier des ordures de la structure et cela vous permettra de gagner du temps
MOHAMED
21
@MOHAMED La comparaison des champs à virgule flottante avec 0.0, -0.0 NaNest un problème avec memcmp(). Les pointeurs qui diffèrent dans la représentation binaire peuvent pointer vers le même emplacement (par exemple DOS: seg: offset) et sont donc égaux. Certains systèmes ont plusieurs pointeurs nuls qui se comparent également. Idem pour les inttypes obscur avec -0 et à virgule flottante avec les encodages redondants. (Intel long double, decimal64, etc.) Ces problèmes ne font aucune différence est calloc()utilisé ou non ou de remplissage.
chux
2
@chux Sur tout système moderne 32 ou 64 bits que je connais, le seul problème est avec la virgule flottante.
Demi
2
Dans le cas où vous vous demandez pourquoi ==ne fonctionne pas avec des structures (comme moi), veuillez consulter stackoverflow.com/questions/46995631/…
stefanct
4
@Demi: Aujourd'hui. Le dixième commandement pour les programmeurs C est: «Tu feras preuve de précaution, tu renonceras et tu abjureras l'hérésie ignoble qui prétend que« Tout le monde est un VAX »...». Le remplacer par "Tout le monde est un PC" n'est pas une amélioration.
Martin Bonner soutient Monica
110

Vous pouvez être tenté d'utiliser memcmp(&a, &b, sizeof(struct foo)), mais cela peut ne pas fonctionner dans toutes les situations. Le compilateur peut ajouter un espace tampon d'alignement à une structure, et les valeurs trouvées aux emplacements de mémoire situés dans l'espace tampon ne sont pas garanties être une valeur particulière.

Mais, si vous utilisez callocou memsetla taille complète des structures avant de les utiliser, vous pouvez faire une comparaison superficielle avec memcmp(si votre structure contient des pointeurs, elle ne correspondra que si l'adresse vers laquelle les pointeurs pointent est la même).

Sufian
la source
19
Fermer, car il fonctionne sur "presque tous" les compilateurs, mais pas tout à fait. Consultez 6.2.1.6.4 dans C90: "Deux valeurs (autres que NaN) avec la même représentation d'objet comparent égal, mais les valeurs qui comparent égal peuvent avoir des représentations d'objet différentes."
Steve Jessop
22
Considérons un champ "BOOL". En termes d'égalité, tout BOOL non nul est égal à chaque valeur BOOL non nulle. Ainsi, alors que 1 et 2 peuvent tous deux être VRAIS et donc égaux, memcmp échouera.
ajs410
4
@JSalazar Plus facile pour vous peut-être, mais beaucoup plus difficile pour le compilateur et le CPU et donc aussi beaucoup plus lent. Pourquoi pensez-vous que le compilateur ajoute un remplissage en premier lieu? Certainement pour ne pas perdre de mémoire pour rien;)
Mecki
4
@Demetri: par exemple, les valeurs flottantes positives et négatives zéro sont égales sur n'importe quelle implémentation flottante IEEE, mais elles n'ont pas la même représentation d'objet. Donc, en fait, je n'aurais pas dû dire que cela fonctionne sur "presque tous les compilateurs", il échouera sur toute implémentation qui vous permet de stocker un zéro négatif. Je pensais probablement à des représentations entières drôles au moment où j'ai fait le commentaire.
Steve Jessop
4
@Demetri: mais beaucoup contiennent des flottants, et le questionneur demande "comment comparez-vous les structures", et non "comment comparez-vous les structures qui ne contiennent pas de flottants". Cette réponse indique que vous pouvez faire une comparaison superficielle avec à memcmpcondition que la mémoire ait été effacée en premier. Ce qui est proche de fonctionner mais pas correct. Ofc, la question ne définit pas non plus "égalité", donc si vous la prenez pour signifier "égalité octet de la représentation de l'objet", cela memcmpfait exactement cela (que la mémoire soit effacée ou non).
Steve Jessop
22

Si vous le faites beaucoup, je suggère d'écrire une fonction qui compare les deux structures. De cette façon, si jamais vous changez la structure, il vous suffit de changer la comparaison en un seul endroit.

Quant à savoir comment le faire .... Vous devez comparer chaque élément individuellement

Ben
la source
1
J'écrirais une fonction distincte même si je ne devais l'utiliser qu'une seule fois.
Sam
18

Vous ne pouvez pas utiliser memcmp pour comparer les structures pour l'égalité en raison des caractères de remplissage aléatoires potentiels entre les champs dans les structures.

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

Ce qui précède échouerait pour une structure comme celle-ci:

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

Vous devez utiliser la comparaison par membre pour être sûr.


la source
25
Peu susceptible d'être rembourré après le double; le char sera parfaitement aligné de manière adéquate immédiatement après le double.
Jonathan Leffler
7

@Greg a raison de dire qu'il faut écrire des fonctions de comparaison explicites dans le cas général.

Il est possible d'utiliser memcmpsi:

  • les structures ne contiennent aucun champ à virgule flottante qui le soit NaN.
  • les structures ne contiennent pas de remplissage (utilisez -Wpaddedavec clang pour vérifier cela) OU les structures sont explicitement initialisées avec memsetà l'initialisation.
  • aucun type de membre (tel que Windows BOOL) n'a de valeurs distinctes mais équivalentes.

À moins que vous ne programmiez pour des systèmes embarqués (ou que vous n'écriviez une bibliothèque qui pourrait être utilisée sur eux), je ne m'inquiéterais pas de certains des cas d'angle dans la norme C. La distinction pointeur proche / éloigné n'existe sur aucun périphérique 32 ou 64 bits. Aucun système non intégré à ma connaissance ne possède plusieurs NULLpointeurs.

Une autre option consiste à générer automatiquement les fonctions d'égalité. Si vous disposez vos définitions de structure de manière simple, il est possible d'utiliser un traitement de texte simple pour gérer des définitions de structure simples. Vous pouvez utiliser libclang pour le cas général - car il utilise le même frontend que Clang, il gère correctement tous les cas d'angle (sauf bogues).

Je n'ai pas vu une telle bibliothèque de génération de code. Cependant, cela semble relativement simple.

Cependant, il est également vrai que de telles fonctions d'égalité générées feraient souvent la mauvaise chose au niveau de l'application. Par exemple, deux UNICODE_STRINGstructures dans Windows doivent-elles être comparées de manière superficielle ou profonde?

Demi
la source
2
L'initialisation explicite des structures avec memset, etc. ne garantit pas la valeur des bits de remplissage après une nouvelle écriture dans un élément struct, voir: stackoverflow.com/q/52684192/689161
gengkev
4

Notez que vous pouvez utiliser memcmp () sur des structures non statiques sans vous soucier du remplissage, tant que vous n'initialisez pas tous les membres (à la fois). Ceci est défini par C90:

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

pixelbeat
la source
1
Est-il réellement spécifié qu'il {0, }mettra également à zéro tous les octets de remplissage?
Alnitak
GCC remet au moins à zéro les octets de remplissage pour les structures partiellement initialisées, comme illustré sur le lien ci-dessus, et stackoverflow.com/questions/13056364/… détaille que C11 spécifie ce comportement.
pixelbeat
1
Pas très utile en général, car tout le rembourrage devient indéterminé lors de l'affectation à un membre
MM
2

Cela dépend si la question que vous posez est:

  1. Ces deux structures sont-elles le même objet?
  2. Ont-ils la même valeur?

Pour savoir s'il s'agit du même objet, comparez les pointeurs aux deux structures pour l'égalité. Si vous voulez savoir en général s'ils ont la même valeur, vous devez faire une comparaison approfondie. Cela implique de comparer tous les membres. Si les membres sont des pointeurs vers d'autres structures, vous devez également revenir dans ces structures.

Dans le cas spécial où les structures ne contiennent pas de pointeurs, vous pouvez effectuer un memcmp pour effectuer une comparaison au niveau du bit des données contenues dans chacun sans avoir à savoir ce que les données signifient.

Assurez-vous que vous savez ce que «égal» signifie pour chaque membre - c'est évident pour les entiers mais plus subtil quand il s'agit de valeurs à virgule flottante ou de types définis par l'utilisateur.

domgblackwell
la source
2

memcmpne compare pas la structure, memcmpcompare le binaire, et il y a toujours des ordures dans la structure, donc il sort toujours faux en comparaison.

Comparez élément par élément son coffre-fort et ne manque pas.

sergio
la source
1
si la variable 2 structures est initialisée avec calloc ou si elle est définie sur 0 par memset, vous pouvez comparer vos 2 structures avec memcmp et vous n'avez pas à vous soucier des ordures de la structure et cela vous permettra de gagner du temps
MOHAMED
1

Si les structures contiennent uniquement des primitives ou si vous êtes intéressé par une stricte égalité, vous pouvez faire quelque chose comme ceci:

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

Cependant, si vos structures contiennent des pointeurs vers d'autres structures ou unions, vous devrez écrire une fonction qui compare correctement les primitives et effectuer des appels de comparaison avec les autres structures, le cas échéant.

Cependant, sachez que vous auriez dû utiliser memset (& a, sizeof (struct my_struct), 1) pour remettre à zéro la plage de mémoire des structures dans le cadre de votre initialisation ADT.

Kevin S.
la source
-1

si la variable 2 structures est initialisée avec calloc ou si elle est définie sur 0 par memset, vous pouvez comparer vos 2 structures avec memcmp et vous n'avez pas à vous soucier des ordures de la structure et cela vous permettra de gagner du temps

MOHAMED
la source
-2

Cet exemple conforme utilise l'extension de compilateur #pragma pack de Microsoft Visual Studio pour garantir que les membres de la structure sont compressés aussi étroitement que possible:

#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
la source
1
C'est bien cela. Mais dans la plupart des cas, vous ne voulez pas que vos structures soient compressées! Un grand nombre d'instructions et de pointeurs nécessitent que les données d'entrée soient alignées sur les mots. Si ce n'est pas le cas, le compilateur doit ajouter des instructions supplémentaires pour copier et réaligner les données avant que l'instruction réelle puisse être exécutée. Si le compilateur ne réaligne pas les données, le CPU lèvera une exception.
Ruud Althuizen