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: https://github.com/lumapu/ahoy/tree/main/src/hm Ist das Arduino-spezifisch oder ein neues Programmierparadigma, von dem ich bisher nichts mitbekommen habe?
Also in C wären das "inline Funktionen" und in C++ "inline Methoden" (expliztit oder implizit). Nutze ich oft, wenn ich nicht extra ein Funktions-Call machen möchte, wenn der Inhalt und die Verarbeitungszeit der Fuktion minimal ist. Inwieweit das auf dein C++ Arduino Bsp. zutrifft kann ich dir nicht sagen. Das alles hat zumindest in C seine Vor- und Nachteile. Muss man halt immer Situationsbedingt abwägen.
:
Bearbeitet durch User
Fritz G. schrieb: > 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: > > https://github.com/lumapu/ahoy/tree/main/src/hm 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.
:
Bearbeitet durch User
Und wenn man die Methoden der Klassen "static" definiert, kann man mit "templates" die Funktionalität als template<typ> in andere Klassen einbringen. Es braucht keine Deklaration der Klasse, sondern nur eine Definition des Klassentyps mit
1 | using meinPin = TPin; |
2 | |
3 | //********************
|
1 | template< class RS, // Funktionalität pin RS |
2 | class RW, // Funktionalität pin RW |
3 | class E, // Funktionalität pin E |
4 | class D4, // Funktionalität pin D4 |
5 | class D5, // Funktionalität pin D5 |
6 | class D6, // Funktionalität pin D6 |
7 | class D7, // Funktionalität pin D7 |
8 | uint8_t LINE_WIDTH=8, // const |
9 | uint8_t LINES =2 > // const |
10 | //***********************
|
11 | class Lcd: public LcdBase { |
12 | //*********
|
13 | |
14 | .. details siehe Anhang .. |
15 | |
16 | static uint8_t |
17 | Read() { |
18 | //******
|
19 | DataBus::template SetConfiguration<DataBus::In>(); |
20 | RW ::Set(); |
21 | E ::Set(); |
22 | // Delay(); // obsolete
|
23 | uint8_t res = DataBus::Read() << 4; |
24 | E ::Clear(); |
25 | Delay(); |
26 | E ::Set(); |
27 | res |= DataBus::Read(); |
28 | E ::Clear(); |
29 | RW ::Clear(); |
30 | return res; |
31 | }
|
32 | }; //.Lcd |
33 | |
34 | //**************************
|
35 | |
36 | using Lcd1 = Lcd<Pc0, Pc1, Pc2, Pb4, Pb5, Pb6, Pb7 >; |
37 | |
38 | int main() { |
39 | Lcd1::Init(); |
40 | Lcd1::Puts("Hello world!", 12); |
41 | |
42 | while(1) {} |
43 | }
|
:
Bearbeitet durch User
Andreas D. schrieb: > Stichwort: Header-only library > https://en.m.wikipedia.org/wiki/Header-only So ist es. Fritz G. schrieb: > 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. Andere Sprache, andere Regeln. Oliver
Bei C ist zu beachten, dass inline Funktionen sich anders verhalten als in C++. Wenn eine Header Datei folgendes enthält:
1 | inline void hello(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 | extern void hello(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.
:
Bearbeitet durch User
Daniel A. schrieb: > 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. Hmm. Aber, wie heißt es so schön nach D. Knuth? Premature optimization is the root of all evil
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.
Norbert schrieb: > 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.
Norbert schrieb: > 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.
Daniel A. schrieb: > 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.
Daniel A. schrieb: > 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.
Harald K. schrieb: > 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:
1 | #define __ATTR_ALWAYS_INLINE__ __inline__ __attribute__((__always_inline__))
|
2 | |
3 | #ifdef __GNUC_STDC_INLINE__
|
4 | #define __ATTR_GNU_INLINE__ __attribute__((__gnu_inline__))
|
5 | #else
|
6 | #define __ATTR_GNU_INLINE__
|
7 | #endif
|
8 | |
9 | extern __ATTR_ALWAYS_INLINE__ __ATTR_GNU_INLINE__ |
10 | int isxdigit (int __c) |
11 | {
|
12 | register int __r24 __asm("r24") = __c; |
13 | __asm ("%~call isxdigit" : "+r" (__r24)); |
14 | return __r24; |
15 | }
|
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 :-)
Fritz G. schrieb: > 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.
Fritz G. schrieb: > 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: > > https://github.com/lumapu/ahoy/tree/main/src/hm > > 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. [1] https://www.boost.org/
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.