Forum: Mikrocontroller und Digitale Elektronik C - warum separate header files (.h) und nicht generell nur source files (.c)


von Rotkohl (Gast)


Lesenswert?

Ich habe noch nie den Grund für die Trennung zwischen source files und 
header files wirklich verstanden.
Vorneweg: ich bin alles andere als ein gelernter Programmierer, daher 
bitte ich um Nachsicht für diese Proletenfrage.

Aktuell arbeite ich mit dem USB-Framework von Microchip und wundere mich 
erneut. In den header files sind lediglich die Funktionen und Prozeduren 
aufgelistet, welche in den gleichnamigen sourcefiles dann enthalten 
sind.

Auf diese Art müssen immer 2 Dateien mit gleichem Namen (einmal .c und 
einmal .h) parallel gepflegt werden.
Warum kann man nicht einfach nur die sourcefiles einbinden, ohne 
headerfiles?
Wenn es der Lesbarkeit wegen ist, kann man doch einfach in den 
sourcefiles zu beginn alle Prototypen auflisten.

Was ist der Grund für die strenge Trennung der Protoypen auf die header 
files und den Code in den sourcefiles?

Danke Leute vorab für die Erleuchtung, die mir noch gänzlich fehlt.

von TestX .. (xaos)


Lesenswert?

kauf dir ein c/c++ buch und lies es dir durch...
es hat mit dem ganzen build prozess zu tun: erst wird compiliert dann 
werden die objektdateien zum programm gelinkt.
zu dem compilier zeitpunkt "versprechen" die header files die existens 
der funktionen/methoden, da der compiler nur jeweils 1ne sourcefile 
kennt.

in der zeit von interpretersprachen ala. java, python, php ist ein wenig 
sinnlos..da es auch anderst gehen könnte...

von robert (Gast)


Lesenswert?

...weil "C" niemals als ernsthafte Programmiersprache konstruiert wurde. 
Das ganze war ein Scherz von zwei Studenten, welcher auf einem möglichst 
blödsinnigem Konzept, der Verwendung überflüssiger Zeichen, hirnrissiger 
syntaktischer Regeln u.s.w. basierte - sprich: alles falsch machen, was 
man falsch machen kann - und sich dann leider verselbstständigte, weil 
einige langhaarige, lichtscheue, pickelige Kellerkinder (die sich auch 
versuchen mit Aluminiumfolie auf dem Kopf vor Strahlenbeeinflussung 
durch Geheimdienste zu schützen)für bahre Münze genommen wurde. Ein 
dysfunktionales "Werkzeug" von Deppen für Deppen.

von g457 (Gast)


Lesenswert?

> Aktuell arbeite ich mit dem USB-Framework von Microchip und wundere mich
> erneut. In den header files sind lediglich die Funktionen und Prozeduren
> aufgelistet, welche in den gleichnamigen sourcefiles dann enthalten
> sind.

So solls sein :-)

> Auf diese Art müssen immer 2 Dateien mit gleichem Namen (einmal .c und
> einmal .h) parallel gepflegt werden.

Das vereinfacht(!) die Sache erheblich.

> Warum kann man nicht einfach nur die sourcefiles einbinden, ohne
> headerfiles?

Dann braucht jedes(tm) Source-File alle(tm) Forward-Deklarationen 
aller(tm) anderen C-Files. Nicht praktikabel.

> Wenn es der Lesbarkeit wegen ist, kann man doch einfach in den
> sourcefiles zu beginn alle Prototypen auflisten.

Das ist nicht wartbar. Siehe oben.

> Was ist der Grund für die strenge Trennung der Protoypen auf die header
> files und den Code in den sourcefiles?

Erhebliche Vereinfachung und Information-Hiding.

von Rotkohl (Gast)


Lesenswert?

Andi D. schrieb:
> kauf dir ein c/c++ buch und lies es dir durch...
> es hat mit dem ganzen build prozess zu tun: erst wird compiliert dann
> werden die objektdateien zum programm gelinkt.
> zu dem compilier zeitpunkt "versprechen" die header files die existens
> der funktionen/methoden, da der compiler nur jeweils 1ne sourcefile
> kennt.
>
> in der zeit von interpretersprachen ala. java, python, php ist ein wenig
> sinnlos..da es auch anderst gehen könnte...

O.k. das ist wohl der Grund.
In meinen C-Anfängen hatte ich nämlich immer nur mit source-files 
gearbeitet:

in meinem Hauptprogramm hat es z.B. so ausgesehen:
#include <defs.c>           /* Typen-, Variablen- und 
Konstantendefinitinen*/
#include <system.c>         /* System- und Interruptfunktionen  */
#include <tasks.c>          /* Anwendungsroutinen  */
und dann kam irgendwann meine main-schleife.

Ich musste natürlich dafür sorgen, dass z.B. in system.c nicht auf 
Funktionen in tasks.c zugegriffen wurde, aber das war relativ einfach zu 
bewerkstelligen und von der Struktur her sehr übersichtlich.
Die "Low-Level"-Funktionen also immer vor den "High-Level-Funktionen"

Aber das USB-Framework ist natürlich "etwas" umfangreicher als mein 
früherer Proletencode und offenbar verwenden dann die einzelnen Module 
untereinander gegenseitig deren Funktionen, so dass meine banale 
Denkweise hier nicht mehr funktionieren würde, so wie es g457 meint 
(nicht mehr pflegbar wegen der daraus resultierenden vielen Protoypen in 
den einzelnen Modulen).
Ich glaube aus dieser Sichtweise heraus habe ich es dann verstanden.
Falls nicht, bitte nochmal aufklären, ansonsten vielen Dank an die werte 
Gesellschaft!

von Blaukraut (Gast)


Lesenswert?

Diese Header Dateien sind nicht wirklich notwendig, sonst würden diverse 
andere Programmiersprachen auch nicht ohne auskommen. Die sind nur dazu 
da, um Anfänger zu verwirren.

von Simon K. (simon) Benutzerseite


Lesenswert?

Rotkohl schrieb:
> in meinem Hauptprogramm hat es z.B. so ausgesehen:
> #include <defs.c>           /* Typen-, Variablen- und
> Konstantendefinitinen*/
> #include <system.c>         /* System- und Interruptfunktionen  */
> #include <tasks.c>          /* Anwendungsroutinen  */
> und dann kam irgendwann meine main-schleife.

Und genau eben dies ist aus ganz ganz vielen Gründen mehr als 
gefährlich.

> Ich musste natürlich dafür sorgen, dass z.B. in system.c nicht auf
> Funktionen in tasks.c zugegriffen wurde, aber das war relativ einfach zu
> bewerkstelligen und von der Struktur her sehr übersichtlich.
> Die "Low-Level"-Funktionen also immer vor den "High-Level-Funktionen"
Ahja, und das ist jetzt leichter zu pflegen meinst du? In einem großen 
Projekt? Und wenn zwei "High-Level-Funktionen" eine "Low-Level-Funktion" 
benötigen, dann hagelt es sofort duplikate Definitionen.

> Ich glaube aus dieser Sichtweise heraus habe ich es dann verstanden.
> Falls nicht, bitte nochmal aufklären, ansonsten vielen Dank an die werte
> Gesellschaft!
Es hat unter anderem damit zu tun, ja.

von Rotkohl (Gast)


Lesenswert?

Blaukraut schrieb:
> Diese Header Dateien sind nicht wirklich notwendig, sonst würden diverse
> andere Programmiersprachen auch nicht ohne auskommen. Die sind nur dazu
> da, um Anfänger zu verwirren.

Darum rede ich ja auch von C...

von Brater (Gast)


Lesenswert?

Ich muss des Öfteren Schnittstellen ändern, wodurch ich für viele Sachen 
die Trennung von Source und Header aufgegeben habe. Und ja, das spricht 
auf Softwareentwickler-Sicht nicht gerade für Qualität, da 
Schnittstellen sich nicht ändern sollten.

Gerade aus dem Grund, weil das so nervig ist, gibt es einige Tools, die 
den Entwickler beim Programmieren unterstützen sollen und auch eine 
intelligente Funktionskopf-Anpassung ermöglichen.

Oder man macht aus allen Funktionen Template-Funktionen :D

von BoeserFisch (Gast)


Lesenswert?

robert schrieb:
> ...weil "C" niemals als ernsthafte Programmiersprache konstruiert wurde.

Bisschen geschmunzelt hab ich ja. Aber fürs Lachen war die Satire dann 
doch wieder nicht gut genug.

Nun ernsthaft:

Die Header sind sozusagen der Kern des 'Wiederverwertungskonzeptes' von 
C/C++.
Also einfach gesagt: Jemand schreibt Code mit Funktionen, die man gerne 
in anderen Programmen wieder benutzen möchte. Damit die Schnittstelle 
stimmt, muss dafür ein Prototyp deklariert werden.
Nun könnte man den Prototyp natürlich 'inline' (im Source selbst) 
deklarieren, aber ändert man die API ('Application programming 
interface' und das kommt bei der über Jahre hinweg gewarteten und 
möglichst portablen Library immer irgendwann mal vor), kann das 
schrecklich in die Hose gehen, wenn die Prototypen nicht per Header 
genau an einer Stelle deklariert und von allen Nutzern 
(referenzierender Programmcode, inklusive der Implementierung!) 
eingebunden (per #include) werden.
Der Klassiker:

Datei a.c: extern char *g_bla;

Datei b.c: char g_bla[] = "blabla";

Wer's noch nicht kennt: einfach mal ausprobieren, was passiert, wenn man 
aus a.c g_bla per printf ausgibt.

Leider ist C nicht so streng wie z.B. ADA, und erlaubt den nicht auf 
API-Programmierung gebürsteten C-Hackern, sich massiv ins Knie zu 
schiessen und es jahrelang nicht zu merken.

Zu dem restlichen Geunke betr. anderer Programmiersprachen bleibt nur zu 
sagen: Um das Problem einer Bibliotheks-API kommt keine native 
Compilersprache herum, und eine Interpretersprachen-Anbindung will auch 
erst mal geschrieben werden (dreimal darf man raten womit).

Abschliessend einige Faustregeln (siehe auch C-FAQ) für eine 
bestmöglichst wartbare API:

- Nur die Prototypen gehören in die Header, die Implementation in die 
.c sources (#include "bla.c" ist ver bo ten!)
- Vorsicht mit globalen Variablen. Wenn möglich eine Context-Struktur 
verwenden und als abstraktes Handle (siehe 'anonymer struct pointer') an 
die API-Funktion übergeben
- Library User sollten nicht direkt auf Mitglieder einer Struktur direkt 
zugreifen. Wenn doch: Versionskontrolle/Inkompatibilitäts-Handling 
nötig.

Wenn man es 'richtig' macht, liefern ein C-Compiler und Linker sehr 
wertvolle Hinweise, die die Wartung und Portierung von Code, somit die 
Wiederverwertung optimal möglich machen.
Wie man Headerstrukturen optimal entwirft, ist eine eigene Wissenschaft, 
die man oft an den FHs/Unis nicht lernt. Was ich sehr empfehlen kann, 
ist, einfach mal eine Portierung eines OS oder Programms auf eine andere 
Architektur anzugehen.

von Rotkohl (Gast)


Lesenswert?

Ja, danke nochmal für die Erläuterungen.
Das zeigt mir zumindest, dass meine Frage nicht gänzlich bescheuert war 
und diese Thematik doch breit bekannt ist.
Gracias Senores!

von Stefan R. (srand)


Lesenswert?

BoeserFisch schrieb:
> - Nur die Prototypen gehören in die Header, die Implementation in die
> .c sources (#include "bla.c" ist ver bo ten!)


Au ja, erzähl das mal ST, die genau so einen Scheiß in ihrer 
stdperiphlib machen.

Kontakt-E-Mail-Adressen oder sowas in Headern, README oder sonstiger 
Dokumentation sind natürlich auch verboten.

Echt jetzt, die spinnen, die Franzosen.

von Rotkohl (Gast)


Lesenswert?

eines muss ich noch los werden.
Hatte vor ca.2 Jahren mal für eine Abteilung eine etwas umfangreichere 
Lüftersteuerung in C schreiben müssen. Hat wunderbar funktioniert und 
ich habe es natürlich nach meiner "altbewährten" header-freien Methode 
wie oben beschrieben gemacht. Alles schön dokumentiert mit 
Flussdiagrammen und allem was dazugehört. Der Software-man dieser 
Abteilung hat meinen Code dann angeschaut und bemerkt, dass ich einen 
komischen Programmierstil habe. Er hat dann überall separate 
header-files daraus und dazu gemacht, das Projekt wurde m.E. dadurch 
erheblich unübersichtlicher. Aber ein Programmierprofi (er hatte das 
gelernt) braucht das offenbar so. Diese Firmware wurde bis dato zwar nie 
geändert aber der offizielle C-Aufbau musste eingehalten werden.
Naja, damals habe ich das nicht wirklich nachvollziehen können, aber 
wenn ein anderer einmal so eine Software pflegen muss, dann tut er sich 
mit einem "Norm-Aufbau" natürlich leichter, als mit meiner sauber 
dokumentierten Proleten-Programmiererei (bin eher der HW- und 
Assembler-Fuzzi).
Ich lerne ja gerne dazu.

von Roland (Gast)


Lesenswert?

Andi D. schrieb:
> ...
> zu dem compilier zeitpunkt "versprechen" die header files die existens
> der funktionen/methoden, da der compiler nur jeweils 1ne sourcefile
> kennt.
> ...

Das Konzept geht noch weiter:
Die Headerfiles ermöglichen zum Beispiel erst den Einsatz von 
Bibliotheken (libs) und extern vorkompilierten Code aus anderen Sprachen 
(asm, maschinencode). Denn diese Dateien werden vom C-Kompiler ja gar 
nicht angefasst, sondern erst vom Linker.

von BoeserFisch (Gast)


Lesenswert?

An Rotkohl:

Natürlich kann man das Spiel genauso ad absurdum treiben, wenn man es 
einfach 'so gelernt' hat und ohne Pragmatismus 'akademisch richtig' 
macht.

Im Endeffekt will kaum einer eine riesige API mit mehr als 20 Funktionen 
pro Library nach aussen abbilden. Was als geschlossenes Modul gedacht 
ist, soll ruhig auch so bleiben, solange die Prototypen/Instanzen genau 
an einer Stelle existieren. Nur muss man's dann auch konsequent tun 
und nicht exportierte Funktionen möglichst mit "static" dekorieren.

Modularer Code ist einfacher wiederzuverwerten und ev. besser zu 
dokumentieren (Doxygen). Aber ich würde auch kaum einen Code von Anfang 
an auf höchste Modularität abstrahieren (was einige C++-'Coder' wiederum 
tun und sich mit 'OO' ins Knie schiessen).

Allerdings kann ich von einem relativ komplexen Code-Refaktoring-Projekt 
berichten, bei dem es drei Mannjahre gekostet hat, den Spaghetticode zu 
einer nutzbaren Library zu machen. Da hätte man es besser von Grund auf 
neugeschrieben bzw. hätte sich im Nachhinein ca. 200 k€ gespart, wenn 
man von Anfang an auf Wiederverwertbarkeit geachtet hätte.

An Stefan:
Ne Menge dicker Fische machen's falsch, mag daran liegen, dass die 
Ameisenarbeit oft an Leute outgesourct werden, die mit 
API-Programmierung wenig Erfahrung haben...

von Rotkohl (Gast)


Lesenswert?

@BoeserFisch
Ja, Du hast schon recht.
Das Hauptproblem ist wohl, dass man auf manche Programmieraufgaben Leute 
wie mich loslässt, die das Programmieren nie sauber gelernt haben (so 
ein Semster lang Dödel-Informatik für E-Techniker ist da nich ganz der 
Reisser...). Dafür gibt es ja nicht umsonst jede Menge Fachleute.
Aber wenn eben die Kapazitäten beschränkt sind, dann nimmt man auch 
Programmier-LowPerformer wie mich. Mir machts ja auch Spaß aber ich habe 
immer das Gefühl, es nicht wirklich gut zu machen. Und dafür bräuchte 
ich solche Aufgaben öfters, damit sich das Aneignen der korrekten 
Techniken auch wirklich lohnt.

Beitrag #5541875 wurde von einem Moderator gelöscht.
Beitrag #5541983 wurde von einem Moderator gelöscht.
Beitrag #5542038 wurde von einem Moderator gelöscht.
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.