Als ich vor Jahrzehnten die C-Programmierung gelernt hatte, wurde uns beigebracht, dass ausführbarer Code und globale Variablendefinitionen, also alles was Speicher belegt, in *.c Files gehören. Die Headerfiles seien Variablendeklarationen und Funktionsprototypen vorbehalten, so dass man den gleichen Header per #include in mehrere Files einbinden kann. Ähnliches kenne ich auch für C++, wo die Klassendefinitionen (eigentlich -deklarationen) in die Header, die Methodenimplementierung in die *.cpp Files gehören.
Nun sehe ich immer öfter, vor allem im Arduino-Umfeld, dass ausführbarer Code in die *.h - Files verlagert wird. Teilweise gibt es nur gar keine *.c bzw. cpp Files mehr, wie z.B. hier:
Nun sehe ich immer öfter, vor allem im Arduino-Umfeld, dass ausführbarer
Code in die *.h - Files verlagert wird. Teilweise gibt es nur gar keine
*.c bzw. cpp Files mehr, wie z.B. hier:
Ein Verzeichnis drüber gibt es main.cpp und app.cpp!
Dein genanntes Verzeichnis definiert lediglich CPP-Klassen, ohne diese jedoch zu instanziieren. Also alles so, wie ich es gelernt habe.
Genau! Der letzte Satz des Wiki-Artikels erklärt es kurz und knapp:
For C++ templates, including the definitions in header is the only way to >compile, since the compiler needs to know the full definition of the
templates in order to instantiate.
Als ich vor Jahrzehnten die C-Programmierung gelernt hatte, wurde uns
beigebracht, dass ausführbarer Code und globale Variablendefinitionen,
also alles was Speicher belegt, in *.c Files gehören.
Was mal wieder deutlich mach, dass C++ keine Erweiterung von C ist.
Bei C ist zu beachten, dass inline Funktionen sich anders verhalten als in C++.
Wenn eine Header Datei folgendes enthält:
1
inlinevoidhello(void){
2
puts("Hello World");
3
}
Dann wird die Funktion eventuell geinlined, aber nicht zwangsläufig.
In C++ gibt es automatisch ein weak symbol, aber nicht in C.
In C muss man mit extern in genau einer Compilation Unit das Symbol exportieren. Also in der regel in einem .c File.
1
#include"hello.h"
2
externvoidhello(void);
Das fiese ist, wenn man es vergisst, kann das manchmal gut gehen, aber auch plötzlich nicht mehr, jenachdem, ob der Compiler die Funktion inlinet oder nicht.
Alternativ gibt es noch "static" und "static inline". Dort bekommt jede compilation unit seine eigene Kopie der Funktion, die nur für diese gilt. Ist aber nicht unbedingt hierfür gedacht.
In C muss man mit extern in genau einer Compilation Unit das Symbol
exportieren.
Das könnte man mit /static inline/ umgehen. Die Definition kommt in die *.h-Datei, und im /worst case/, also wenn der Compiler das /inline/-Attribut ignoriert, landet die dann als separate Funktion in jeder /compilation unit/, die diese *.h-Datei einbindet.
Damit ist zwar nichts gewonnen, aber man hat keine Scherereien mit dem Linker.
Andererseits würden einem Scherereien mit dem Linker klar signalisieren, daß der Compiler aus irgendeinem Grund keine Lust auf das Inlining hat.
Diese.c/.h file trennung verstößt gegen eine elementare Regel der Programmierung: "dry" - don't repeat yourself.
Klar gab es früher Gründe, warum man das so implementiert hat, aber die waren (und sind) fast ausschließlich technischer Natur.
Diese.c/.h file trennung verstößt gegen eine elementare Regel der
Programmierung: "dry" - don't repeat yourself.
Dennoch ist sie ein elementarer Bestandteil der C- und C++-Programmierung.
Auch in Assemblerprojekten, die über triviale ein-Quelltextdatei-Projekte hinausgehen, gibt es vergleichbares; wenn eine Funktion aus einem anderen Modul aufgerufen werden soll, muss der Assembler das Symbol kennen und wissen, daß er es als externe Referenz in die Objektdatei einträgt, damit der Linker es auflösen kann.
Klar, in Assembler genügt es, den Namen des Symbols in einer *.inc-Datei o.ä. zu vermerken, da es keine Typprüfung gibt, sind weitere Details überflüssig.
Diese.c/.h file trennung verstößt gegen eine elementare Regel der
Programmierung: "dry" - don't repeat yourself.
Diese .c/.h file Trennung führt zu einer Trennung von Code und dessen Interface.
Das definieren sauberer Interfaces ist fast wichtiger als dessen Implementation.
Es macht auch alles um einiges Modularer. Es erlaubt auch, dass Interfaces und deren Implementationen von unterschiedlichen Personen bereitgestellt werden, da nicht die Implementation das Interface vorgibt.
Diese .c/.h file Trennung führt zu einer Trennung von Code und dessen
Interface.
Ich vermute, daß sich Norbert darauf bezieht, daß der Text, der als Funktionsprototyp in einer *.h-Datei angegeben wird, halt /in Kopie/ auch zum Funktionsrumpf in der Implementierung gehört.
Und wenn man etwas daran ändert, muss man darauf achten, das in beiden Dateien zu tun, sonst kann man lustige Effekte erleben.
Diese .c/.h file Trennung führt zu einer Trennung von Code und dessen
Interface.
Nein das tut sie hoffentlich nicht.
Steht ja in beiden Dateien bis auf kleinste syntaktische Unterschiede genau das gleiche. Es hat sich einfach historisch so entwickelt. Und so funktionieren C Compiler jetzt eben.
(Oder vielleicht gibt's ja in 20 Jahren auch sowas wie C++ modules für C?)
Natürlich ist es wichtig sich Gedanken über seine Interfaces zu machen. Aber es ist defakto in C einfach sehr umständlich (für den Programmierer, nicht für den Compiler Entwickler) gelöst. Das gleiche Definition einfach zu wiederholen, ist bestimmt nicht der Weisheit letzter Schluss. Ebenso das stumpfe Textersetzen bei #include.
Aber all das möchten wir C/C++ ja gar nicht übel nehmen (C++ vielleicht schon ein bisschen). Es ist einfach relativ früh entstanden und die Erfinder haben sich mit Sicherheit nicht träumen lassen, dass nach all den Jahre, C (und C++ mittlerweile) für viele Plattformen noch immer quasi alternativlos sind.
Das könnte man mit /static inline/ umgehen. Die Definition kommt in die
*.h-Datei, und im /worst case/, also wenn der Compiler das
/inline/-Attribut ignoriert, landet die dann als separate Funktion in
jeder /compilation unit/, die diese *.h-Datei einbindet.
Neben "static inline" und extern inline gibt es noch eine 3te Variante: extern GNU inline: Die alte Semantik von GCC's extern inline, die inkompatibel war zu inline von C99.
Diese Semantik wird immer noch unterstützt. Beispiel aus ctype.h der AVR-LibC:
Semantik ist die folgende: Wenn die Funktion nicht geinlinet wird (z.B. weil die Adresse der Funktion genommen wird), dann wird isxdigit im Gegensatz zum Standard-extern-inline /nicht/ instanziiert, sondern es wird isxdigit aufgerufen. Die Implementierung von isxdigit muss also irgendwo anders vorhanden sein (hier: libc.a).
Im Gegensatz zum ersten Anschein beschreibt der Code oben also keine unendliche Rekursion, sondern den kleinen Fußabdruck einer Assembler-Implementierung :-)
Nun sehe ich immer öfter, vor allem im Arduino-Umfeld, dass ausführbarer
Code in die *.h - Files verlagert wird. Teilweise gibt es nur gar keine
*.c bzw. cpp Files mehr, wie z.B. hier:
Ist das Arduino-spezifisch oder ein neues Programmierparadigma, von dem
ich bisher nichts mitbekommen habe?
Hallo,
das hat nichts mit Arduino zu tun. Arduino ist doch nur ein Framework. Arduino ist gewöhnliches C++.
Seit es Template Programmierung gibt muss man "Header only" programmieren.
Das heißt im Umkehrschluss man kann auch ohne Template Programmierung Header only schreiben. Hätte man schon vorher Heady only schreiben können. Wofür benötigt man eigentlich die .cpp Implementationsdatei? Benötigt man doch eigentlich nur für gekapselte Variablen/Daten. Oder weil man es Jahrelang gewohnt ist. Ich programmiere fast nur noch Heady only. Ist einfacher zu überblicken, man muss nicht 2 Dateien pflegen. Für extra Kapselung verwende ich Namespace. Wenn man Klassen schreibt empfinde ich Header only angenehmer. Es fällt fiel Tipparbeit weg und übersichtlicher.
Wenn für C++ die neue Art Modulprogrammierung fertig ist, dann schreibt man sowieso nur noch eine Datei und die Trennung hat sich erledigt.
Als ich vor Jahrzehnten die C-Programmierung gelernt hatte, wurde uns
beigebracht, dass ausführbarer Code und globale Variablendefinitionen,
also alles was Speicher belegt, in *.c Files gehören. Die Headerfiles
seien Variablendeklarationen und Funktionsprototypen vorbehalten, so
dass man den gleichen Header per #include in mehrere Files einbinden
kann. Ähnliches kenne ich auch für C++, wo die Klassendefinitionen
(eigentlich -deklarationen) in die Header, die Methodenimplementierung
in die *.cpp Files gehören.
Ja, so wurde das vor Jahrzehnten mal gelehrt. Bei mir wurde das allerdings damit begründet, daß man dann nur den Binärcode weitergeben müsse und der Empfänger denselben, zusammen mit den Headerdateien, in eigenen Programmen verwenden könne. Ansonsten war das immer nur eine Konvention, an die sich manche gehalten haben, und andere -- auch damals schon -- nicht.
Nun sehe ich immer öfter, vor allem im Arduino-Umfeld, dass ausführbarer
Code in die *.h - Files verlagert wird. Teilweise gibt es nur gar keine
*.c bzw. cpp Files mehr, wie z.B. hier:
Ist das Arduino-spezifisch oder ein neues Programmierparadigma, von dem
ich bisher nichts mitbekommen habe?
Das hat nichts mit Arduino zu tun, schau Dir etwa die Boost-Bibliotheken [1] genauer an. Die Leute, die daran arbeiten, sind häufig dieselben, die in den Standardisierungsgremien für C++ und STL sitzen.