Як ви порівнюєте структури рівності в С?


Відповіді:


196

C не забезпечує жодних мовних засобів для цього - ви повинні зробити це самостійно і порівняти кожного члена структури за членом.


19
якщо змінна 2 структури ініціалізована символом calloc або вони встановлені з 0 по memset, тож ви можете порівняти свої 2 структури з memcmp, і ви не турбуєтесь про сміття структури, і це дозволить вам заробити час
MOHAMED

21
@MOHAMED Порівняння полів з плаваючою комою 0.0, -0.0 NaNє проблемою memcmp(). Покажчики, які відрізняються бінарним поданням, можуть вказувати на одне місце (наприклад, DOS: seg: offset) і так дорівнювати. Деякі системи мають кілька нульових покажчиків, які порівнюються однаково. Те ж саме для незрозумілих intтипів -0 і плаваючої точки з надмірними кодуванням. (Intel довгий подвійний, десятковий64 тощо). Ці питання не мають жодної різниці в calloc()застосуванні чи ні.
chux

2
@chux У будь-якій сучасній 32- або 64-бітній системі, про яку я знаю, єдине питання полягає у плаваючій точці.
Демі

2
У разі , якщо вам цікаво , чому ==не працює зі структурами (як я), будь ласка , см stackoverflow.com/questions/46995631 / ...
stefanct

4
@Demi: сьогодні. Десята заповідь для програмістів на C - "Ти повинен одягнути вбрання, відмовитись від нього і пошкодити мерзенну єресь, яка стверджує, що" В усьому світі VAX "...". Заміна цього пункту "ПК усього світу" не є вдосконаленням.
Мартін Боннер підтримує Моніку

110

Ви можете спокуситись використовувати memcmp(&a, &b, sizeof(struct foo)), але це може не спрацювати у всіх ситуаціях. Компілятор може додати в буфер простір буфера вирівнювання, і значення, знайдені в місцях пам'яті, що лежать у буферному просторі, не гарантуються як якесь конкретне значення.

Але якщо ви використовуєте callocабо memsetповний розмір структур перед їх використанням, ви можете провести неглибоке порівняння memcmp(якщо ваша структура містить покажчики, вона збігатиметься лише в тому випадку, якщо адреса покажчиків однакова).


19
Близько, бо він працює на «майже всіх» компіляторах, але не зовсім. Перевірте 6.2.1.6.4 в C90: "Два значення (крім NaN) з однаковим представленням об'єкта порівнюють рівні, але значення, що порівнюють рівні, можуть мати різні представлення об'єкта."
Стів Джессоп

22
Розглянемо поле "BOOL". За рівності будь-яка ненульова BOOL дорівнює кожному ненульовому значенню BOOL. Тож як 1 і 2 можуть бути істинними, і тому однаковими, пам’ятка не матиме помилок.
ajs410

4
@JSalazar Простіше для вас, можливо, але набагато складніше для компілятора і процесора, а отже, і набагато повільніше. Чому, на ваш погляд, компілятор додає набивання в першу чергу? Безумовно, не втрачати пам’ять ні за що;)
Мецький

4
@Demetri: наприклад, значення поплавця позитивні та від'ємні нулі порівнюються рівними в будь-якій реалізації IEEE float, але вони не мають однакового представлення об'єкта. Тож насправді я не повинен був говорити, що він працює на "майже всіх компіляторах", але не вдасться до жодної реалізації, яка дозволяє зберігати негативний нуль. Я, мабуть, думав про кумедні цілі уявлення в той час, коли я робив коментар.
Стів Джессоп

4
@Demetri: але багато хто містить поплавці, і запитуючий запитує "як ти порівнюєш структури", а не "як ти порівнюєш структури, які не містять поплавків". Ця відповідь говорить, що ви можете зробити неглибоке порівняння з memcmpтим, що спочатку очищено пам'ять. Що близьке до роботи, але не коректно. Звичайно, питання також не визначає "рівність", тож якщо ви вважаєте, що це означає "байт-рівність рівності представлення об'єкта", то memcmpце саме так (чи очищена пам'ять чи ні).
Стів Джессоп

22

Якщо ти це робиш багато, я б запропонував написати функцію, яка порівнює дві структури. Таким чином, якщо ви коли-небудь змінюєте структуру, вам потрібно змінити лише порівняння в одному місці.

Щодо того, як це зробити .... Вам потрібно порівняти кожен елемент окремо


1
Я би написав окрему функцію, навіть якби використати її лише один раз.
Сем

18

Ви не можете використовувати memcmp для порівняння структур для рівності через потенційні випадкові символи прокладки між полями в структурах.

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

Вищезазначене не вдасться для такої структури:

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

Ви повинні використовувати порівняння для членів, щоб бути безпечним.


25
Навряд чи буде прокладка після дубля; char буде ідеально адекватно вирівняний відразу після дубля.
Джонатан Леффлер

7

@Greg вірно, що в загальному випадку потрібно писати явні функції порівняння.

Можна використовувати, memcmpякщо:

  • структури не містять поля з плаваючою комою, які можливо NaN.
  • структури не містять прокладок (використовуйте -Wpaddedкланг для перевірки цього) АБО структури явно ініціалізуються memsetпри ініціалізації.
  • не існує типів членів (наприклад, Windows BOOL), які мають чіткі, але еквівалентні значення.

Якщо ви не програмуєте вбудовані системи (або записуєте бібліотеку, яка може бути використана на них), я б не турбувався про деякі найважливіші випадки стандарту С. Відмінність вказівника від ближнього та далекого не існує на жодному 32- або 64-бітному пристрої. Жодна невбудована система, про яку я знаю, не має декількох NULLпокажчиків.

Інший варіант - автоматичне створення функцій рівності. Якщо ви викладете свої визначення структури простим способом, для простого визначення структури можна використовувати просту обробку тексту. Ви можете використовувати libclang для загального випадку - оскільки він використовує той самий фронт, як і Clang, він обробляє всі кутові корпуси правильно (помилки заборони).

Я не бачив такої бібліотеки генерації коду. Однак це видається порівняно просто.

Однак також буває так, що такі створені функції рівності часто роблять неправильну справу на рівні програми. Наприклад, чи слід UNICODE_STRINGпорівняти дрібно чи глибоко дві структури в Windows?


2
Явна ініціалізація структур з memsetі так далі не гарантує вартість заповнення біт після подальшого запису на структуру елемент, см: stackoverflow.com/q/52684192/689161
gengkev

4

Зауважте, що ви можете використовувати memcmp () для нестатичних конструкцій, не турбуючись про підкладку, якщо ви не ініціалізуєте всіх членів (одразу). Це визначено C90:

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


1
Насправді вказано, що {0, }також буде нульовим будь-який байт padding?
Альнітак

GCC принаймні дорівнює нулю байтів для заповнення для частково ініціалізованих структур, як показано за посиланням вище, а stackoverflow.com/questions/13056364/… деталізує, що C11 визначає таку поведінку.
піксельбіт

1
Загалом, не дуже корисно, тому що всі накладки стають невизначеними після присвоєння будь-якому члену
ММ

2

Це залежить від того, чи задаєте ви запитання:

  1. Ці дві структури є одним об'єктом?
  2. Чи мають вони однакове значення?

Щоб дізнатись, чи є вони однаковим об'єктом, порівняйте покажчики на дві структури на рівність. Якщо ви хочете дізнатися, чи мають вони однакове значення, ви повинні провести глибоке порівняння. Це включає порівняння всіх членів. Якщо члени є вказівниками на інші структури, вам також потрібно повторити їх.

У спеціальному випадку, коли структури не містять покажчиків, ви можете зробити memcmp, щоб виконати побітове порівняння даних, що містяться в кожному, не знаючи, що означають дані.

Переконайтеся, що ви знаєте, що означає "рівний" для кожного члена - це очевидно для вкладишів, але більш тонко, якщо мова йде про значення з плаваючою комою або визначені користувачем типи.


2

memcmpне порівнює структуру, memcmpпорівнює двійкову, і в структурі завжди є сміття, тому завжди виходить помилковим у порівнянні.

Порівняйте елемент за елементами його безпечним і не виходить з ладу.


1
якщо змінна 2 структури ініціалізована символом calloc або вони встановлені з 0 по memset, тож ви можете порівняти свої 2 структури з memcmp, і ви не турбуєтесь про сміття структури, і це дозволить вам заробити час
MOHAMED

1

Якщо структури містять лише примітиви або якщо вас цікавить сувора рівність, ви можете зробити щось подібне:

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

Однак якщо ваші структури містять вказівники на інші структури чи об'єднання, вам потрібно буде написати функцію, яка правильно порівнює примітиви та робити відповідні виклики проти інших структур, як це доречно.

Однак майте на увазі, що вам слід було використовувати memset (& a, sizeof (struct my_struct), 1), щоб нульове вимкнення діапазону пам'яті структур було частиною вашої ініціалізації ADT.


-1

якщо змінна 2 структури ініціалізується calloc або вони встановлені з 0 по memset, тож ви можете порівняти свої 2 структури з memcmp, і ви не турбуєтесь про сміття структури, і це дозволить вам заробити час


-2

Цей сумісний приклад використовує розширення компілятора пакетів #pragma від Microsoft Visual Studio, щоб забезпечити, щоб члени структури були упаковані максимально щільно:

#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))) {
    /* ... */
  }
}

1
Це дійсно правильно. Але в більшості випадків ви не хочете, щоб ваші структури були упаковані! Досить багато інструкцій та покажчиків вимагають, щоб вхідні дані були вирівняні за словом. Якщо це не так, тоді компілятору потрібно додати додаткові інструкції для копіювання та повторної вирівнювання даних, перш ніж реальна інструкція може бути виконана. Якщо компілятор не повторно вирівняє дані, процесор видасть виняток.
Рууд Алтуйзен
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.