2014-11-08 9 views
5

Ho un progetto C che è progettato per essere portabile su varie piattaforme (PC e embedded).Come si digita una struct definita dall'implementazione in un'intestazione generica?

Il codice dell'applicazione utilizzerà varie chiamate che avranno implementazioni specifiche della piattaforma, ma condivideranno un'API (generica) comune per facilitare la portabilità. Sto cercando di stabilire il modo più appropriato per dichiarare la funzione prototipi e strutture.

Ecco cosa mi è venuta in mente finora:

main.c:

#include "generic.h" 

int main (int argc, char *argv[]) { 
    int ret; 
    gen_t *data; 

    ret = foo(data); 
    ... 
} 

generic.h: (indipendente dalla piattaforma includono)

typedef struct impl_t gen_t; 

int foo (gen_t *data); 

impl.h: (dichiarazione specifica piattaforma)

#include "generic.h" 

typedef struct impl_t { 
    /* ... */ 
} gen_t; 

impl.c: (implementazione specifica della piattaforma)

int foo (gen_t *data) { 
    ... 
} 

Corporatura:

gcc -c -fPIC -o platform.o impl.c 
gcc -o app main.c platform.o 

Ora, questo sembra funzionare ... nel senso che compila OK. Tuttavia, di solito non taggato le mie strutture poiché non sono mai accessibili al di fuori dell'alias typedef. È un piccolo pignolo, ma mi chiedo se c'è un modo per ottenere lo stesso effetto con le strutture anonime?

sto anche chiedendo per i posteri, dal momento che ho cercato per un po 'e la risposta più vicina che ho trovato è stato questo: (Link)

Nel mio caso, non sarebbe l'approccio giusto, come l'applicazione nello specifico non dovrebbe mai includere direttamente le intestazioni di implementazione - l'intero punto è disaccoppiare il programma dalla piattaforma.

vedo un paio di altri meno-che-ideale modi per risolvere questo, ad esempio:

generic.h:

#ifdef PLATFORM_X 
#include "platform_x/impl.h" 
#endif 

/* or */ 

int foo (struct impl_t *data); 

Nessuno di questi sembra particolarmente interessante, e sicuramente non il mio stile. Anche se non voglio nuotare controcorrente, non voglio nemmeno uno stile conflittuale quando potrebbe esserci un modo migliore per implementare esattamente ciò che avevo in mente. Quindi penso che la soluzione typedef sia sulla strada giusta, ed è solo il bagaglio tag struct che mi rimane.

Pensieri?

+0

Si potrebbe semplicemente rendere 'gen_t' un tipo di puntatore (un handle). Quindi i client dovrebbero usare la tua API per inizializzarla/usarla/distruggerla. O una leggera variazione, rendi la tua struttura esposta pubblicamente con un puntatore opaco all'attuale implementazione. altrimenti, non c'è molto altro che puoi fare. È necessario creare un oggetto o tracciamento del percorso che il cliente dovrà conoscere. –

+0

c'è un problema nel fatto che il generic.h viene incluso PRIMA di impl.h Ma generico.h fa riferimento alla struttura definita in impl.h I.E. un riferimento futuro. (probabilmente non verrà compilato) molto meglio avere generic.h include impl.h (dove impl.h è specifico della piattaforma) Quindi la struttura sarà già definita quando generic.h prova ad usarlo. – user3629249

+1

è molto meglio definire una struttura come: struct tagname {;;; }; quindi riferiscilo sempre come "struct tagname myname;" ci sono un certo numero di ragioni per questo: 1) le strutture nominate è un formato deprecato 2) i debugger usano il nome del tag per i campi di riferimento, ecc. 3) usando typedef su una struct è (in generale) la cattiva pratica di programmazione – user3629249

risposta

4

La tecnica corrente è corretta. Provare a usare un anonimo (senza tag) struct sconfigge ciò che stai cercando di fare - dovresti esporre i dettagli della definizione di struct ovunque, il che significa che non hai più un tipo di dati opaco.


In un comment, user3629249 detto:

L'ordine delle inclusioni di file di intestazione significa che v'è un riferimento in avanti alla struct dal file generic.h; cioè, prima che la struct sia definita, viene usata. È improbabile che questo possa essere compilato.

Questa osservazione non è corretta per le intestazioni mostrate nella domanda; è accurato per il codice campione main() (che non avevo notato fino ad aggiungere questa risposta).

Il punto chiave è che le funzioni dell'interfaccia mostrate prendono o restituiscono puntatori al tipo gen_t, che a sua volta si associa a un puntatore struct impl_t. Finché il codice client non ha bisogno di allocare spazio per la struttura, o di un riferimento a una struttura per accedere a un membro della struttura, il codice cliente non ha bisogno di conoscere i dettagli della struttura. È sufficiente avere il tipo di struttura dichiarato come esistente. Si potrebbe utilizzare uno di questi per dichiarare l'esistenza di struct impl_t:

struct impl_t; 

typedef struct impl_t gen_t; 

Quest'ultimo introduce anche l'alias gen_t per il tipo struct impl_t. Vedere anche Which part of the C standard allows this code to compile? e Does the C standard consider that there are one or two struct uperms entry types in this header?

Il programma originale main() in questione era:

int main (int argc, char *argv[]) { 
    int ret; 
    gen_t data; 

    ret = foo(&data); 
    … 
} 

Questo codice non può essere compilato con gen_t come opaco (non-puntatore) digitare. Che avrebbe funzionato bene con:

typedef struct impl_t *gen_t; 

Non sarebbe compilare con:

typedef struct impl_t gen_t; 

perché il compilatore deve sapere quanto è grande la struttura è quella di allocare lo spazio corretto per data, ma il compilatore non può sapere che dimensione per definizione di cosa sia un tipo opaco. (Vedere Is it a good idea to typedef pointers? per typedefing puntatori a strutture.)

Così, il codice main() dovrebbe essere più simile:

#include "generic.h" 

int main(int argc, char **argv) 
{ 
    gen_t *data = bar(argc, argv); 
    int ret = foo(data); 
    ... 
} 

dove (per questo esempio) bar() è definito come extern gen_t *bar(int argc, char **argv);, quindi restituisce un puntatore al tipo opaco gen_t.

L'opinione è divisa su se è meglio utilizzare sempre struct tagname o utilizzare un typedef per il nome. Il kernel di Linux è una parte sostanziale del codice che non usa il meccanismo typedef; tutte le strutture sono esplicitamente struct tagname. D'altra parte, C++ elimina la necessità dell'esplicita typedef; scrittura:

struct impl_t; 

in un programma C++ significa che il nome è ora il nome di un tipo.Dal momento che i tipi di struttura opaca richiedono un tag (o si finisce per utilizzare void * per tutto, il che è negativo per un'intera legione di motivi, ma il motivo principale è che si perde tutta la sicurezza del tipo usando void *, ricordare, typedef introduce un alias per un sottostante tipo, non è un nuovo tipo distinto), il codice che ho modo in C simula C++:

typedef struct Generic Generic; 

meglio cercare di evitare il suffisso _t sulle tipologie perché POSIX si riserva il _t per l'attuazione di utilizzare * (vedi anche What does a type followed by _t represent?). Potresti essere fortunato e farla franca. Ho lavorato su basi di codice in cui tipi come dec_t e loc_t sono stati definiti dal code base (che non faceva parte dell'implementazione - dove "l'implementazione" indica il compilatore C e il relativo codice di supporto, o la libreria C e il relativo codice di supporto), ed entrambi i tipi hanno causato dolore per decenni perché alcuni dei sistemi in cui è stato eseguito il porting del codice definivano questi tipi, così come la prerogativa del sistema. Uno dei nomi di cui sono riuscito a liberarmi; l'altro no. "È stato doloroso! Se è necessario utilizzare _t (è un modo conveniente per indicare che qualcosa è un tipo), si consiglia di utilizzare anche un prefisso distinto: pqr_typename_t per alcuni progetti pqr, ad esempio.

* Vedere la riga inferiore della seconda tabella in The Name Space nello standard POSIX.

+0

l'ordine dell'intestazione inclusioni di file significa che c'è un riferimento in avanti alla struct dal file generic.h, IE prima che la struct sia definita, viene usata. è improbabile che questo possa compilare – user3629249

+0

Questa è una risposta eccellente che chiarisce tutto ciò di cui ero preoccupato. Grazie! A proposito, hai assolutamente ragione sull'uso di gen_t in main(). L'ho ridotto al minimo del codice rilevante e ho ingannato la parte in traduzione. La prima chiamata di funzione utilizzata sul puntatore fa parte dell'implementazione, che ne conosce l'esatta dimensione e alloca la memoria secondo necessità. Infine, grazie per l'heads-up sul suffisso _t. Non ne ero consapevole - uno dei lati negativi del non essere formalmente addestrato forse. – SirNickity

Problemi correlati