2013-12-17 5 views
5

So che memcmp() non può essere utilizzato per confrontare le strutture che non sono state memset() a 0 a causa del riempimento non inizializzato. Tuttavia, nel mio programma ho una struttura con alcuni tipi diversi all'inizio, quindi diverse dozzine dello stesso tipo fino alla fine della struttura. Il mio pensiero è stato quello di confrontare manualmente i primi tipi, quindi utilizzare uno memcmp() sul blocco di memoria contigua rimanente degli stessi membri digitati.Confrontare le strutture in C utilizzando memcmp() e aritmetica del puntatore

La mia domanda è: cosa garantisce lo standard C per il riempimento della struttura? Posso ottenerlo in modo affidabile su qualsiasi o tutti i compilatori? Lo standard C consente l'inserimento di struct padding tra membri dello stesso tipo?

Ho implementato il mio soluzione proposta, e sembra funzionare esattamente come previsto con gcc:

#include <stdlib.h> 
#include <string.h> 
#include <stdio.h> 

struct foo 
{ 
    char a; 
    void *b; 
    int c; 
    int d; 
    int e; 
    int f; 
}; 

static void create_struct(struct foo *p) 
{ 
    p->a = 'a'; 
    p->b = NULL; 
    p->c = 1; 
    p->d = 2; 
    p->e = 3; 
    p->f = 4; 
} 

static int compare(struct foo *p1, struct foo *p2) 
{ 
    if (p1->a != p2->a) 
     return 1; 

    if (p1->b != p2->b) 
     return 1; 

    return 
     /* Note the typecasts to char * so we don't get a size in ints. */ 
     memcmp(
      /* A pointer to the start of the same type members. */ 
      &(p1->c), 
      &(p2->c), 
      /* A pointer to the start of the last element to be compared. */ 
      (char *)&(p2->f) 
      /* Plus its size to compare until the end of the last element. */ 
      +sizeof(p2->f) 
      /* Minus the first element, so only c..f are compared. */ 
      -(char *)&(p2->c) 
     ) != 0; 
} 

int main(int argc, char **argv) 
{ 
    struct foo *p1, *p2; 
    int ret; 

    /* The loop is to ensure there isn't a fluke with uninitialized padding 
    * being the same. 
    */ 
    do 
    { 
     p1 = malloc(sizeof(struct foo)); 
     p2 = malloc(sizeof(struct foo)); 

     create_struct(p1); 
     create_struct(p2); 

     ret = compare(p1, p2); 

     free(p1); 
     free(p2); 

     if (ret) 
      puts("no match"); 
     else 
      puts("match"); 
    } 
    while (!ret); 

    return 0; 
} 
+0

Minore: Poiché i puntatori confrontati restituiscono 0 o 1, suggerendo di assicurare 'memcmp() 'restituisce 0 o 1 con' memcmp()! = 0'. – chux

+0

@chux Buona idea, grazie per il suggerimento. – John

risposta

4

Non vi è alcuna garanzia di questo nello standard C. Da un punto di vista pratico è vero come parte dell'ABI per ogni implementazione C corrente, e non sembra esserci alcun motivo nell'aggiunta di padding (ad es. Non potrebbe essere usato per controllare gli overflow del buffer, poiché un programma conforme è autorizzato a scrivere sul imbottitura). Ma in senso stretto non è "portatile".

0

Purtroppo, non v'è alcuna C standard (che io abbia mai sentito parlare), che consente di controllare la struttura imbottitura. V'è il fatto che l'assegnazione automatica che viene inizializzato simili

struct something val = { 0 }; 

causerà tutti i membri di val essere inizializzato a 0. Ma il riempimento intermedio è lasciato alla realizzazione.

Ci sono estensioni del compilatore che puoi usare come GCC __attribute__((packed)) per eliminare la maggior parte se non tutte le strutture di riempimento, ma a parte questo potresti essere in perdita.

So anche che senza grandi ottimizzazioni, la maggior parte dei compilatori non si preoccuperanno di aggiungere il riempimento della struttura nella maggior parte dei casi, il che spiegherebbe perché funziona in GCC.

Detto questo, se i membri della struttura causano problemi di allineamento strani come questo

struct something { char onebyte; int fourbyte; }; 

che farà sì che il compilatore per aggiungere padding dopo il membro onebyte per soddisfare le esigenze di allineamento degli Stati fourbyte.

+1

Questo: 'struct something val = {0};' inizializza il primo membro a 0 e quindi default inizializza i restanti membri (possibilmente con 0 se questo è il loro default). 'struct something val = {};' default inializza tutti i membri che è più generico perché il primo elemento può o non può essere un membro integrale. –

+0

@JerryJeremiah vero, ma questo ha un'idea migliore. – randomusername

+0

Quando ho investigato con 'gdb' ho scoperto che c'erano 7 byte di padding aggiunti dopo' char a', rendendo l'intera struct 32 byte sul mio sistema (invece di 25, che era il caso di '__attribute __ ((__ packed __))') . Quando si usava un semplice 'memcmp()' sull'intera struttura, ovviamente non erano uguali. – John