Forum: PC-Programmierung C - Inwieweit spielt die Reihenfolge beim Einbinden von Headern eine Rolle


von Daniel S. (pallas)


Lesenswert?

Hallo zusammen,
gerade bin ich dabei einen bestehenden Programmcode in die 
Programmiersprache C umzuschreiben.

Im konkreten geht es aber darum, dass ich mehrere Headerdateien in mein 
Hauptprogramm einbinde.

Eine dieser Dateien führt zu einem Linkerfehler. Der Header wird als 
letztes eingebunden. Binde ich ihn als erstes ein, verschwindet der 
Linkerfehler und alles läuft problemlos.

Bei dem Fehler an sich handelt es sich um die Doppeldefinition einer 
Variable. Diese Variable ist in der Header zugehörigen .c-Datei 
definiert.

Es handelt sich dabei um einen Zeiger auf eine Struktur, welche in der 
Header-Datei deklariert ist. Dieser Zeiger ist erst einmal mit NULL 
initialisiert.

Es handelt sich also um eine "echte" Definition.

Im Grunde kann ich mir natürlich vorstellen wo das Problem liegt.
Das komische ist aber, dass eben diese Header-Datei in keiner anderen 
Datei außer eben dem Hauptprogramm inkludiert ist.
Außerdem arbeite ich selbstverständlich mit Include-Guards.

Eine Variable mit gleicher Benennung gibt es auch nirgendwo. Der Typ ist 
ja dann an dieser Stelle schon egal.

Ich kann mir nur vorstellen, dass mir bestimmte Informationen fehlen um 
mir das Verhalten erklären zu können.

Vielleicht kann sich jemand das oben beschriebene Verhalten erklären und 
möchte mir weiter helfen.

Wenn jemand Beispielcode braucht poste ich diesen natürlich gerne. Aber 
aktuell denke ich, dass meine Beschreibung das Verhaltens ganz 
ausreichend sein sollte.

Lieben Gruß
Pallas

: Bearbeitet durch User
von Bernd K. (prof7bit)


Lesenswert?

Idealerweise soll jeder einzelne Header bewußt so gestaltet werden daß 
später die Reihenfolge vollkommen belanglos ist und auch 
Mehrfach-Include keine Probleme bereitet.

Erreicht wird das in der Praxis durch folgende zwei einfache Maßnahmen:

* Jeder Header hat einen Include-Guard
* Jeder Header included genau all diejenigen anderen Header die 
notwendig sind damit er für sich allein fehlerfrei geparst werden kann. 
Zum Beispiel willst Du in einem Deiner Header den Datentyp uint32_t 
verwenden dann muss Dein Header stdint.h includen. Benutzt er noch 
andere Definitionen aus anderen Headern dann muß er auch diese ebenfalls 
includen.

Wenn alle verwendeten Header das genau so machen dann funktionieren sie

* jeder eigenständig für sich allein
* alle zusammen in beliebiger Reihenfolge

Es gab irgendwo mal einen Styleguide der NASA der das obige schön 
erklärt hat und diese Praxis für jeglichen Code dort im Hause zwingend 
vorgeschrieben hat, ich find ihn nur leider grad nicht. Ich halte mich 
seit Jahren für meine eigenen Sachen strikt daran und hab noch nie 
Probleme mit der Reihenfolge gehabt. Auch die üblichen Standard-Header 
die mit dem Compiler geliefert werden und die CMSIS-Header für ARM 
Mikrocontroller halten sich an diese Regeln.

: Bearbeitet durch User
von Daniel S. (pallas)


Lesenswert?

Ja, genau.
So habe ich mir das in letzter Zeit auch erarbeitet.
(Ich lerne C gerade).

Trotzdem bekomme ich dieses Verhalten.

Wenn jetzt niemandem so spontan  eine andere Möglichkeit einfällt, habe 
ich bestimmt irgendwo etwas übersehen.

von RAc (Gast)


Lesenswert?

Daniel S. schrieb:
>
> Es handelt sich dabei um einen Zeiger auf eine Struktur, welche in der
> Header-Datei deklariert ist. Dieser Zeiger ist erst einmal mit NULL
> initialisiert.
>
> Es handelt sich also um eine "echte" Definition.
>

Das ist grundsätzlich sehr fragwürdiger Stil.

Abhilfe: Mit Copy&Paste die Variablendeklaration in die passende C 
Deklaration umsetzen und in der Headerdatei ein extern davor setzen und 
die Initialisierung herausnehmen. Also:

Headerdatei vorher:

struct xxx *g_StructPtr = NULL;

Headerdatei nachher:

extern struct xxx *g_StructPtr;

Quelldatei neu:

struct xxx *g_StructPtr = NULL;

von Bernd K. (prof7bit)


Lesenswert?

Daniel S. schrieb:
> Bei dem Fehler an sich handelt es sich um die Doppeldefinition einer
> Variable.

Variablen in Headern sollen mit der Speicherklasse extern deklariert 
werden, das ist dann eine Vorwärtsdeklaration ähnlich eines 
Funktionsprototypen damit der Compiler erstmal fehlerfrei durchlaufen 
kann und für alle benötigten Symbole zumindest deren Deklaration kennt.

Erst der Linker sucht dann später die echten Definitionen (er such in 
jeder gelinkten .o Datei danach) für alles was im ganzen Programm 
irgendwo auf diese Weise deklariert und irgendwo verwendet wurde.

Du definierst also diese Variable in einer C-datei, sinnvollerweise 
(aber nicht zwingenderweise) in genau der C-Datei die auch zu dem 
betreffenden Header gehört und in den Header kommt nur die 
Vorwärtsdeklaration der Variable mit dem Schlüsselwort extern.

Funktionsdeklarationen in Headern sind strenggenommen auch extern, nur 
muß man das da nicht dazuschreiben (man könnte und es würde keinen 
Unterschied machen) bei Funktionsdeklarationen denkt sich der Compiler 
das extern automatisch selbst dazu.

: Bearbeitet durch User
von Daniel S. (pallas)


Lesenswert?

Nein nein nein. :-)
Im Header steht gar nichts über den Pointer.
Da steht nur die Deklaration der Struktur an sich.

Dieser Pointer ist eine globale Variable in der zugehörigen .c-Datei.
Er wird auch nur dort gebraucht.
Wirklich nur dateiintern praktisch.

von RAc (Gast)


Lesenswert?

Dann poste den Code.

von Bernd K. (prof7bit)


Lesenswert?

Daniel S. schrieb:
> Nein nein nein. :-)
> Im Header steht gar nichts über den Pointer.
> Da steht nur die Deklaration der Struktur an sich.

Wenn allein der #include schon eine Mehrfachdefinition auslöst dann ist 
in mindestens einem der Header eine Definition vorhanden.

Die ist da irgendwo, die musst Du suchen und finden und eliminieren!

Eigentlich müsste der Compiler Dich in der Fehlermeldung schon mit der 
Nase draufstoßen! Vielleicht hast Du versehentlich eine .c Datei 
includiert, vielleicht indirekt in irgendwas anderem was Du inkludierst, 
folge der Kette der Inklusionen bis zum Ende.

Jegliche Definition hat in einem Header erstmal grundsätzlich nichts 
verloren. Geduldete Ausnahmen im begründeten Einzelfall sind kleine 
static inline Funktionen (beachte das Schlüsselwort static!) und 
eventuell auch static const in solchen Fällen wo das einfach die 
pragmatischste Lösung ist, sollte aber die Ausnahme und nicht die Regel 
sein.

: Bearbeitet durch User
von Dirk B. (dirkb2)


Lesenswert?

Setze den Warnlevel vom Compiler auf Maximum, so dass er alle 
fragwürdigen Dinge meldet.

Eliminiere den Grund der Warnungen. (es ist nicht der hohe Warnlevel)

von Daniel S. (pallas)


Lesenswert?

Ja, das werde ich machen.
Ich muss da auf jeden Fall mal genauer hinschauen.

Danke für eure Hilfe.

von my2ct (Gast)


Lesenswert?

Bernd K. schrieb:
> Wenn allein der #include schon eine Mehrfachdefinition auslöst dann ist
> in mindestens einem der Header eine Definition vorhanden.
>
> Die ist da irgendwo, die musst Du suchen und finden und eliminieren!

Ein vernünftiger Compiler würde verraten, wo die Mehrfachdefinition alle 
stehen, so dass man sie nicht suchen muss.

von Daniel S. (pallas)


Lesenswert?

Ja, daran hatte ich gar nicht gedacht.
Ich benutze den gcc.
Gibt es da eventuell spezielle Schalter?

von Oliver S. (oliverso)


Lesenswert?

Der Compiler würde schon was zielführendes melden, da die Fehlermelding 
aber vom linker kommt, ist die weniger aussagekräftig.

Der verrät dir aber immerhin noch den Namen der Variablen, um die es 
geht. Damit solltest du das Problem doch schnell finden.

Und zur Ausgangsfrage: Ja, die Reihenfolge spielt ein Rolle.

Der Pre-Prozessor ersetzt ein #include schlicht und einfach durch den 
Inhalt der inkludierten Datei. Damit ergibt sich der endgültige 
Sourcecode, den der Compiler zu Gesicht bekommt. Wenn damit was nicht 
stimmt, stimmt halt was nicht.

Oliver

: Bearbeitet durch User
von A. S. (Gast)


Lesenswert?

Poste hier:

Die Warnmeldungen dazu
Alle Zeilen, die in den Warnmeldungen genannt werden
Alle Deklarationen und Definitionen der Variable und in welcher Datei.

Das sind nur ein paar Zeilen.

von Dirk B. (dirkb2)


Lesenswert?

Daniel S. schrieb:
> Ja, daran hatte ich gar nicht gedacht.
> Ich benutze den gcc.
> Gibt es da eventuell spezielle Schalter?

-Wall

Alles warnt der aber auch nicht.
Schau in die Doku.

von W.S. (Gast)


Lesenswert?

Daniel S. schrieb:
> Das komische ist aber, dass eben diese Header-Datei in keiner anderen
> Datei außer eben dem Hauptprogramm inkludiert ist.

Dann gehört sie aus dem Projekt entfernt und ihr Inhalt direkt in dein 
Hauptprogramm eingefügt.

Aber:
"Bei dem Fehler an sich handelt es sich um die Doppeldefinition einer
Variable."
In diesem Falle benenne deine Variable um, denn so etwas ist definitiv 
ein Programmiererfehler. Zwei Variablen gleichen Namens gehören in kein 
Programm.

W.S.

von Bernd K. (prof7bit)


Lesenswert?

W.S. schrieb:
> Zwei Variablen gleichen Namens gehören in kein
> Programm.

Variablen gehören nicht in Header-Files.

von Jdhdiebdkx (Gast)


Lesenswert?

Haben alle Header einen include guard? Ich tippe zu 99% dass der in 
einem der Header fehlt...

von P. S. (namnyef)


Lesenswert?

Ohne Code (Minimalbeispiel!) ist das alles etwas Stochern im Dunkeln.

von Jdhdiebdkx (Gast)


Lesenswert?

Kommt hier noch eine Rückmeldung was es jetzt war?

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.