Domanda

Sto scrivendo una libreria C multipiattaforma ma alla fine ho riscontrato un errore nei miei unittest, ma solo su macchine Windows. Ho rintracciato il problema e ho scoperto che è correlato all'allineamento delle strutture (sto usando array di strutture per conservare i dati per più oggetti simili). Il problema è: memset (sizeof (struct)) e l'impostazione dei membri delle strutture uno alla volta producono risultati byte-to-byte diversi e quindi memcmp () restituisce "non uguale"; risultato.

Qui il codice per l'illustrazione:

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

typedef struct {
    long long      a;
    int            b;
} S1;

typedef struct {
    long           a;
    int            b;
} S2;

S1 s1, s2;

int main()
{
    printf("%d %d\n", sizeof(S1), sizeof(S2));

    memset(&s1, 0xFF, sizeof(S1));
    memset(&s2, 0x00, sizeof(S1));

    s1.a = 0LL; s1.b = 0;

    if (0 == memcmp(&s1, &s2, sizeof(S1)))
        printf("Equal\n");
    else
        printf("Not equal\n");

    return 0;
}

Questo codice con MSVC 2003 @ Windows produce il seguente output:

16 8
Not equal

Ma lo stesso codice con GCC 3.3.6 @ Linux funziona come previsto:

12 8
Equal

Questo rende il mio test di unità molto difficile.

Comprendo correttamente che MSVC utilizza le dimensioni del tipo nativo più grande (lungo lungo) per determinare l'allineamento alla struttura?

Qualcuno può darmi consigli come posso cambiare il mio codice per renderlo più robusto contro questo strano problema di allineamento? Nel mio codice reale sto lavorando con matrici di strutture tramite puntatori generici per eseguire memset / memcmp e di solito non conosco il tipo esatto, ho solo il valore sizeof (struct).

È stato utile?

Soluzione

Quello che abbiamo fatto è stato usato il pacchetto #pragma per specificare quanto grandi dovrebbero essere gli oggetti:

#pragma pack(push, 2)

typedef struct {
    long long      a;
    int            b;
} S1;

typedef struct {
    long           a;
    int            b;
} S2;

#pragma pack(pop)

In questo caso, le strutture avranno le stesse dimensioni su entrambe le piattaforme.

Altri suggerimenti

L'aspettativa del test unitario è errata. Esso (o il codice che verifica) non deve eseguire la scansione del buffer della struttura byte per byte. Per dati a byte precisi, il codice dovrebbe creare esplicitamente un buffer di byte nello stack o nell'heap e riempirlo con gli estratti di ciascun membro. Gli estratti possono essere ottenuti in modo indipendente dalla CPU endianness usando l'operazione di spostamento a destra rispetto ai valori interi e trasmettendo il risultato con il tipo di byte come (carattere senza segno).

A proposito, lo snippet scrive oltre s2. Potresti risolverlo cambiando questo

memset(&s2, 0x00, sizeof(S1));

s1.a = 0LL; s1.b = 0;

if (0 == memcmp(&s1, &s2, sizeof(S1)))

a questo

memset(&s2, 0x00, sizeof(S2));

s1.a = 0LL; s1.b = 0;

if (0 == memcmp(&s1, &s2, sizeof(S2)))

ma il risultato è tecnicamente "indefinito" perché l'allineamento dei membri nelle strutture è specifico del compilatore.

Manuale GCC:

  

Nota che lo standard ISO C richiede che l'allineamento di una determinata struttura o tipo di unione sia almeno un perfetto multiplo del multiplo comune più basso degli allineamenti di tutti i membri della struttura o unione in questione.

Inoltre, ciò introduce in genere un elemento di riempimento (ovvero byte di riempimento per allineare la struttura). Puoi usare #pragma con un argomento di imballato . Nota, #pragma NON è un modo portatile di lavorare. Sfortunatamente, questo riguarda anche l'unico modo di lavorare nel tuo caso.

Riferimenti: Qui GCC sull'allineamento della struttura. MSDN allineamento della struttura.

Nota che questo non è un problema di allineamento "strano". MSVC ha scelto di assicurarsi che la struttura sia allineata su un limite a 64 bit poiché ha un membro a 64 bit, quindi aggiunge un po 'di riempimento alla fine della struttura per garantire che le matrici di quegli oggetti abbiano ciascun elemento correttamente allineato. In realtà sono sorpreso che GCC non faccia lo stesso.

Sono curioso di sapere cosa fa il test unitario che colpisce un problema: la maggior parte delle volte l'allineamento dei membri della struttura non è necessario a meno che non sia necessario abbinare un formato di file binario o un protocollo wire o non sia davvero necessario per ridurre la memoria utilizzata da una struttura (utilizzata soprattutto nei sistemi embedded). Senza sapere cosa stai cercando di fare nei tuoi test, non credo che possa essere dato un buon suggerimento. L'imballaggio della struttura potrebbe essere una soluzione, ma comporta alcuni costi: prestazioni (soprattutto su piattaforme non Intel) e portabilità (il modo in cui è impostato il pacchetto di strutturazione può essere diverso da compilatore a compilatore). Questi potrebbero non essere importanti per te, ma potrebbe esserci un modo migliore per risolvere il problema nel tuo caso.

Puoi fare qualcosa come

#ifdef _MSC_VER
#pragma pack(push, 16)
#endif

/* your struct defs */

#ifdef _MSC_VER
#pragma pack(pop)
#endif

per fornire una direttiva del compilatore che forza l'allineamento

Oppure vai nelle opzioni del progetto e modifica l'allineamento predefinito della struttura [in Generazione di codice]

Il riempimento della struttura per i valori a 64 bit è diverso su diversi compilatori. Ho visto differenze anche tra i target gcc.

Nota che il riempimento esplicito con l'allineamento a 64 bit nasconderà solo il problema. Tornerà se inizi a nidificare ingenuamente le strutture, perché i compilatori non saranno ancora d'accordo sul naturale allineamento delle strutture interne.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top