Como você compara estruturas para igualdade em C?

216

Como você compara duas instâncias de estruturas para igualdade no padrão C?

Hans Sjunnesson
fonte

Respostas:

196

C não fornece recursos de idioma para fazer isso - você precisa fazer isso sozinho e comparar cada membro da estrutura por membro.

Greg Hewgill
fonte
19
se as 2 estruturas variáveis são initialied com calloc ou eles são definidos com 0 por memset para que você possa comparar as suas 2 estruturas com memcmp e não há nenhuma preocupação sobre a estrutura de lixo e isso vai permitir-lhe ganhar tempo
MOHAMED
21
@MOHAMED A comparação de campos de ponto flutuante 0.0, -0.0 NaNé um problema memcmp(). Ponteiros que diferem na representação binária podem apontar para o mesmo local (por exemplo, DOS: seg: offset) e, portanto, são iguais. Alguns sistemas têm vários ponteiros nulos que se comparam igualmente. O mesmo para obscuro intcom os tipos -0 e ponto flutuante com codificações redundantes. (Intel long double, decimal64, etc.) Esses problemas não fazem diferença, são calloc()usados ​​ou não, ou preenchimento.
chux - Restabelece Monica
2
@chux Em qualquer sistema moderno de 32 ou 64 bits que conheço, o único problema é o ponto flutuante.
Demi
2
No caso de você se perguntar por que ==não está trabalhando com estruturas (como eu), consulte stackoverflow.com/questions/46995631/…
stefanct
4
@ Demi: Hoje. O décimo mandamento para programadores C é: 'Você deve prever, renunciar e abjurar a vil heresia que afirma que “Todo o mundo é um VAX” ...'. Substituir isso por "Todo o mundo é um PC" não é uma melhoria.
Martin Bonner apoia Monica
110

Você pode ser tentado a usar memcmp(&a, &b, sizeof(struct foo)), mas pode não funcionar em todas as situações. O compilador pode adicionar espaço de buffer de alinhamento a uma estrutura, e não é garantido que os valores encontrados nos locais de memória no espaço de buffer tenham um valor específico.

Porém, se você usar callocou memseto tamanho total das estruturas antes de usá-las, poderá fazer uma comparação superficial com memcmp(se sua estrutura contiver ponteiros, ela corresponderá apenas se o endereço apontado pelos ponteiros for o mesmo).

Sufiano
fonte
19
Fechar, porque funciona em "quase todos" compiladores, mas não completamente. Confira 6.2.1.6.4 no C90: "Dois valores (exceto NaNs) com a mesma representação de objeto comparam iguais, mas valores que comparam iguais podem ter representações diferentes de objetos."
Steve Jessop
22
Considere um campo "BOOL". Em termos de igualdade, qualquer BOOL diferente de zero é igual a todo valor BOOL diferente de zero. Portanto, embora 1 e 2 possam ser VERDADEIRO e, portanto, iguais, o memcmp falhará.
ajs410
4
@JSalazar Mais fácil para você, talvez, mas muito mais difícil para o compilador e a CPU e, portanto, também muito mais lento. Por que você acha que o compilador adiciona preenchimento em primeiro lugar? Certamente para evitar desperdício de memória para nada;)
Mecki
4
@ Demetri: por exemplo, os valores de flutuação positivos e negativos de zero comparam iguais em qualquer implementação de flutuação IEEE, mas eles não têm a mesma representação de objeto. Então, na verdade, eu não deveria ter dito que funciona em "quase todos os compiladores", ele falhará em qualquer implementação que permita armazenar um zero negativo. Eu provavelmente estava pensando em representações inteiras engraçadas no momento em que fiz o comentário.
21715 Steve Steveop
4
@ Demetri: mas muitos contêm carros alegóricos, e o questionador pergunta "como você compara estruturas", e não "como você compara estruturas que não contêm carros alegóricos". Esta resposta diz que você pode fazer uma comparação superficial com a memcmpcondição de que a memória foi limpa primeiro. O que está quase funcionando, mas não está correto. Muitas vezes, a questão também não define "igualdade"; portanto, se você entender "igualdade de bytes da representação do objeto", memcmpfará exatamente isso (se a memória está limpa ou não).
21715 Steve Jobs (
22

Se você fizer muito isso, sugiro escrever uma função que compare as duas estruturas. Dessa forma, se você alterar a estrutura, precisará alterar apenas a comparação em um único local.

Quanto a como fazê-lo .... Você precisa comparar todos os elementos individualmente

Ben
fonte
1
Eu escreveria uma função separada, mesmo que a usasse apenas uma vez.
Sam
18

Você não pode usar o memcmp para comparar estruturas de igualdade devido a caracteres de preenchimento aleatório em potencial entre campos em estruturas.

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

O exemplo acima falharia em uma estrutura como esta:

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

Você precisa usar a comparação entre membros para estar seguro.


fonte
25
É improvável que esteja preenchendo após o dobro; o caractere estará perfeitamente alinhado imediatamente após o dobro.
Jonathan Leffler
7

@Greg está correto em que é necessário escrever funções de comparação explícitas no caso geral.

É possível usar memcmpse:

  • as estruturas não contêm campos de ponto flutuante que são possivelmente NaN.
  • as estruturas não contêm preenchimento (use -Wpaddedcom clang para verificar isso) OU as estruturas são explicitamente inicializadas memsetna inicialização.
  • não há tipos de membros (como Windows BOOL) que tenham valores distintos, mas equivalentes.

A menos que você esteja programando para sistemas incorporados (ou escrevendo uma biblioteca que possa ser usada neles), eu não me preocuparia com alguns dos casos de canto no padrão C. A distinção entre ponteiro versus ponto distante não existe em nenhum dispositivo de 32 ou 64 bits. Nenhum sistema não incorporado que eu conheço possui vários NULLponteiros.

Outra opção é gerar automaticamente as funções de igualdade. Se você definir suas definições de estrutura de uma maneira simples, é possível usar um processamento de texto simples para lidar com definições simples de estrutura. Você pode usar libclang para o caso geral - uma vez que usa o mesmo front-end que o Clang, ele lida com todos os casos de canto corretamente (exceto erros).

Eu não vi essa biblioteca de geração de código. No entanto, parece relativamente simples.

No entanto, também é o caso de que tais funções de igualdade geradas frequentemente façam a coisa errada no nível do aplicativo. Por exemplo, duas UNICODE_STRINGestruturas no Windows devem ser comparadas superficial ou profundamente?

Demi
fonte
2
Explicitamente inicializar as estruturas com memset, etc., não garante o valor dos bits de preenchimento após mais de escrita para um elemento de estrutura, consulte: stackoverflow.com/q/52684192/689161
gengkev
4

Observe que você pode usar o memcmp () em estruturas não estáticas sem se preocupar com preenchimento, desde que não inicialize todos os membros (de uma só vez). Isso é definido por C90:

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

pixelbeat
fonte
1
É realmente especificado que {0, }também zerará quaisquer bytes de preenchimento?
Alnitak
O GCC zera pelo menos zeros de preenchimento para estruturas parcialmente inicializadas, conforme demonstrado no link acima, e stackoverflow.com/questions/13056364/… detalha que C11 especifica esse comportamento.
pixelbeat
1
Não é muito útil em geral, porque todo o preenchimento se torna indeterminado ao ser atribuído a qualquer membro.
29/10/17
2

Depende se a pergunta que você está fazendo é:

  1. Essas duas estruturas são o mesmo objeto?
  2. Eles têm o mesmo valor?

Para descobrir se eles são o mesmo objeto, compare os ponteiros com as duas estruturas de igualdade. Se você deseja descobrir em geral se eles têm o mesmo valor, é necessário fazer uma comparação profunda. Isso envolve comparar todos os membros. Se os membros forem ponteiros para outras estruturas, você também precisará recursá-las.

No caso especial em que as estruturas não contêm ponteiros, você pode executar um memcmp para realizar uma comparação bit a bit dos dados contidos em cada uma, sem precisar saber o que os dados significam.

Certifique-se de saber o que 'igual' significa para cada membro - é óbvio para ints, mas mais sutil quando se trata de valores de ponto flutuante ou tipos definidos pelo usuário.

domgblackwell
fonte
2

memcmpnão compara estrutura, memcmpcompara o binário e sempre há lixo na estrutura; portanto, sempre sai falso em comparação.

Compare elemento por elemento, é seguro e não falha.

sergio
fonte
1
se as 2 estruturas variáveis são initialied com calloc ou eles são definidos com 0 por memset para que você possa comparar as suas 2 estruturas com memcmp e não há nenhuma preocupação sobre a estrutura de lixo e isso vai permitir-lhe ganhar tempo
MOHAMED
1

Se as estruturas contiverem apenas primitivas ou se você estiver interessado em igualdade estrita, poderá fazer algo assim:

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

No entanto, se suas estruturas contiverem ponteiros para outras estruturas ou uniões, será necessário escrever uma função que compare os primitivos corretamente e faça chamadas de comparação com as outras estruturas, conforme apropriado.

Lembre-se, no entanto, de que você deve ter usado o memset (& a, sizeof (struct my_struct), 1) para zerar o intervalo de memória das estruturas como parte da inicialização do ADT.

Kevin S.
fonte
-1

se a variável 2 estruturas for inicializada com calloc ou for definida com 0 pelo memset, você poderá comparar suas 2 estruturas com o memcmp e não haverá preocupação com o lixo da estrutura, o que permitirá ganhar tempo

MOHAMED
fonte
-2

Este exemplo compatível usa a extensão do compilador #pragma pack do Microsoft Visual Studio para garantir que os membros da estrutura sejam compactados da maneira mais rígida possível:

#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
fonte
1
Isso é realmente correto. Mas, na maioria dos casos, você não deseja que suas estruturas sejam embaladas! Muitas instruções e ponteiros exigem que os dados de entrada sejam alinhados por palavras. Caso contrário, o compilador precisa adicionar instruções extras para copiar e realinhar dados antes que a instrução real possa ser executada. Se o compilador não realinhar os dados, a CPU lançará uma exceção.
Ruud Althuizen