2014-09-19 26 views
5

EDIT: cambiato foo_t a foo come typename perché POSIX riserva tipi che terminano in 't EDIT: cambiato _foo_s per foo_s causa C sostiene nomi che iniziano con un carattere di sottolineaturacontrastanti dichiarazione anticipata anonimo nell'intestazione

Sono perplesso su ciò che il modo migliore è quello di avere il seguente allo stesso tempo:

  1. l'attuazione biblioteca vede i membri struct ma gli utenti della biblioteca Non
  2. il compilatore controlla se la definizione della funzione nell'intestazione corrisponde l'attuazione
  3. uso C99

Il mio primo tentativo di questo è stato quello di effettuare le seguenti operazioni:

foo.h (guardie di inclusione omesse per brevità):

typedef struct foo_s foo; 

struct foo_s; 

foo* foo_create(void); 
void foo_do_something(foo *foo); 

foo.c:

#include "foo.h" 

struct foo_s { 
    /* some_hidden_members */ 
}; 

foo* foo_create() { 
    /* allocate memory etc */ 
} 

void foo_do_something(foo *foo) { 
    /* do something with foo */ 
} 

Questo sembra funzionare con gcc. Chiunque includa foo.h vede solo la dichiarazione anticipata anonima e il layout reale di struct foo_s è noto solo in foo.c.

Ho iniziato ad annusare qualcosa di strano con quanto sopra quando ho provato ad usare include-che-usi che usa clang. Quando l'ho usato per controllare foo.c mi ha informato che foo.h non deve contenere la dichiarazione in avanti di struct foo_s. Ho pensato che fosse un bug in iwyu perché ovviamente questo non sarebbe un problema per nessuno che includesse lo foo.h.

A questo punto fammi venire alla mia seconda richiesta dall'inizio. foo.c include foo.h in modo che il compilatore possa assicurarsi che ogni funzione dichiarata in foo.h corrisponda all'implementazione in foo.c. Penso di aver bisogno di questo perché ho riscontrato errori di segmentazione troppo spesso perché la firma della funzione della mia implementazione non corrispondeva a quella nell'intestazione utilizzata dall'altro codice.

In seguito ho provato la compilazione del codice con clang (compilo con -Wall -Wextra -Werror) ed è stato informato che:

error: redefinition of typedef 'foo' is a C11 feature 

Non voglio il mio codice di dipendere da una caratteristica C11 e voglio essere sicuro che le funzioni nell'intestazione pubblica coincidano con l'implementazione. Come lo risolvo?

vedo un modo che è diviso in foo.hfoo.h e foo_private.h:

foo.h (guardie inclusione omesse per brevità):

struct foo_s; 

#include "foo_private.h" 

foo_private.h (guardie inclusione omesse per brevità) :

typedef struct foo_s foo; 

foo* foo_create(void); 
void foo_do_something(foo *foo); 

e poi vorrei includere foo_private.h in foo.c e altri il codice includerebbe foo.h.Ciò significherebbe che foo.c non vede più la dichiarazione forward di foo_s e quindi clang e iwyu dovrebbe essere felice. Significa anche che l'implementazione delle mie funzioni viene controllata per corrispondere all'intestazione.

Ma mentre questo funziona mi fa domando se questa è la soluzione migliore perché:

  • sembra uno spreco avere un file di intestazione con solo due linee
  • Non so di altri progetti che fare così (e guardando nel mio/usr/include non vedo nessuno dei due)

Quindi quale sarebbe una soluzione che soddisfi i tre criteri elencati in alto? O è la soluzione che ho trovato quella da seguire?

+4

Non c'è bisogno di dichiarare '_foo_s struct;' 'dopo typedef struct _foo_s foo_t; '. Già solo quel typedef dichiara * sia * 'struct _foo_s' che' foo_t'. La seguente 'struct _foo_s;' non aggiunge nulla ad essa. È ridondante. Forse è esattamente quello che iwyu stava cercando di dirti. – AnT

+1

Il codice che si sta visualizzando non dà luogo a un doppio 'typedef'. Non dovresti avere problemi se hai a che fare con protezioni che proteggono il contenuto del tuo file '.h'. –

+1

OT: POSIX non consente tipi che terminano con '_t'. – alk

risposta

4

Complimenti per la nobile intenzione di nascondere i dati!

Che ne dici di quanto segue?

foo.h (guardie di inclusione omesse per brevità):

typedef struct foo_t foo_t; // note change 0 

// note change 1 

foo_t* foo_create(void); 
void foo_do_something(foo_t *foo); 

foo.c:

#include "foo.h" 

struct foo_t {    // note change 2 
    /* some_hidden_members */ 
}; 

foo_t* foo_create() { 
    /* allocate memory etc */ 
} 

void foo_do_something(foo_t *foo) { 
    /* do something with foo */ 
} 
+1

1+ ma mi libererei del 'typedef'. È inutile. Sostituisci 'typedef struct foo_t foo_t;' con 'struct foo;' e resta fedele all'utilizzo di 'struct foo'. – alk

+1

@alk: Grazie! OP era già abbastanza vicino, volevo fare il minimo cambiamento ... Immagino che l'OP voglia che i client usino solo 'foo_t', non' struct foo_t'; quindi l'ho lasciato così com'è. Comprendo anche la restrizione POSIX, ma questo è un argomento diverso. – Arun

+0

Sei corretto, ho spostato il mio commento su '_t' all'OP. – alk