Jak porównać struktury dla równości w C?

216

Jak porównać dwa przypadki struktur dla równości w standardowym C?

Hans Sjunnesson
źródło

Odpowiedzi:

196

C nie udostępnia żadnych narzędzi językowych, aby to zrobić - musisz to zrobić sam i porównać każdego członka struktury według członka.

Greg Hewgill
źródło
19
jeśli zmienna 2 struktur jest inicjowana za pomocą calloc lub jest ustawiona na 0 przez memset, możesz więc porównać swoje 2 struktury z memcmp i nie martw się o śmieci struktur, a to pozwoli ci zarobić czas
MOHAMED
21
@MOHAMED Porównując pływających pól punktowych ze 0.0, -0.0 NaNjest problem z memcmp(). Wskaźniki, które różnią się reprezentacją binarną, mogą wskazywać na tę samą lokalizację (np. DOS: seg: offset), a więc są równe. Niektóre systemy mają wiele zerowych wskaźników, które porównują się jednakowo. To samo dotyczy niejasnych inttypów -0 i zmiennoprzecinkowych z kodowaniem redundantnym. (Intel long double, decimal64 itp.) Te problemy nie mają calloc()znaczenia, czy są używane, czy nie, ani padding.
chux - Przywróć Monikę
2
@chux W każdym znanym mi systemie 32- lub 64-bitowym jedynym problemem jest zmiennoprzecinkowy.
Demi
2
Jeśli zastanawiasz się, dlaczego ==nie działa ze strukturami (jak ja), zobacz stackoverflow.com/questions/46995631/...
stefanct
4
@Demi: Dzisiaj. Dziesiąte przykazanie dla programistów C brzmi: „Napastnikujesz, wyrzekasz się i odrzucasz ohydną herezję, która głosi, że„ Cały świat jest VAX ”...”. Zastąpienie go słowem „Cały komputer na świecie” nie stanowi ulepszenia.
Martin Bonner wspiera Monikę
110

Możesz ulec pokusie użycia memcmp(&a, &b, sizeof(struct foo)) , ale może nie działać we wszystkich sytuacjach. Kompilator może dodawać przestrzeń bufora wyrównania do struktury, a wartości znalezione w lokalizacjach pamięci znajdujących się w przestrzeni bufora nie są gwarantowane, że będą jakąś szczególną wartością.

Ale jeśli używasz calloclub memsetpełny rozmiar struktur przed ich użyciem, to może zrobić płytkie porównanie z memcmp(jeśli struktura zawiera wskaźniki, będzie pasować tylko wtedy, gdy adres Wskaźniki wskazują na to samo).

Sufian
źródło
19
Zamknij, ponieważ działa na „prawie wszystkich” kompilatorach, ale nie do końca. Sprawdź wersję 6.2.1.6.4 w C90: „Dwie wartości (inne niż NaN) z tą samą reprezentacją obiektu są równe, ale wartości, które są równe, mogą mieć różne reprezentacje obiektu”.
Steve Jessop
22
Rozważ pole „BOOL”. Pod względem równości, każda niezerowa wartość BOOL jest równa każdej niezerowej wartości BOOL. Tak więc, chociaż 1 i 2 mogą być PRAWDZIWE, a zatem równe, memcmp się nie powiedzie.
ajs410 16.04.13
4
@JSalazar Być może łatwiejsze dla ciebie, ale znacznie trudniejsze dla kompilatora i procesora, a więc także znacznie wolniejsze. Jak myślisz, dlaczego kompilator dodaje padding w pierwszej kolejności? Z pewnością nie marnujmy pamięci za nic;)
Mecki
4
@Demetri: na przykład wartości zmiennoprzecinkowe dodatnie i ujemne zero są równe w dowolnej implementacji zmiennoprzecinkowej IEEE, ale nie mają takiej samej reprezentacji obiektu. Więc właściwie nie powinienem był mówić, że działa na „prawie wszystkich kompilatorach”, zawiedzie w każdej implementacji, która pozwala przechowywać ujemne zero. Prawdopodobnie myślałem o zabawnych reprezentacjach liczb całkowitych w momencie, gdy komentowałem.
Steve Jessop,
4
@Demetri: ale wiele zawiera zmiennoprzecinkowe, a pytający pyta „jak porównujesz struktury”, a nie „jak porównujesz struktury, które nie zawierają liczb zmiennoprzecinkowych”. Ta odpowiedź mówi, że możesz wykonać płytkie porównanie z memcmpwarunkiem, że pamięć została najpierw wyczyszczona. Co jest bliskie pracy, ale nie jest poprawne. Często pytanie również nie definiuje „równości”, więc jeśli przyjmiesz, że oznacza to „bajtową równość reprezentacji obiektu”, to memcmprobi dokładnie to (niezależnie od tego, czy pamięć jest wyczyszczona, czy nie).
Steve Jessop,
22

Jeśli często to robisz, sugerowałbym napisanie funkcji porównującej dwie struktury. W ten sposób, jeśli kiedykolwiek zmienisz strukturę, wystarczy zmienić porównanie w jednym miejscu.

Co do tego, jak to zrobić ... Musisz porównać każdy element indywidualnie

Ben
źródło
1
Napisałbym osobną funkcję, nawet gdybym jej używał tylko raz.
Sam
18

Nie można używać memcmp do porównywania struktur dla równości ze względu na potencjalne losowe wypełnianie znaków między polami w strukturach.

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

Powyższe nie powiedzie się dla struktury takiej jak ta:

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

Aby być bezpiecznym, musisz użyć porównania członków.


źródło
25
Jest mało prawdopodobne, aby padding po podwójnym; znak zostanie idealnie wyrównany natychmiast po podwójnym.
Jonathan Leffler
7

@Greg ma rację, że w ogólnym przypadku należy napisać wyraźne funkcje porównawcze.

Można użyć, memcmpjeśli:

  • struktury nie zawierają ewentualnych pól zmiennoprzecinkowych NaN.
  • struktury nie zawierają dopełnienia (użyj -Wpaddedz clang, aby to sprawdzić) LUB struktury są jawnie inicjowane memsetprzy inicjalizacji.
  • nie ma typów elementów (takich jak Windows BOOL), które mają odrębne, ale równoważne wartości.

O ile nie programujesz dla systemów wbudowanych (lub piszesz bibliotekę, która może być na nich użyta), nie martwiłbym się niektórymi przypadkami narożnymi w standardzie C. Rozróżnienie wskaźnika bliskiego i dalekiego nie istnieje na żadnym urządzeniu 32- lub 64-bitowym. Żaden znany mi system niewbudowany nie ma wielu NULLwskaźników.

Inną opcją jest automatyczne generowanie funkcji równości. Jeśli rozłożysz definicje struktur w prosty sposób, możliwe jest użycie prostego przetwarzania tekstu do obsługi prostych definicji struktur. Możesz użyć libclang do ogólnego przypadku - ponieważ używa tej samej nakładki co Clang, poprawnie obsługuje wszystkie przypadki narożne (z wyjątkiem błędów).

Nie widziałem takiej biblioteki do generowania kodu. Wydaje się jednak stosunkowo prosty.

Jednak zdarza się również, że takie generowane funkcje równości często robią coś złego na poziomie aplikacji. Na przykład, czy dwie UNICODE_STRINGstruktury w systemie Windows powinny być porównywane płytko czy głęboko?

Demi
źródło
2
Jawne inicjowanie struktur za pomocą memsetitp. Nie gwarantuje wartości bitów dopełniających po dalszym zapisywaniu do elementu struct, patrz: stackoverflow.com/q/52684192/689161
gengkev
4

Pamiętaj, że możesz używać memcmp () na statycznych konstrukcjach bez obawy o wypełnianie, o ile nie zainicjujesz wszystkich elementów (jednocześnie). Jest to zdefiniowane przez C90:

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

pixelbeat
źródło
1
Czy rzeczywiście określono, że {0, }wyzeruje również bajty wypełniania?
Alnitak,
GCC przynajmniej zeruje bajty wypełniające dla częściowo zainicjowanych struktur, jak pokazano w powyższym linku, oraz stackoverflow.com/questions/13056364/… szczegóły, że C11 określa to zachowanie.
pixelbeat,
1
Ogólnie niezbyt przydatne, ponieważ całe wypełnienie staje się nieokreślone po przypisaniu do dowolnego członka
MM
2

Zależy to od tego, czy zadajesz pytanie:

  1. Czy te dwie struktury to ten sam obiekt?
  2. Czy mają taką samą wartość?

Aby dowiedzieć się, czy są to ten sam obiekt, porównaj wskaźniki do dwóch struktur zapewniających równość. Jeśli chcesz dowiedzieć się ogólnie, czy mają taką samą wartość, musisz dokonać głębokiego porównania. Polega to na porównaniu wszystkich członków. Jeśli członkowie są wskaźnikami do innych struktur, musisz również powrócić do tych struktur.

W szczególnym przypadku, gdy struktury nie zawierają wskaźników, możesz wykonać memcmp, aby wykonać bitowe porównanie danych zawartych w każdym z nich bez konieczności poznania znaczenia danych.

Upewnij się, że wiesz, co oznacza „równa się” dla każdego elementu członkowskiego - jest to oczywiste dla liczb całkowitych, ale bardziej subtelne, jeśli chodzi o wartości zmiennoprzecinkowe lub typy zdefiniowane przez użytkownika.

domgblackwell
źródło
2

memcmpnie porównuje struktury, memcmpporównuje plik binarny, a struktura zawsze zawiera śmieci, dlatego zawsze wychodzi Fałsz w porównaniu.

Porównaj element po elemencie, jest bezpieczny i nie zawiedzie.

sergio
źródło
1
jeśli zmienna 2 struktur jest inicjowana za pomocą calloc lub jest ustawiona na 0 przez memset, możesz więc porównać swoje 2 struktury z memcmp i nie martw się o śmieci struktur, a to pozwoli ci zarobić czas
MOHAMED
1

Jeśli struktury zawierają tylko prymitywy lub jeśli jesteś zainteresowany ścisłą równością, możesz zrobić coś takiego:

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

Jeśli jednak twoje struktury zawierają wskaźniki do innych struktur lub związków, będziesz musiał napisać funkcję, która odpowiednio porównuje prymitywy i odpowiednio wywoła porównania z innymi strukturami.

Pamiętaj jednak, że powinieneś był użyć memset (& a, sizeof (struct my_struct), 1), aby wyzerować zakres pamięci struktur w ramach inicjalizacji ADT.

Kevin S.
źródło
-1

jeśli zmienna 2 struktury została zainicjowana za pomocą calloc lub jest ustawiona na 0 przez memset, możesz więc porównać swoje 2 struktury z memcmp i nie martw się o śmieci struktur, a to pozwoli ci zarobić czas

MOHAMED
źródło
-2

W tym zgodnym przykładzie użyto rozszerzenia kompilatora pakietu #pragma z Microsoft Visual Studio, aby zapewnić możliwie ścisłe spakowanie elementów struktury:

#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
źródło
1
To jest rzeczywiście poprawne. Ale w większości przypadków nie chcesz, aby twoje struktury były zapakowane! Sporo instrukcji i wskazówek wymaga, aby dane wejściowe były dostosowane do słów. Jeśli tak nie jest, kompilator musi dodać dodatkowe instrukcje, aby skopiować i ponownie wyrównać dane, zanim będzie można wykonać rzeczywistą instrukcję. Jeśli kompilator nie wyrówna danych, procesor zgłasza wyjątek.
Ruud Althuizen,