Hallo!
Ich würde mich noch als C-Anfänger bezeichnen. Habe einiges in Assembler
gemacht und schwenke jetzt auf C um. Ich habe mir bereits den Artikel zu
Include-Files durchgelesen und der verlinkte Thread in diesem Artikel
bietet viel Information. Trotzdem ist mir noch eine Frage offen
geblieben.
Nehmen wir ein ganz einfaches Beispiel. Eine Tasterentprellung. Ich habe
also eine Timer.c, eine Main.c und eine Funktion.c
In der Timer.c ist mein Code vom Timer-Interrupt, also für die
Entprellung, welche, wenn ein Tastendruck erkannt wurde, eine Variable
"flag" auf 1 setzt. Diese Variable wird im Hauptprogramm in der Main.c
abgefragt und wenn "flag" true ist, wird die Funktion aus der Funktion.c
ausgeführt. Die Variable flag ist also global und wegen Interrupt ist
sie volatile. Ich würde diese Variable in der Timer.c definieren. Jetzt
muss aber in der Main.c diese Variable ja auch bekannt
gemacht/deklariert werden. Also mit extern flag. Meine Frage ist jetzt,
schreibe ich das extern flag in die Main.h oder in die Timer.h und
includiere die Timer.h dann in der Main.h?
Und wie verhält es sich mit der Funktion aus Funktion.c? Setze ich einen
Prototypen in meine Funktion.h und includiere diese dann in der Main.h
oder schreibe ich den Prototypen in die Main.h?
Sicherlich für manche eine etwas lächerliche Frage, aber ich als
Anfänger kann gerade einfach nicht abschätzen, was bei größeren
Projekten wohl übersichtlicher wäre. Ich danke euch schonmal für eure
Antworten.
MfG Dennis
Dennis H. schrieb:> Meine Frage ist jetzt,> schreibe ich das extern flag in die Main.h oder in die Timer.h und> includiere die Timer.h dann in der Main.h?
Kann man machen wie man will, ich schreibe globale Variablen immer in
die main.h
Dennis H. schrieb:> Und wie verhält es sich mit der Funktion aus Funktion.c? Setze ich einen> Prototypen in meine Funktion.h und includiere diese dann in der Main.h> oder schreibe ich den Prototypen in die Main.h?
Funktionsprototypen würde ich immer in die dazugehörige *.h packen.
Headerfiles includest du immer nur in *.c Files, nicht in header, das
führt schnell zu Chaos.
Man muss sich eine Strategie überlegen. Entweder in alle Sourcefiles
nur die main.h , dann includest du alle anderen Header in die main.h,
oder du includest in die Sourcefiles nur die Header die du auch
benötigst. Ist glaube ich geschmackssache...
Ingo
Hallo!
Ingo schrieb:> Kann man machen wie man will, ich schreibe globale Variablen immer in> die main.h
Das macht Sinn, hätte ich auch selbst drauf kommen können.
Ingo schrieb:> Funktionsprototypen würde ich immer in die dazugehörige *.h packen.> Headerfiles includest du immer nur in *.c Files, nicht in header, das> führt schnell zu Chaos.
Mh, aber genau das wird im angesprochenen Artikel über Include-Files
gemacht. Weil gerade ein Main.c kann es ja dann passieren, das man
Massen von *.h Dateien includieren muss und der Code wieder etwas lang
wird. Wenn das in der Main.h alles includiert wird, was die Main.c
benötigt sollte das doch nicht verkehrt sein. Und gegen
Mehrfachincludierung kann man sich ja absichern, wie im Artikel
beschrieben und auch bei vielem Code schon so gesehen.
MfG Dennis
Ja, aber wenn du die main.h noch woanders includest z.B. für die
globalen Variablen? Mmmh, ich weiß nicht. Dann mach's doch so wie ich es
noch vorgeschlagen habe, alle Header in die main.h und die main.h dann
in alle Sourcefiles. Ich denke das man dein Artikel auch.
Aber grundsätzlich beschreibt ein Header das dazugehörige Sourcefile,
die main.h kann da offensichtlich eine Ausnahme sein.
Ingo
Dennis H. schrieb:> Mh, aber genau das wird im angesprochenen Artikel über Include-Files> gemacht. Weil gerade ein Main.c kann es ja dann passieren, das man> Massen von *.h Dateien includieren muss und der Code wieder etwas lang> wird.
Ingo hat hier auch nicht ganz recht.
Es kann auch und es ist auch sinnvoll, Header Files in Header Files zu
inkludieren.
> Wenn das in der Main.h alles includiert wird, was die Main.c> benötigt sollte das doch nicht verkehrt sein.
Das allerdings ist die falsche Vorgehensweise.
Um solche Dinge zu entscheiden, haltest du dich einfach an die ganze
einfache Regel:
Wenn ich nur dieses eine File betrachte, losgelöst von allen anderen
(egal ob h-File oder c-File): Welche Informationen von ausserhalb
benötigt dieses File?
Das ist die Frage, die du dir stellst und die dich zur Entscheidung
bringt welche anderen Files zu inkludieren sind.
Hast du also ein Head File, in dem eine Funktion deklariert wird, die
einen Struktur-Pointer als Argument nimmt
1
#ifndef FILEA_H_INCLUDED
2
#define FILEA_H_INCLUDED
3
4
voidAddToList(structEntry*pEntry);
5
6
#endif
dann lautet die Frage hier: Wo kommt eigentich der struct Entry her?
Und wenn die Antwort darauf lautet: Der steht in einem File "Entry.h"
1
#ifndef ENTRY_H_INCLUDED
2
#define ENTRY_H_INCLUDED
3
4
#define MAX_ENTRY_TEXT_LEN 20
5
6
structEntry
7
{
8
uint8_ttimeStamp;
9
uint16_ttemperature;
10
chartext[MAX_ENTRY_TEXT_LEN];
11
};
12
13
#endif
dann musst du (ok, in diesem Fall käme man auch mit einer
Forward-Deklaration durch) in FileA.h dann auch Entry.h inkludieren,
damit dort bekannt ist, dass es eine struct Entry auch tatsächlich gibt.
1
[C]
2
#ifndef FILEA_H_INCLUDED
3
#define FILEA_H_INCLUDED
4
5
#include"Entry.h"
6
7
voidAddToList(structEntry*pEntry);
8
9
#endif
Was du aber nicht haben willst, das ist, dass du dem Verwender, zb einer
Datei Messung.c aufbürdest, zu wissen, dass es Entry.h inkludieren muss,
wenn es FileA.h verwenden will.
Also es soll nicht so sein
Messung.c
1
#include"Entry.h"
2
#include"FileA.h"
3
....
wenn der einzige Grund für den Enry.h Include der ist, das er für
FileA.h benötigt wird. Statt dessen soll es so sein
Messung.c
1
#include"FileA.h"
2
...
(und weil sich FileA.h den Entry.h selber zieht, geht das dann auch).
Jedes File, egal ob Header File oder Source File, zieht sich mit einem
#include die Files selber rein, die es selbst benötigt. Gerade bei
Header Files willst du NICHT haben, dass derjenige der das Header File
benutzt wissen muss, welche anderen Header Files zusätzlich auch noch
notwendig sind. Halte dich einfach an die Regel: Jedes File soll für
sich selbst in sich abgeschlossen sein und nicht darauf angewiesen sein,
dass der Verwenderr die Include Reihenfolge richtig hat.
Einzig für das jeweils zusammen gehörende Pärchen aus Header/Source
macht man eine Ausnahme: Wenn FileA.h den Entry.h schon inkludiert, dann
lässt man den #include dafür in FileA.c weg. FileA.c inkludiert sowieso
FileA.h und kommt über diesen Umweg zum entsprechenden Include. D.h.
hier wird eine kleine Ausnahme gemacht und Wissen aus dem Header File
auch im Source File benutzt. Aber nur deswegen, weil dieses File-Pärchen
ja sowieso enger 'gekoppelt' ist als zu allen anderen Files.
Dann fährst du am Besten und am Einfachsten.
Für globale Variablen geht man so vor:
Zu irgend jemanden (irgendeinem Modul) gehören ja die globalen
Variablen. Die Variablen für eine FIFO gehören logisch gesehen zur FIFO.
Die Variablen für eine UART gehören logisch gesehen zur UART. Die
Variablen zur Temperaturmessung gehören zu den Funktionen dazu, die die
Temperaturmessung machen. Die LCD Variablen zum LCD-Modul etc. etc.
Das jeweilige Modul stellt diese Variablen der Allgemeinheit zur
Verfügung. Und es tut dies, in dem es diese Variablen in den 'Pool' für
globale Variablen stellt.
Daraus folgt: Lass die Dinge dort, wo sie hingehören.
Die global verfügbar gemachten Variablen des LCD-Moduls bleiben im
LCD-Modul und werden dort im C File definiert. Im zugehörigen Header
File kommt die extern-Deklaration rein. Die Variablen des UART-Moduls
bleiben im Source-File mit den UART Funktionen definiert. Im Header File
des UART-Moduls kommt die entsprechende extern Deklaration rein. Die
Variablen der Stack Funktionalität (die global verfügbar gemacht werden
sollen) bleiben im Source File mit den Stack-Funktionen ...
1
#include"stack.h"
2
3
uint8_tNrOfEntries;
4
staticuint8_tEntries[STACK_MAX_NR_ENTRIES];
5
6
voidpush(uint8_tvalue)
7
{
8
if(NrOfEntries<STACK_MAX_NR_ENTRIES)
9
Entries[NrOfEntries++]=value;
10
}
11
12
uint8_tpop(void)
13
{
14
if(NrEntries==0)
15
return0;
16
17
returnEntries[--NrOfEntries];
18
}
..., die entsprechenden extern Deklarationen kommen ins Header File zum
Stack.
1
#ifndef STACK_H_INCLUDED
2
#define STACK_H_INCLUDED
3
4
#include<stdint.h>
5
6
#define STACK_MAX_NR_ENTRIES 20
7
externuint8_tNrOfEntries;
8
9
voidpush(uint8_tvalue);
10
uint8_tpop(void);
11
12
#endif
Damit hast du erreicht:
Wenn jemand auf irgendwelche Dinge, die dem Stack betreffen zugreifen
will, sei es Funktionen aufrufen oder auf entsprechende globale
Variablen zugreifen will, dann inkludiert er stack.h und hat damit
seinen Zugriff. Durch den Include von stack.h verfügt er über alles was
er über den Stack wissen muss und was er benötigt um den Stack benutzen
zu können.
Im Einzelfall kann man von diesem Schema auch schon mal abweichen. Aber
im ersten Anlauf fährt man mit so einem Schema meistens sehr gut.
Was du auf keinen Fall haben willst bzw. was du auf jeden Fall vermeiden
willst: Das Code ausserhalb von stack.h bzw. stack.c irgendwelche
Klimmzüge abgesehen vom include machen muss, um mit dem Stack arbeiten
zu können. Bzw. auch die Umkehrung: Wenn etwas nicht im Header File
auftaucht, dann ist das für den Verwender (zb die Temperatur-Funktionen)
der klare Fingerzeig: "Lass das in Ruhe - du hast an diesen Variablen
nichts verloren!". Die Temperatur-Aufnehme-Funktionen haben sich nicht
direkt am Array 'Entries' zu schaffen zu machen - dafür gibt es
Funktionen, die sie zu benutzen zu haben. 'Entries' taucht daher im
Header File gar nicht auf und wird statt dessen als File-static im
Source File definiert. Für Code der nicht zu den Stack Funktionen
gehört, diese aber verwenden will, steht alles was sie dazu wissen
müssen bzw. brauchen, im Header File und nur im Header File.
Dennis H. schrieb:> Nehmen wir ein ganz einfaches Beispiel. Eine Tasterentprellung. Ich habe> also eine Timer.c, eine Main.c und eine Funktion.c> In der Timer.c ist mein Code vom Timer-Interrupt, also für die> Entprellung, welche, wenn ein Tastendruck erkannt wurde, eine Variable> "flag" auf 1 setzt. Diese Variable wird im Hauptprogramm in der Main.c> abgefragt und wenn "flag" true ist, wird die Funktion aus der Funktion.c> ausgeführt. Die Variable flag ist also global und wegen Interrupt ist> sie volatile. Ich würde diese Variable in der Timer.c definieren. Jetzt> muss aber in der Main.c diese Variable ja auch bekannt> gemacht/deklariert werden. Also mit extern flag. Meine Frage ist jetzt,> schreibe ich das extern flag in die Main.h oder in die Timer.h und> includiere die Timer.h dann in der Main.h?
In Timer.c:
1
#include"Timer.h"
2
3
volatileuint8_tflag;
wobei "flag" nicht wirklich ein guter Name ist.
in Timer.h:
1
#include<stdint.h> // wegen uint8_t
2
3
externvolatileuint8_tflag;
In Main.h:
1
#include"Timer.h"
> Und wie verhält es sich mit der Funktion aus Funktion.c? Setze ich einen> Prototypen in meine Funktion.h und includiere diese dann in der Main.h
Ja. Das geht ganz analog.
> oder schreibe ich den Prototypen in die Main.h?
Nein.
Hallo!
@Karl Heinz
Danke für deine ausführliche Erklärung, auch wenn ich deinen ersten Post
noch ein paar mal lesen muss, um ihn ganz zu verstehen, aber ich denke,
ich bekomme das hin. Das mit den globalen Variablen erscheint logisch,
das ich sie in dem File definiere, in dem sie gebraucht werden, so war
auch mein Gedankengang.
Johann L. schrieb:> wobei "flag" nicht wirklich ein guter Name ist.
Guter oder nicht guter Name lässt sich immer streiten, für mein
simpelst-Beispiel hat er ausgereicht um das ihr wisst, was ich meine :)
Auf jedenfall vielen Dank für die guten Antworten
MfG Dennis