Hallo an Alle,
programmiere meine Sachen immer auf AVR Studio und nutze meistens selbst
geschriebene Libraries (LCD oder sonst was). In der Zwischenzeit bin ich
etwas ins Zweifeln gekommen über den Sinn/Unsinn von Headerfiles.
Ist es richtig, das ich an und für sich komplett ohne .h Files
programmieren kann (wenn nicht, bitte Beispiele, bin bisher auf nix
gestoßen!)? Wenn doch (also ja), warum sollte ich dann überhaupt welche
anlegen wollen? Wann sind Headers sinnig und wann nicht?
PS: Jupp, habe gesucht & gelesen, aber irgendwie schweigen sich die
Bücher, die ich hier habe, darüber tot und/oder nehmen das einfach als
bereits bekannt an. Also wenn die Frage blöd ist, dann sorry,
programmiere erst seit ca. einem Jahr auf GCC (und damit auch C!).
THNX!!! :) (mal wieder... puh, würde ohne das Forum hier echt alle paar
Monate irgendwo dick hängen bleiben, also noch mal Danke!)
payce wrote:
> Ist es richtig, das ich an und für sich komplett ohne .h Files> programmieren kann
Das ist grundsätzlich richtig.
Die Behauptung folgt sofort aus der Tatsache, dass
der eigentliche C Compiler die Header Files nie zu Gesicht
kriegt.
Header Files werden vom Präprozessor aufgelöst. Der ersetzt
die Zeile
#include "xyz"
durch den Inhalt der Datei xyz und erst dann geht der so
bearbeitete Quelltext durch den C Compiler.
> gestoßen!)? Wenn doch (also ja), warum sollte ich dann überhaupt welche> anlegen wollen? Wann sind Headers sinnig und wann nicht?
Das Problem ist ein anderes.
Normalerweise sind Programme ein bischen umfangreicher als ein
paar Dutzend Programmzeilen. Eine Programmgröße von ein paar
Millionen Code Zeilen sind auch in C in professioneller Projekt-
arbeit nicht ungewöhnlich (zugegeben: nicht auf einem AVR. Aber
C wird ja nicht nur im µC Bereich eingesetzt).
Nun kann man aber nicht eine einzelne Textdatei machen in der
2 Millionen Lines of Code enthalten sind. Genauer gesagt:
technisch ist das schon möglich nur im Licht der Projektwartung
macht das keinen Sinn.
Daher geht man her und unterteilt ein Projekt in Module. Jedes
Modul kommt in eine eigene *.c Datei. Jede *.c Datei wird
unabhängig von allen anderen *.c Dateien compiliert und die
einzeln compilierten Module werden dann zum kompletten Programm
zusammengelinkt.
Das hat neben einer besseren Übersicht auch noch einen weiteren
Vorteil: Wenn eine Änderung in einer Funktion in einem Modul
notwendig ist, dann muss auch nur dieses Modul neu compiliert
werden. Durch den abschliessenden Linker-Schritt wird dann aus
den Einzelteilen wieder ein komplettes Programm zusammengestellt.
Ein einzelnes *.c File mit vielleicht 200 Zeilen zu kompilieren
geht aber schneller als wenn 2 Millionen Zeilen übersetzt werden
müssen.
Wie kommen da aber jetzt Header Files ins Spiel?
Nun. Ein Modul stellt ja Funktionen bereit, die irgendwelche
Dinge tun. Damit eine Funktion aber aufgerufen werden kann,
muss den Compiler gezeigt werden
* dass es diese Funktion gibt
* Anzahl und Datentyp der Argumente, die die Funktion nimmt
* Datentyp des Return Typs der Funktion
Das alles kann man dem Compiler mit einem Prototypen zeigen
1
voidfoo(doublea,intb);
2
3
intmain()
4
{
5
// da es einen Prototypen für foo gibt, kann die Funktion
6
// ohne Probleme aufgerufen werden
7
foo(2.0,4);
8
}
wird in einer anderen *.c Datei ebenfalls die Funktion foo benutzt,
so braucht sie ebenfalls einen Protoyp (Zur Erinnerung: Einzelne
*.c Dateien werden ja unabhängig voneinander compiliert)
Hast du also 30 *.c Dateien, in denen jeweils die Funktion foo
benutzt werden soll, so muss in jede *.c ein Prototyp hinein.
Aber jetzt kommts: Der Compiler hat keine Chance zu überprüfen,
ob der angegebene Prototyp auch mit der tatsächlichen Funktions-
signatur übereinstimmt. Er muss dem Programmierer in dieser Hinsicht
vertrauen.
Speziell geht es jetzt um den Fall, dass sich die Signatur von
foo aus irgendeinem Grund ändert. Sagen wir mal foo liefert jetzt
einen double zurück.
Dann müssen natürlich all die Protoypen in all den *.c Files
geändert und angepasst werden. Und wehe du vergisst einen.
Aber es gibt einen Ausweg: Man kann den eigentlichen Protoyp
in eine eigene Datei auslagern
xyz.h
*****
void foo( double a, int b );
und diese Datei wird mittels einer Präprozessor Anweisung
in jedes *.c hineingezigen in der der Protoyp gebraucht wird:
main.c
******
#include "xyz.h"
int main()
{
foor( 2.0, 3 );
}
Nachdem der Präprozessor sich main.c vorgenommen hat, ist
die Zeile #include "xyz.h" durch den Inhalt der Datei xyz.h
ersetzt worden. Der Quelltext von main.c wird also modifiziert zu:
void foo( double a, int b );
int main()
{
foo( 2.0, 3 );
}
und dieser Quelltext wird durch den C Compiler gejagt.
Aber: Dasselbe kann man auch mit den 30 anderen *.c Dateien
machen, die ebenfalls die Funktion foo benutzen wollen.
Es gibt aber grosse Unterschiede
* erstens sind in einem Header File normalerweise mehrere
Protoypen enthalten. Durch den #include spart man daher
Tipparbeit und dadurch wird natürlich auch die Tippfehler-
häufigkeit reduziert
* zum zweiten ist es so, dass bei einer Änderung am Protoypen
diese Änderung nur einer einer Stelle gemacht werden muss,
nämlich in der Datei xyz.h
Alle *.c Dateien bekommen diese Änderung automatisch mit, da
ja die Einbindung von xyz.h erst kurz vor dem Compilieren
automatisch vorgenommen wird.
>> PS: Jupp, habe gesucht & gelesen, aber irgendwie schweigen sich die> Bücher, die ich hier habe, darüber tot und/oder nehmen das einfach als> bereits bekannt an. Also wenn die Frage blöd ist, dann sorry,> programmiere erst seit ca. einem Jahr auf GCC (und damit auch C!).>> THNX!!! :) (mal wieder... puh, würde ohne das Forum hier echt alle paar> Monate irgendwo dick hängen bleiben, also noch mal Danke!)
Also erstmal: WOW!!! Ausführlichste Antwort! Haaaammer! Vielen, vielen
Dank.
Auf jeden Fall beruhigt mich Deine Antwort schon sehr, hab ich also
verständnissmäßig nix falsch gemacht. Und auch das anschauliche
Beispiel, wann sowas sinnig wird war sehr hilfreich. Also dann werde ich
mir bei der µC Programmierung erstmal Headers sparen. Falls es jemals
doch notwendig werden sollte (kann ich mir momentan noch nicht
vorstellen) kann man das ja noch nachpflegen - ist dann halt einmal
etwas mehr Aufwand. Ansonsten werde ich so weiter machen wie bisher
(sprich: Kleine, modulare .c & .s Libraries mit entpsrechenden
Funktionen ohne Headers).
Vorschlag von meiner Seite: Deinen Absatz sollte man unbedingt als
Artikel abspeichern oder auch gleich mit ins GCC Tutorial einspeisen
(meinetwegen im Anhang). Mir hats echt weiter geholfen!
Danke noch mal, gute Nacht und Grüße payce! :D
payce wrote:
> Also erstmal: WOW!!! Ausführlichste Antwort! Haaaammer! Vielen, vielen> Dank.>> Auf jeden Fall beruhigt mich Deine Antwort schon sehr, hab ich also> verständnissmäßig nix falsch gemacht. Und auch das anschauliche> Beispiel, wann sowas sinnig wird war sehr hilfreich. Also dann werde ich> mir bei der µC Programmierung erstmal Headers sparen.
Dann hab ich mein Ziel verfehlt.
Du sollst dir Header nicht sparen, sondern überlegen wie
du deine Projekte modular aufbauen kannst. In dem Moment,
indem du Module hast, kommst du vernünftigerweise um
Header Files nicht mehr drum herum.
Es kann jetzt allerdings auch sein, dass wir aneinander
vorbeireden.
Wenn dein Aufbau so aussieht
1
intmain()
2
{
3
foo()
4
}
5
6
voidfoo()
7
{
8
bar();
9
}
10
11
voidbar()
12
{
13
}
dann ist ein Header File mit den Protoypen für foo() und bar()
natürlich Blödsinn.
Solche Dinge löst man am besten auch nicht mit Protoypen,
wie hier:
1
voidfoo();
2
voidbar();
3
4
intmain()
5
{
6
foo()
7
}
8
9
voidfoo()
10
{
11
bar();
12
}
13
14
voidbar()
15
{
16
}
sondern indem man die Reihenfolge der Funktionen umdreht:
1
voidbar()
2
{
3
}
4
5
voidfoo()
6
{
7
bar();
8
}
9
10
intmain()
11
{
12
foo()
13
}
Grundsatz:
Ein Protoyp der nicht gebraucht wird, kann auch nicht falsch sein.
Nur in dem Fall, in dem du Module hast, sagen wir mal eine UART
Ansteuerung, macht dann das Konzept auch wirklich Sinn.
Du hast vielleicht ein
uart.c
******
1
voidInitUart(unsignedintBaud)
2
{
3
....
4
}
5
6
voidUart_Putc(charc)
7
{
8
....
9
}
10
11
voidUart_puts(constchar*String)
12
{
13
...
14
}
15
16
voidUart_putint(intValue)
17
{
18
...
19
}
dann wäre es natürlich töricht, wenn du das Programm welches
die Uart Funktionen benutzt so schreiben würdest:
main.c
******
1
...
2
3
voidInitUart(unsignedintBaud)
4
voidUart_Putc(charc);
5
voidUart_puts(constchar*String);
6
voidUart_putint(intValue);
7
8
intmain()
9
{
10
InitUart(9600);
11
12
Uart_puts("Hello and Welcome\r\n");
13
}
Diese Protoypen kommen besser in ein Uart.h
Uart.h
******
1
voidInitUart(unsignedintBaud)
2
voidUart_Putc(charc);
3
voidUart_puts(constchar*String);
4
voidUart_putint(intValue);
und damit wird das Programm zu
main.c
******
1
...
2
3
#include"Uart.h"
4
5
intmain()
6
{
7
InitUart(9600);
8
9
Uart_puts("Hello and Welcome\r\n");
10
}
bzw.
uart.c
******
1
#include"Uart.h"
2
3
voidInitUart(unsignedintBaud)
4
{
5
....
6
}
7
8
voidUart_Putc(charc)
9
{
10
....
11
}
12
13
voidUart_puts(constchar*String)
14
{
15
...
16
}
17
18
voidUart_putint(intValue)
19
{
20
...
21
}
Zum einen hast du dadurch in main.c den Tippaufwand weg und
zum anderen hast du durch die Inclusion in Uart.c eine gewisse
Sicherheit, dass die Protoypen mit den Funktionen auch tatsächlich
übereinstimmen.
Deine Uart-Bibliothek besteht damit aus 2 Teilen
* aus Uart.c, welches die tatsächliche Implementierung
durchführt
* aus Uart.h, welches alles Notwendige enthält, damit ein
Programm den 'Service' des Uart Moduls auch in Anspruch
nehmen kann.
In der Headerdatei wird das Interface einer Bibliothek definiert und
beschrieben. Das ist doch eine sinnvolle und saubere Form. Ich will doch
nicht jedesmal, wenn ich eine Bibliothek einbinde, in den .c-Files
nachschauen, welche Funktionen es gibt und dafür dann Prototypen
erstellen. Und dann noch schauen, welche anderen Bibliotheken ich alles
einbinden muss, damit das geschnürte Gesamtpaket dann funktioniert.