2014-11-08 5 views
5

Ich habe ein C-Projekt, das auf verschiedene Plattformen (PC und Embedded) portierbar ist.Wie tippe ich eine implementierungsdefinierte Struktur in einem generischen Header ein?

Der Anwendungscode wird verschiedene Aufrufe verwenden, die plattformspezifische Implementierungen haben, aber eine gemeinsame (generische) API zur Unterstützung der Portabilität verwenden. Ich versuche, den am besten geeigneten Weg zu finden, die Funktionsprototypen und Strukturen zu deklarieren.

Hier ist, was ich mit so weit habe kommen:

main.c:

#include "generic.h" 

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

    ret = foo(data); 
    ... 
} 

generic.h: (plattformunabhängig sind)

typedef struct impl_t gen_t; 

int foo (gen_t *data); 

impl.h: (Plattformspezifische Deklaration)

#include "generic.h" 

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

impl.c: (plattformspezifische Implementierung)

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

Körperbau:

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

Nun, dies ... in zu arbeiten scheint, dass es kompiliert OK. Normalerweise markiere ich meine Strukturen jedoch nicht, da sie nie außerhalb des typedef 'd Alias ​​aufgerufen werden. Es ist ein kleiner Schwachpunkt, aber ich frage mich, ob es eine Möglichkeit gibt, den gleichen Effekt mit anonymen Strukturen zu erreichen?

Ich bin auch für die Nachwelt zu fragen, da ich für eine Weile gesucht und die nächste Antwort fand ich, war dies: (Link)

In meinem Fall, das wäre der richtige Ansatz nicht sein, da die Anwendung Insbesondere sollten die Implementierungsköpfe niemals direkt enthalten sein - der einzige Punkt ist, das Programm von der Plattform zu entkoppeln.

Ich sehe ein paar anderen, weniger als ideale Möglichkeiten, dies zu lösen, zum Beispiel:

generic.h:

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

/* or */ 

int foo (struct impl_t *data); 

Keines dieser besonders attraktiv scheint, und auf jeden Fall nicht mein Stil. Obwohl ich nicht stromaufwärts schwimmen möchte, möchte ich auch keinen widersprüchlichen Stil, wenn es eine schönere Möglichkeit gibt, genau das umzusetzen, was ich mir vorgestellt habe. Also denke ich, dass die typedef-Lösung auf dem richtigen Weg ist, und es ist nur das struct-tag-Gepäck, das mir geblieben ist.

Gedanken?

+0

Sie könnten einfach 'gen_t' einen Zeigertyp (ein Handle) machen. Dann müssten Clients Ihre API verwenden, um sie zu initialisieren/zu verwenden/zu zerstören. Oder eine kleine Variation, lassen Sie Ihre öffentlich exponierte Struktur einen opaken Zeiger auf die tatsächliche Implementierung enthalten. Ansonsten kann man sonst nicht viel tun. Ein Objekt oder eine Wegstrecke muss erstellt werden, über die der Client Bescheid wissen muss. –

+0

gibt es ein Problem, dass das generic.h vor impl.h enthalten ist. Aber, generic.h verweist auf die in impl.h I.E. eine Vorwärtsreferenz. (wird wahrscheinlich nicht kompilieren) viel besser zu haben generic.h include impl.h (wo impl.h plattformspezifisch ist) Dann wird die Struktur bereits definiert, wenn generic.h versucht, sie zu benutzen. – user3629249

+1

ist es viel besser, eine Struktur wie folgt zu definieren: struct Tagname {;;; }; dann referenziere es immer als 'struct tagname myname;' Es gibt eine Reihe von Gründen dafür: 1) benannte Strukturen ist ein abgeschriebenes Format 2) Debugger verwenden den Tag-Namen, um Felder, usw. Referenz 3) mit typedef auf einer Struktur ist (im Allgemeinen) schlechte Programmierpraxis – user3629249

Antwort

4

Ihre aktuelle Technik ist korrekt. Der Versuch, ein anonymes (unmarkiertes) struct zu verwenden, widerlegt, was Sie versuchen - Sie müssten die Definitionsdetails des struct überall aufdecken, was bedeutet, dass Sie keinen undurchsichtigen Datentyp mehr haben.


In einem comment, user3629249 gesagt:

Die Reihenfolge der Headerdatei Einschlüssen bedeutet, dass es auf die Struktur der Datei generic.h ein Vorwärtsverweis ist; das heißt, bevor die Struktur definiert ist, wird sie verwendet. Es ist unwahrscheinlich, dass dies kompilieren würde.

Diese Beobachtung ist falsch für die Header in der Frage gezeigt; es ist genau für die Probe main() Code (die ich bis zum Hinzufügen dieser Antwort nicht bemerkt hatte).

Der entscheidende Punkt ist, dass die gezeigten Schnittstellenfunktionen Zeiger auf den Typ gen_t aufnehmen oder zurückgeben, der wiederum auf einen Zeiger struct impl_t abgebildet wird. Solange der Client-Code keinen Speicherplatz für die Struktur reservieren muss oder einen Zeiger auf eine Struktur für den Zugriff auf ein Mitglied der Struktur dereferenzieren muss, muss der Client-Code die Details der Struktur nicht kennen. Es reicht aus, wenn der Strukturtyp als vorhanden deklariert ist. Sie könnten entweder von diesen verwenden, um die Existenz von struct impl_t zu erklären:

struct impl_t; 

typedef struct impl_t gen_t; 

Letzteres führt auch den Alias ​​gen_t für den Typ struct impl_t. Siehe auch Which part of the C standard allows this code to compile? und Does the C standard consider that there are one or two struct uperms entry types in this header?

Das ursprüngliche main() Programm in der Frage war:

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

    ret = foo(&data); 
    … 
} 

Dieser Code nicht mit gen_t als opakem (nicht-Zeiger) kompiliert werden kann geben. Es würde OK mit:

typedef struct impl_t *gen_t; 

Es wäre mit nicht kompilieren:

typedef struct impl_t gen_t; 

weil der Compiler muss wissen, wie groß die Struktur ist die richtige Platz für data zuordnen, aber der Compiler kann nicht wissen, dass Größe nach Definition, was ein undurchsichtiger Typ ist. (Siehe Is it a good idea to typedef pointers? für Zeiger auf Strukturen typedefing.)

Somit wird der main() Code sollte mehr sein wie:

#include "generic.h" 

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

wo (für dieses Beispiel) bar() als extern gen_t *bar(int argc, char **argv); definiert ist, so dass es einen Zeiger auf die zurück undurchsichtiger Typ gen_t.

Die Meinung ist geteilt, ob es besser ist, immer struct tagname zu verwenden oder einen typedef für den Namen zu verwenden. Der Linux-Kernel ist ein wesentlicher Code, der den Mechanismus typedef nicht verwendet; Alle Strukturen sind explizit struct tagname. Auf der anderen Seite, C++ beseitigt die Notwendigkeit für die explizite typedef; Schreiben:

struct impl_t; 

in einem C++ Programm bedeutet, dass der Name nun der Name eines Typs ist.Da undurchsichtige Strukturtypen erfordern ein Tag (oder Sie am Ende mit void * für alles, was für eine ganze Reihe von Gründen ist schlecht, aber der Hauptgrund ist, dass Sie alle Typ Sicherheit verlieren mit void *; denken Sie daran, typedef führt einen Alias ​​für einen zugrunde liegenden Typ, nicht neu bestimmter Typ), die Art, wie ich Code in C C simuliert ++:

typedef struct Generic Generic; 

ich auf meiner Art mit dem _t Suffix vermeiden, weil POSIX die _t für die Implementierung behält auch * (siehe verwenden What does a type followed by _t represent?). Du hast vielleicht Glück und kommst damit durch. Ich habe an Codebasen gearbeitet, wo Typen wie dec_t und loc_t durch die Codebasis definiert wurden (die nicht Teil der Implementierung war), wobei 'die Implementierung' den C-Compiler und seinen unterstützenden Code oder die C-Bibliothek und ihren unterstützenden Code bedeutet), und beide dieser Typen verursachten jahrzehntelang Schmerzen, da einige der Systeme, auf denen der Code portiert wurde, diese Typen definierten, wie es das System vorgibt. Einer der Namen, die ich loswerden konnte; das andere tat ich nicht. Es ist schmerzhaft! Wenn Sie _t verwenden müssen (es ist ein bequemer Weg, um anzuzeigen, dass etwas ein Typ ist), empfehle ich, auch ein eindeutiges Präfix zu verwenden: pqr_typename_t für ein Projekt pqr, zum Beispiel.

* Siehe die unterste Zeile der zweiten Tabelle in The Name Space im POSIX-Standard.

+0

die Reihenfolge der Header Dateieinschlüsse bedeutet, dass es einen Vorwärtsverweis auf die Struktur durch die Datei generic.h, IE, gibt bevor die Struktur definiert ist, wird sie verwendet. es ist unwahrscheinlich, dass dies kompilieren würde – user3629249

+0

Dies ist eine ausgezeichnete Antwort, die alles aufräumt, worüber ich besorgt war. Vielen Dank! BTW, du hast absolut recht mit der Verwendung von gen_t in main(). Ich habe dies auf den minimalen relevanten Code reduziert und diesen Teil in der Übersetzung verpatzt. Der erste Funktionsaufruf, der für den Zeiger verwendet wird, ist Teil der Implementierung, die ihre genaue Größe kennt und Speicher nach Bedarf zuweist. Zum Schluss, Danke für das Heads-up auf dem Suffix _t. Ich war mir dessen nicht bewusst - einer der Nachteile, wenn ich mich nicht formell ausbilden lasse. – SirNickity