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.
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...
...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.
> 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.
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!
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.
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.
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...
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
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.
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!
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.
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.
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.
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...
@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.