Wie vergleicht man Strukturen für Gleichheit in C?

216

Wie vergleichen Sie zwei Instanzen von Strukturen auf Gleichheit in Standard C?

Hans Sjunnesson
quelle

Antworten:

196

C bietet hierfür keine Sprachfunktionen - Sie müssen dies selbst tun und jede Struktur Mitglied für Mitglied vergleichen.

Greg Hewgill
quelle
19
Wenn die Variablen mit 2 Strukturen mit calloc initialisiert werden oder mit memset auf 0 gesetzt werden, können Sie Ihre 2 Strukturen mit memcmp vergleichen, und Sie müssen sich keine Sorgen um Strukturmüll machen. Dadurch können Sie Zeit verdienen
MOHAMED
21
@MOHAMED Der Vergleich von Gleitkommafeldern mit 0.0, -0.0 NaNist ein Problem mit memcmp(). Zeiger, die sich in der binären Darstellung unterscheiden, können auf dieselbe Position zeigen (z. B. DOS: seg: offset) und sind daher gleich. Einige Systeme haben mehrere Nullzeiger, die sich gleichermaßen vergleichen lassen. Gleiches gilt für Obskur intmit -0- und Gleitkommatypen mit redundanten Codierungen. (Intel Long Double, Decimal64 usw.) Diese Probleme machen keinen Unterschied, ob sie calloc()verwendet werden oder nicht.
chux
2
@chux Auf jedem modernen 32- oder 64-Bit-System, das ich kenne, ist das einzige Problem das Gleitkomma.
Demi
2
Falls Sie sich fragen, warum Sie ==nicht mit Strukturen arbeiten (wie ich), lesen
stefanct
4
@ Demi: Heute. Das 10. Gebot für C-Programmierer lautet: "Du sollst die abscheuliche Häresie, die besagt, dass" die ganze Welt eine VAX ist "... voraussehen, aufgeben und abschwören. Das Ersetzen durch "Die ganze Welt ist ein PC" ist keine Verbesserung.
Martin Bonner unterstützt Monica
110

Sie könnten versucht sein, es zu verwenden memcmp(&a, &b, sizeof(struct foo)), aber es funktioniert möglicherweise nicht in allen Situationen. Der Compiler kann einer Struktur einen Ausrichtungspufferraum hinzufügen, und es wird nicht garantiert, dass die Werte, die an Speicherstellen gefunden werden, die im Pufferraum liegen, ein bestimmter Wert sind.

Wenn Sie jedoch die Strukturen callocoder memsetdie volle Größe der Strukturen verwenden, bevor Sie sie verwenden, können Sie einen flachen Vergleich mit durchführen memcmp(wenn Ihre Struktur Zeiger enthält, stimmt sie nur überein, wenn die Adresse, auf die die Zeiger zeigen, identisch ist).

Sufian
quelle
19
Schließen, weil es auf "fast allen" Compilern funktioniert, aber nicht ganz. Lesen Sie 6.2.1.6.4 in C90: "Zwei Werte (außer NaNs) mit derselben Objektdarstellung sind gleich, aber Werte, die gleich sind, können unterschiedliche Objektdarstellungen haben."
Steve Jessop
22
Betrachten Sie ein "BOOL" -Feld. In Bezug auf die Gleichheit ist jede BOOL ungleich Null gleich jedem BOOL-Wert ungleich Null. Während also 1 und 2 beide WAHR und daher gleich sein können, schlägt memcmp fehl.
Ajs410
4
@JSalazar Vielleicht einfacher für Sie, aber viel schwieriger für den Compiler und die CPU und damit auch viel langsamer. Warum fügt der Compiler Ihrer Meinung nach überhaupt Padding hinzu? Sicherlich nicht umsonst Gedächtnis verschwenden;)
Mecki
4
@Demetri: Zum Beispiel sind die Float-Werte positiv und negativ Null bei jeder IEEE-Float-Implementierung gleich, aber sie haben nicht die gleiche Objektdarstellung. Eigentlich hätte ich nicht sagen sollen, dass es auf "fast allen Compilern" funktioniert, es wird bei jeder Implementierung fehlschlagen, bei der Sie eine negative Null speichern können. Ich dachte wahrscheinlich an lustige ganzzahlige Darstellungen, als ich den Kommentar machte.
Steve Jessop
4
@ Demetri: aber viele enthalten Floats, und der Fragesteller fragt "Wie vergleichst du Strukturen?", Nicht "Wie vergleichst du Strukturen, die keine Floats enthalten?". Diese Antwort besagt, dass Sie einen flachen Vergleich mit durchführen können, memcmpvorausgesetzt, der Speicher wurde zuerst gelöscht. Welches ist nah an der Arbeit, aber nicht korrekt. Natürlich definiert die Frage auch nicht "Gleichheit". Wenn Sie also "byteweise Gleichheit der Objektdarstellung" meinen, dann memcmptut dies genau das (ob der Speicher gelöscht wird oder nicht).
Steve Jessop
22

Wenn Sie viel tun, würde ich vorschlagen, eine Funktion zu schreiben, die die beiden Strukturen vergleicht. Auf diese Weise müssen Sie den Vergleich nur an einer Stelle ändern, wenn Sie jemals die Struktur ändern.

Wie es geht ... Sie müssen jedes Element einzeln vergleichen

Ben
quelle
1
Ich würde eine separate Funktion schreiben, selbst wenn ich sie nur einmal verwenden würde.
Sam
18

Sie können memcmp nicht verwenden, um Strukturen auf Gleichheit zu vergleichen, da möglicherweise zufällige Auffüllzeichen zwischen Feldern in Strukturen vorhanden sind.

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

Das Obige würde für eine Struktur wie diese fehlschlagen:

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

Sie müssen einen Vergleich nach Mitgliedern verwenden, um sicher zu sein.


quelle
25
Es ist unwahrscheinlich, dass nach dem Double gepolstert wird. Der Saibling wird unmittelbar nach dem Double perfekt ausgerichtet.
Jonathan Leffler
7

@ Greg ist richtig, dass man im allgemeinen Fall explizite Vergleichsfunktionen schreiben muss.

Es ist möglich zu verwenden, memcmpwenn:

  • Die Strukturen enthalten möglicherweise keine Gleitkommafelder NaN.
  • Die Strukturen enthalten kein Auffüllen (verwenden Sie dies -Wpaddedmit clang, um dies zu überprüfen) ODER die Strukturen werden memsetbei der Initialisierung explizit mit initialisiert.
  • Es gibt keine Elementtypen (z. B. Windows BOOL) mit unterschiedlichen, aber äquivalenten Werten.

Wenn Sie nicht für eingebettete Systeme programmieren (oder eine Bibliothek schreiben, die möglicherweise auf ihnen verwendet wird), würde ich mir keine Gedanken über einige der Eckfälle im C-Standard machen. Die Nah- und Fernzeigerunterscheidung existiert auf keinem 32- oder 64-Bit-Gerät. Kein mir bekanntes nicht eingebettetes System verfügt über mehrere NULLZeiger.

Eine andere Möglichkeit besteht darin, die Gleichheitsfunktionen automatisch zu generieren. Wenn Sie Ihre Strukturdefinitionen auf einfache Weise anordnen, können Sie mithilfe einfacher Textverarbeitung einfache Strukturdefinitionen verarbeiten. Sie können libclang für den allgemeinen Fall verwenden - da es dasselbe Frontend wie Clang verwendet, werden alle Eckfälle korrekt behandelt (mit Ausnahme von Fehlern).

Ich habe eine solche Codegenerierungsbibliothek nicht gesehen. Es erscheint jedoch relativ einfach.

Es ist jedoch auch der Fall, dass solche generierten Gleichheitsfunktionen auf Anwendungsebene häufig das Falsche tun. UNICODE_STRINGSollten beispielsweise zwei Strukturen in Windows flach oder tief verglichen werden?

Demi
quelle
2
Das explizite Initialisieren der Strukturen mit memsetusw. garantiert nicht den Wert der Füllbits nach dem weiteren Schreiben in ein Strukturelement, siehe: stackoverflow.com/q/52684192/689161
gengkev
4

Beachten Sie, dass Sie memcmp () für nicht statische Strukturen verwenden können, ohne sich Gedanken über das Auffüllen machen zu müssen, solange Sie nicht alle Mitglieder (auf einmal) initialisieren. Dies wird durch C90 definiert:

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

Pixelbeat
quelle
1
Ist tatsächlich angegeben, dass {0, }auch Auffüllbytes auf Null gesetzt werden?
Alnitak
GCC setzt mindestens Auffüllbytes für teilweise initialisierte Strukturen auf Null, wie unter dem obigen Link gezeigt, und stackoverflow.com/questions/13056364/… gibt an, dass C11 dieses Verhalten angibt.
Pixelbeat
1
Im Allgemeinen nicht sehr nützlich, da alle Polster bei der Zuweisung an ein Mitglied unbestimmt werden
MM
2

Es hängt davon ab, ob die Frage, die Sie stellen, lautet:

  1. Sind diese beiden Strukturen dasselbe Objekt?
  2. Haben sie den gleichen Wert?

Um herauszufinden, ob es sich um dasselbe Objekt handelt, vergleichen Sie die Zeiger auf die beiden Strukturen, um die Gleichheit zu gewährleisten. Wenn Sie allgemein herausfinden möchten, ob sie den gleichen Wert haben, müssen Sie einen gründlichen Vergleich durchführen. Dies beinhaltet den Vergleich aller Mitglieder. Wenn die Mitglieder Zeiger auf andere Strukturen sind, müssen Sie auch in diese Strukturen zurückgreifen.

In dem speziellen Fall, in dem die Strukturen keine Zeiger enthalten, können Sie ein memcmp ausführen, um einen bitweisen Vergleich der jeweils enthaltenen Daten durchzuführen, ohne wissen zu müssen, was die Daten bedeuten.

Stellen Sie sicher, dass Sie wissen, was "gleich" für jedes Mitglied bedeutet - es ist für Ints offensichtlich, aber subtiler, wenn es um Gleitkommawerte oder benutzerdefinierte Typen geht.

domgblackwell
quelle
2

memcmpvergleicht die Struktur nicht, memcmpvergleicht die Binärdatei und es gibt immer Müll in der Struktur, daher kommt sie im Vergleich immer als falsch heraus.

Vergleichen Sie Element für Element, es ist sicher und schlägt nicht fehl.

Sergio
quelle
1
Wenn die Variablen mit 2 Strukturen mit calloc initialisiert werden oder mit memset auf 0 gesetzt werden, können Sie Ihre 2 Strukturen mit memcmp vergleichen, und Sie müssen sich keine Sorgen um Strukturmüll machen. Dadurch können Sie Zeit verdienen
MOHAMED
1

Wenn die Strukturen nur Grundelemente enthalten oder wenn Sie an strikter Gleichheit interessiert sind, können Sie Folgendes tun:

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

Wenn Ihre Strukturen jedoch Zeiger auf andere Strukturen oder Vereinigungen enthalten, müssen Sie eine Funktion schreiben, die die Grundelemente ordnungsgemäß vergleicht, und gegebenenfalls Vergleichsaufrufe mit den anderen Strukturen durchführen.

Beachten Sie jedoch, dass Sie memset (& a, sizeof (struct my_struct), 1) verwendet haben sollten, um den Speicherbereich der Strukturen im Rahmen Ihrer ADT-Initialisierung auf Null zu setzen.

Kevin S.
quelle
-1

Wenn die Variablen mit 2 Strukturen mit calloc initialisiert werden oder mit memset auf 0 gesetzt werden, können Sie Ihre 2 Strukturen mit memcmp vergleichen, und Sie müssen sich keine Sorgen um Strukturmüll machen. Dadurch können Sie Zeit verdienen

MOHAMED
quelle
-2

In diesem kompatiblen Beispiel wird die Compiler-Erweiterung #pragma pack von Microsoft Visual Studio verwendet, um sicherzustellen, dass die Strukturelemente so dicht wie möglich gepackt sind:

#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
quelle
1
Das ist in der Tat richtig. In den meisten Fällen möchten Sie jedoch nicht, dass Ihre Strukturen gepackt werden! Viele Anweisungen und Zeiger erfordern, dass die Eingabedaten wortausgerichtet sind. Ist dies nicht der Fall, muss der Compiler zusätzliche Anweisungen hinzufügen, um Daten zu kopieren und neu auszurichten, bevor die eigentliche Anweisung ausgeführt werden kann. Wenn der Compiler die Daten nicht neu ausrichten würde, löst die CPU eine Ausnahme aus.
Ruud Althuizen