Forum: Compiler & IDEs Ausführbarer Code im .h File ?


von Fritz G. (fritz65)


Lesenswert?

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?

von Adam P. (adamap)


Lesenswert?

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
von Nick (gogol)


Lesenswert?

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.

von Andreas D. (diner)


Lesenswert?

Stichwort: Header-only library
https://en.m.wikipedia.org/wiki/Header-only

von Nick (gogol)


Lesenswert?

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
von A. B. (Firma: ab) (bus_eng)


Angehängte Dateien:

Lesenswert?

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
von Oliver S. (oliverso)


Lesenswert?

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

von Daniel A. (daniel-a)


Lesenswert?

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
von Harald K. (kirnbichler)


Lesenswert?

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

von Norbert (nfet)


Lesenswert?

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.

von Harald K. (kirnbichler)


Lesenswert?

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.

von Daniel A. (daniel-a)


Lesenswert?

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.

von Harald K. (kirnbichler)


Lesenswert?

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.

von Norbert (nfet)


Lesenswert?

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.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

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 :-)

von Veit D. (devil-elec)


Lesenswert?

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.

von Sheeva P. (sheevaplug)


Lesenswert?

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
Noch kein Account? Hier anmelden.