2014-09-20 14 views
5

Supponiamo che una libreria C debba condividere i dettagli di una struttura con il codice dell'applicazione e debba mantenere la compatibilità con API e ABI a ritroso. Prova a farlo controllando la dimensione della struttura passata ad esso.In che modo sizeof (struct) aiuta a fornire la compatibilità ABI?

Supponiamo che la seguente struttura debba essere aggiornata. Nella versione della libreria 1,

typedef struct { 
    int size; 
    char* x; 
    int y; 
} foo; 

Nella versione 2 della libreria, viene aggiornato a:

typedef struct { 
    int size; 
    char* x; 
    int y; 
    int z; 
} foo_2; 

Ora, biblioteca versione 2 vuole verificare se l'applicazione sta passando il nuovo foo_2 o il vecchio foo come argomento, arg, a una funzione. Si presuppone che l'applicazione ha impostato arg.size-sizeof(foo) o sizeof(foo_2) e cerca di capire se il codice dell'applicazione groks versione 2.

if(arg.size == sizeof(foo_2)) { 
    // The application groks version 2 of the library. So, arg.z is valid. 
} else { 
    // The application uses of version 1 of the library. arg.z is not valid. 
} 

mi chiedo il motivo per cui questo non mancherà. Su GCC 4.6.3, con flag -O3, sia sizeof(foo) sia sizeof(foo_2) sono 24. Quindi, il codice libreria v2 non riuscirà a capire se l'applicazione sta passando una struttura di tipo foo o foo_2? Se sì, come mai questo approccio sembra essere stato usato?

http://wezfurlong.org/blog/2006/dec/coding-for-coders-api-and-abi-considerations-in-an-evolving-code-base/

http://blogs.msdn.com/b/oldnewthing/archive/2003/12/12/56061.aspx


seguire su domanda: C'è una buona ragione per favorire l'utilizzo di sizeof(struct) di discriminazione versione? Come indicato nei commenti, perché non utilizzare un membro esplicito version nella struttura condivisa?

+1

Da dove vieni a 24? –

+0

Probabilmente non funzionerà. 'sizeof' è una cosa in fase di compilazione e si desidera controllare la dimensione in * runtime *. –

+0

@BasileStarynkevitch: Hm, cosa? Sappiamo quale versione della struttura utilizzata dal chiamante, non il callee, quindi sembra buona da quella direzione. Tuttavia, spiky ha ragione che sulla maggior parte delle piattaforme a 64 bit, il puntatore è allineato e dimensionato a 8 byte, l'int 4, e quindi non vi è alcuna differenza di dimensione tra le due strutture. – Deduplicator

risposta

2

Al fine di soddisfare le vostre osservazioni, ho postulare

  • char* ha dimensioni 8 e l'allineamento 8.
  • int ha dimensioni 4 e l'allineamento 4.
  • L'implementazione utilizza imballaggio ottimale.

Hai ragione che in quel caso, sia la struttura vecchia e nuova avrebbe la stessa dimensione, e come la versione-discriminatore è la dimensione delle strutture, l'aggiornamento è un cambiamento ABI-breaking. (Pochi errori logici sono anche errori di sintassi e il primo non viene diagnosticato da un compilatore).

Solo le modifiche alla struttura che risultano in una dimensione più grande, con la nuova struttura contenente tutti i campi del vecchio con gli stessi offset, possono essere ABI-compatibili con tale schema: Aggiungere alcune variabili dummy.


C'è una possibilità che potrebbe salvare la situazione però:

  • Se un campo contiene un valore che in precedenza era valido, che potrebbe indicare che qualsiasi altra cosa potrebbe dover essere interpretato differencty.
+2

la soluzione corretta è il pad v2 della struct in modo che non abbia lo stesso 'sizeof' – Mgetz

1

Suggerisco l'uso di una struttura intermedia. Per esempio:

typedef struct 
{ 
    int   version; 
    void*   data; 
} foo_interface; 

typedef struct 
{ 
    char*   x; 
    int   y; 
} foo; 

typedef struct 
{ 
    char*   x; 
    int   y; 
    int   z; 
} foo_2; 

Nella mia biblioteca versione 2, vorrei esportare per nome la seguente funzione:

foo_interface* getFooObject() 
{ 
    foo_interface* objectWrapper = malloc(sizeof(foo_interface)); 
    foo_2* realObject = malloc(sizeof(foo_2)); 

    /* Fill foo_2 with random data... */ 
    realObject.x = malloc(1 * sizeof(char)); 
    realObject.y = 2; 
    realObject.z = 3; 

    /* Fill our interface. */ 
    objectWrapper.version = 2; /* Here we specify version 2. */ 
    objectWrapper.data = (void*)realObject; 

    /* Return our wrapped data. */ 
    return (objectWrapper); 
} 

Poi nel ricorso principale che vorrei fare:

int main(int ac, char **av) 
{ 
    /* Load library + Retrieve getFooObject() function here. */ 

    foo_interface* objectWrapper = myLibrary.getFooObject(); 

    switch (objectWrapper->version) 
    { 
     case 1: 
      foo* realObject = (foo*)(objectWrapper ->data); 
      /* Do something with foo here. */ 
      break; 
     case 2: 
      foo_2* realObject = (foo_2*)(objectWrapper ->data); 
      /* Do something with foo_2 here. */ 
      break; 
     default: 
      printf("Unknown foo version!"); 
      break; 
    } 
    return (0); 
} 

Come al solito, i controlli di sicurezza (quando si assegna memoria ad esempio) non sono inclusi per la leggibilità del codice.

Inoltre, userei stdint.h garantire tipi di dati compatibilità binaria (per essere sicuri che le dimensioni di int, double, char* e così via sono uguali tra diverse architetture). Ad esempio, invece di int userei int32_t.

+0

APR? Basta usare stdint.h – James

+0

@James Grazie, ho appena modificato la mia risposta :) – HRold

1

Se si desidera utilizzare questo schema per distinguere diverse versioni dell'API, è necessario assicurarsi che le diverse versioni di struttura abbiano dimensioni diverse.

Per fare ciò, è possibile provare a fare foo più piccolo forzando il compilatore di utilizzare imballaggi più stretto, oppure si può fare foo_2 più grande con l'aggiunta di ulteriori campi (non utilizzati).

In qualsiasi modo, è necessario aggiungere un'asserzione (preferibilmente in fase di compilazione) per sizeof(foo) != sizeof(foo_2) per assicurarsi che le strutture abbiano sempre dimensioni diverse.

Problemi correlati