2010-03-20 24 views
6

Ho usato il seguente codice per creare varie strutture, ma solo dare alle persone esterne al file C un puntatore. (Sì, so che potrebbero potenzialmente rovinare tutto, quindi non è del tutto simile alla parola chiave privata in Java, ma va bene per me).Typedef e Struct in file C e H

In ogni caso, ho usato il seguente codice, e l'ho guardato oggi, e sono davvero sorpreso che funzioni, qualcuno può spiegare perché questo è?

Nel mio file C, Creo il mio struct, ma non me ne frega un tag nello spazio dei nomi typedef:

struct LABall { 
    int x; 
    int y; 
    int radius; 
    Vector velocity; 
}; 

E nel file H, ho messo questo:

typedef struct LABall* LABall; 

Ovviamente sto usando #include "LABall.h" nel file c, ma NON sto usando #include "LABall.c" nel file di intestazione, in quanto ciò vanificherebbe l'intero scopo di un file di intestazione separato. Quindi, perché sono in grado di creare un puntatore alla struttura LABall * nel file H quando non l'ho effettivamente incluso? Ha qualcosa a che fare con il namespace struct che lavora su file, anche quando un file non è in alcun modo collegato ad un altro?

Grazie.

+2

Nascondere il fatto che qualcosa è un puntatore è generalmente considerato di cattivo gusto. E non è necessario - i puntatori opachi funzionano bene in C, date un'occhiata a FILE * per esempio - non c'è un tipo di puntatore FILE speciale. –

+0

Ok, grazie. Sono solo troppo abituato a zoppicare java, che sembra voler nascondere il fatto che tutto è un puntatore. –

risposta

7

Dato che stai chiedendo un motivo preciso per "perché" il linguaggio funziona in questo modo, presumo che tu voglia dei riferimenti precisi. Se si scopre che pedante, basta saltare le note ...

Funziona a causa di due cose:

  • Tutto puntatore per strutturare i tipi hanno la stessa rappresentazione (si noti che si tratta di non vero per tutti tipi di puntatore, per quanto riguarda lo standard C). [1] Quindi, il compilatore ha abbastanza informazioni per generare codice corretto per tutti gli usi del tipo pointer-to-struct.

  • Il namespace del tag (struct, enum, union) è effettivamente compatibile con tutte le unità di traduzione. [2] Quindi, le due strutture (anche se una non è completamente definita, cioè manca delle dichiarazioni dei membri) sono una e la stessa cosa.

(BTW, # import è non standard.)

[1] Secondo n1256 §6.2.5.27:

Tutti i puntatori per strutturare i tipi devono avere la stessa rappresentazione e requisiti di allineamento reciproci. I puntatori ad altri tipi non devono avere la stessa rappresentazione o requisiti di allineamento.

[2] Secondo n1256 §6.2.7.1:

due strutture, l'unione, o tipi enumerati dichiarati in unità di traduzione distinte sono compatibili se i loro tag e membri soddisfano i seguenti requisiti: Se uno è dichiarato con un tag, l'altro deve essere dichiarato con lo stesso tag. Se entrambi sono tipi completi, si applicano i seguenti requisiti aggiuntivi: [non ci riguarda].

+1

"(BTW, #import è non standard.)" Woops, stavo mixando java ... Volevo digitare #include. :) Lo aggiusterò. –

1

In

typedef struct A* B; 

dal interfacce tutti i puntatori sono gli stessi, sapendo che B si intende un puntatore ad una struct A contiene già informazioni sufficienti. L'implementazione effettiva di A è irrilevante (questa tecnica è chiamata "puntatore opaco".)

(A proposito, meglio rinomina uno dei 's LABall. È confusione che lo stesso nome viene utilizzato per tipi incompatibili.)

+0

Davvero, ho pensato che il fatto che solo uno di essi esistesse nello spazio dei nomi struct e uno di essi esistesse nello spazio dei nomi typedef sarebbe sufficiente per distinguerli? Oh bene, lo farò, grazie. –

+3

@Leif: Va bene per il * compilatore *, ma * gli utenti * saranno confusi, specialmente quelli che usano C++. – kennytm

22

un modello standard per cose del genere è quello di avere un file foo.h che definisce l'API come

typedef struct _Foo Foo; 

Foo *foo_new(); 
void foo_do_something(Foo *foo); 

e un file foo.c fornire un'implementazione per tale API come

struct _Foo { 
    int bar; 
}; 

Foo *foo_new() { 
    Foo *foo = malloc(sizeof(Foo)); 
    foo->bar = 0; 
    return foo; 
} 

void foo_do_something(Foo *foo) { 
    foo->bar++; 
} 

Questa nasconde tutto il layout di memoria e dimensione della struttura nella realizzazione di foo.c, e l'interfaccia esposta tramite foo.h è completamente indipendente da quelle interne: Un caller.c cui solo fa #include "foo.h" avrà solo per memorizzare un puntatore a qualcosa, e puntatori sono sempre le stesse dimensioni:

#include "foo.h" 

void bleh() { 
    Foo *f = foo_new(); 
    foo_do_something(f); 
} 

Nota: ho lasciato liberare la memoria, come un esercizio per il lettore. :-)

Naturalmente, ciò significa che il seguente file broken.c volontà NON lavoro:

#include "foo.h" 

void broken() { 
    Foo f; 
    foo_do_something(&f); 
} 

come la dimensione della memoria necessaria per creare effettivamente una variabile di tipo Foo non è noto in questo file.