Как вы сравниваете структуры для равенства в C?

216

Как вы сравниваете два случая структур на равенство в стандарте C?

Ханс Сьюннессон
источник

Ответы:

196

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

Грег Хьюгилл
источник
19
если переменная 2 структурам инициализирована с помощью calloc, или они установлены в 0 с помощью memset, так что вы можете сравнить свои 2 структуры с memcmp, и вам не нужно беспокоиться о мусоре структуры, и это позволит вам заработать время
MOHAMED
21
@MOHAMED Сравнение полей с плавающей точкой с 0.0, -0.0 NaNпроблемой memcmp(). Указатели, которые отличаются в двоичном представлении, могут указывать на одно и то же местоположение (например, DOS: seg: offset) и, следовательно, равны. Некоторые системы имеют несколько нулевых указателей, которые сравниваются одинаково. То же самое для неясных intтипов с -0 и типов с плавающей запятой с избыточным кодированием. (Intel long double, decimal64 и т. Д.) Эти проблемы не имеют значения, calloc()используется или нет или заполнение.
Chux - Восстановить Монику
2
@chux В любой современной 32- или 64-битной системе, о которой я знаю, единственная проблема связана с плавающей запятой.
Деми
2
В случае , если вам интересно , почему ==не работает со структурами (как я), пожалуйста , см stackoverflow.com/questions/46995631/...
stefanct
4
@ Деми: сегодня. Десятая заповедь для программистов на Си гласит: «Ты должен предвидеть, отречься и отречься от гнусной ереси, которая утверждает, что« Весь мир - 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 могут быть ИСТИНА и, следовательно, равны, memcmp не удастся.
ajs410
4
@JSalazar Может быть, проще для вас, но гораздо сложнее для компилятора и процессора и, следовательно, гораздо медленнее. Как вы думаете, почему компилятор добавляет отступы? Конечно, не
стоит
4
@Demetri: например, значения с плавающей точкой положительного и отрицательного нуля сравниваются равными в любой реализации IEEE с плавающей точкой, но они не имеют одинакового представления объекта. Так что на самом деле я не должен был говорить, что он работает «почти на всех компиляторах», он потерпит неудачу в любой реализации, которая позволяет хранить отрицательный ноль. Я, вероятно, думал о забавных целочисленных представлениях в то время, когда я сделал комментарий.
Стив Джессоп
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
Маловероятно, чтобы быть дополнением после двойного; символ будет идеально выровнен сразу после двойного.
Джонатан Леффлер
7

@ Грег правильно, что нужно написать явные функции сравнения в общем случае.

Можно использовать, memcmpесли:

  • структуры не содержат полей с плавающей точкой, которые возможно NaN.
  • структуры не содержат отступов (используйте -Wpaddedдля проверки clang) ИЛИ структуры явно инициализируются memsetпри инициализации.
  • нет типов элементов (таких как Windows BOOL), которые имеют различные, но эквивалентные значения.

Если вы не программируете для встраиваемых систем (или пишете библиотеку, которая может быть использована на них), я бы не стал беспокоиться о некоторых ключевых случаях в стандарте C. Различение ближнего и дальнего указателя не существует ни на одном 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

pixelbeat
источник
1
Это действительно указано, что {0, }также будет обнулять любые байты заполнения?
Альнитак
GCC, по крайней мере, обнуляет байты заполнения для частично инициализированных структур, как показано в ссылке выше, и stackoverflow.com/questions/13056364/… подробно описывает, что C11 определяет это поведение.
pixelbeat
1
В целом не очень полезно, потому что все заполнение становится неопределенным при назначении любому члену
ММ
2

Это зависит от того, является ли вопрос, который вы задаете:

  1. Являются ли эти две структуры одним и тем же объектом?
  2. Они имеют одинаковую ценность?

Чтобы выяснить, являются ли они одним и тем же объектом, сравните указатели с двумя структурами равенства. Если вы хотите узнать в общем, имеют ли они одинаковое значение, вы должны сделать глубокое сравнение. Это включает в себя сравнение всех членов. Если участники являются указателями на другие структуры, вам также необходимо участвовать в этих структурах.

В особом случае, когда структуры не содержат указателей, вы можете сделать memcmp, чтобы выполнить побитовое сравнение данных, содержащихся в каждом, без необходимости знать, что эти данные означают.

Убедитесь, что вы знаете, что означает «равно» для каждого члена - это очевидно для целых чисел, но более тонко, когда речь идет о значениях с плавающей запятой или пользовательских типах.

domgblackwell
источник
2

memcmp не сравнивает структуру, memcmp сравнивает двоичный файл, и в структуре всегда есть мусор, поэтому он всегда выдает False в сравнении.

Сравните элемент за элементом, это безопасно и не терпит неудачу.

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

Если структуры содержат только примитивы или если вы заинтересованы в строгом равенстве, вы можете сделать что-то вроде этого:

int my_struct_cmp (const struct my_struct * lhs, const struct my_struct * rhs)
{
    вернуть memcmp (lhs, rsh, sizeof (struct my_struct));
}

Однако, если ваши структуры содержат указатели на другие структуры или объединения, вам нужно написать функцию, которая правильно сравнивает примитивы и при необходимости выполняет сравнения с другими структурами.

Имейте в виду, однако, что вы должны были использовать memset (& a, sizeof (struct my_struct), 1), чтобы обнулить диапазон памяти структур как часть вашей инициализации ADT.

Кевин С.
источник
-1

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

MOHAMED
источник
-2

В этом совместимом примере используется расширение компилятора #pragma pack из 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
Это действительно правильно. Но в большинстве случаев вы не хотите, чтобы ваши структуры были упакованы! Довольно много инструкций и указателей требуют, чтобы входные данные были выровнены по словам. Если это не так, то компилятору необходимо добавить дополнительные инструкции для копирования и повторного выравнивания данных, прежде чем можно будет выполнить саму инструкцию. Если компилятор не перенастроит данные, процессор выдаст исключение.
Рууд Алтуизен