Forum: Compiler & IDEs include in include (oder wo sonst?)


von Horst S. (Gast)


Lesenswert?

GCC

Ich will einige im Controller fortlaufende Nummerierungskonstanten aus 
den entsprechenden Modulheadern herausnehmen und in eine "MagicIds.h" 
packen.

Z.B. standen vorher die Konstanten
1
#ifndef INCL_MAGICIDS_H
2
   #define INCL_MAGICIDS_H
3
4
    // Magic EEPROM page IDs
5
    #define EE_PAGE_DRIVEPARAMS 0
6
    #define EE_PAGE_LIDARPARAMS 1
7
    #define EE_PAGE_PHYSICSPARAMS 2
8
    ...
9
#endif
über die einzelnen Header verteilt.

Die habe ich nun zusammengepackt, damit ich nicht jedes mal neu im 
gesamten Projekt gucken muss, wenn ich eine neue EEPROM-Seite 
implementieren will.

Jetzt kribbelt es mich allerdings irgendwie in den Fingern beim Einfügen 
der include-Direktiven in den einzelnen Header-Dateien.

Include ich jetzt korrekterweise die MagicIds.h aus den Headerdateien 
(Drive.h/ Lidar.h/ Physics.h) oder aus den verwendenden c-Files?

Funktionieren würde beides, jedoch bin ich mir noch nicht ganz über 
Vor-/Nachteile im Klaren.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Horst S. schrieb:
> Include ich jetzt korrekterweise die MagicIds.h aus den Headerdateien
> (Drive.h/ Lidar.h/ Physics.h) oder aus den verwendenden c-Files?

Verwenden die Headerdateien Drive.h Lidar.h Physics.h vielleicht 
Konstanten, die nun in MagicIds.h definiert werden?

Wenn ja, gehört der Include definitv in die Header-Dateien. Sonst 
müsstest Du ja auch darauf achten, dass Du in *.c die richtige 
Reihenfolge inhältst (erst MagicIds.h und dann z.B. erst Drive.h).

Wenn nein, dann gehört der Include in *.c.

Merke: Include immer dort, wo Du das Include auch brauchst.

Include-Guard nicht vergessen!

: Bearbeitet durch Moderator
von Horst S. (Gast)


Lesenswert?

Frank M. schrieb:
> Verwenden die Headerdateien Drive.h Lidar.h Physics.h vielleicht
> Konstanten, die nun in MagicIds.h definiert werden?

Tun sie nicht, aber das Argument mit der Reihenfolge ist gut (hab ich 
z.B. mal wieder komplett ausgeblendet gehabt)

Es wird sogar so sein, dass ich bei den externen Transferfunktionen 
(Parameterblöcke über Bluetooth lesen/schreiben) in einem Modul jetzt 
nur noch die MagicIDs.h benötige, da hatte ich vorher sämtliche 
Referenzen wegen der über die einzelnen Header verteilten Konstanten 
drin.

Irgendwie trotzdem hässlich: Zuerst versucht man krampfhaft, den Scope 
auf die Module über die Header dicht zu halten und anschließend wird die 
Sache sperrig und achlecht wartbar, dann landet man doch wieder in so 
'nem globalen Murks. Vielleicht ist das die eigentliche Ursache meines 
Fingerkribbelns.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Horst S. schrieb:
> Tun sie nicht, aber das Argument mit der Reihenfolge ist gut (hab ich
> z.B. mal wieder komplett ausgeblendet gehabt)
>
> Es wird sogar so sein, dass ich bei den externen Transferfunktionen
> (Parameterblöcke über Bluetooth lesen/schreiben) in einem Modul jetzt
> nur noch die MagicIDs.h benötige, da hatte ich vorher sämtliche
> Referenzen wegen der über die einzelnen Header verteilten Konstanten
> drin.

Beide Argumente sprechen für das Include in *.c.

> Irgendwie trotzdem hässlich: Zuerst versucht man krampfhaft, den Scope
> auf die Module über die Header dicht zu halten und anschließend wird die
> Sache sperrig und achlecht wartbar, dann landet man doch wieder in so
> 'nem globalen Murks.

Ja, das kenne ich. Da kann ich Dir nur empfehlen: So wenig global wie 
möglich. Vieles lässt sich auch static innerhalb eines Moduls abfackeln. 
Dann können die ausschließlich dafür benötigten Konstanten oder 
Datenstrukturen raus aus den Headern und rein in das xxx.c.

Die Header sollten nur das Interface zum Modul deklarieren und mehr 
nicht.

von Dirk B. (dirkb2)


Lesenswert?

Die Headerdateien werden das eingebunden, wo sie gebraucht werden.

Brauchst du sie für Typdefinitionen oder Deklarationen in der .h Datei, 
musst du sie da einbinden.

Wenn du die Infos nur in Code brauchst, dann werden sie in der .c 
eingebunden.

von Horst S. (Gast)


Lesenswert?

Frank M. schrieb:
> Merke: Include immer dort, wo Du das Include auch brauchst.

Kann ich als Faustregel mit leben!!!

Danke.

von W.S. (Gast)


Lesenswert?

Horst S. schrieb:
> Frank M. schrieb:
>> Merke: Include immer dort, wo Du das Include auch brauchst.
>
> Kann ich als Faustregel mit leben!!!

Na hoffentlich.

ich sehe das ganz anders. Mit automatisch inkludierten Rattenschwänzen 
von Headerdateien lebt es sich zwar recht bequem, aber häufig genug 
inludiert man ungewollt Zeugs, was einem später schwer auffindbare Bugs 
beschert.
Deshalb bin ich gar kein Freund von sowas.

W.S.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

W.S. schrieb:
> ich sehe das ganz anders. Mit automatisch inkludierten Rattenschwänzen
> von Headerdateien lebt es sich zwar recht bequem, aber häufig genug
> inludiert man ungewollt Zeugs, was einem später schwer auffindbare Bugs
> beschert.

Beispiel für solch einen Bug?

Wenn ich schreibe:

    Include immer dort, wo Du das Include auch brauchst.

wieso meinst Du dann, dass man "ungewollt Zeugs" includiert? Man 
includiert doch nur das, was man braucht. Mehr kann man aus der 
Faustregel nicht herauslesen. Wenn da bei Dir weiteres "ungewolltes 
Zeugs" in Deinen Header-Dateien herumschwirrt, dann sind Deine 
Header-Dateien eventuell falsch strukturiert.

: Bearbeitet durch Moderator
von Horst S. (Gast)


Lesenswert?

Was spricht gegen folgende Lösung?
Modul-Header, z.B. Drive.h
1
#ifndef INCL_DRIVE_H
2
    #define INCL_DRIVE_H
3
4
    #include "MagicIDs.h"
5
    ...


und nun MagicIDs.h:
1
// Magic EEPROM page IDs
2
#ifdef INCL_DRIVE_H
3
    #ifndef EE_PAGE_DRIVEPARAMS
4
        #define EE_PAGE_DRIVEPARAMS 0
5
    #endif
6
#endif    
7
#ifdef INCL_LIDAR_H
8
    #ifndef EE_PAGE_LIDARPARAMS
9
        #define EE_PAGE_LIDARPARAMS 1
10
    #endif
11
#endif    
12
#ifdef INCL_PHYSICS_H
13
    #ifndef EE_PAGE_PHYSICSPARAMS
14
        #define EE_PAGE_PHYSICSPARAMS 2
15
    #endif
16
#endif

Würde das Chaos der fortlaufenden Nummerierung lösen UND den 
Grundgedanken erhalten: Binde aus den anderen Modulen EINEN Header für 
EIN Modul ein.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Horst S. schrieb:
> Was spricht gegen folgende Lösung?
> [...]
> Würde das Chaos der fortlaufenden Nummerierung lösen UND den
> Grundgedanken erhalten: Binde EINEN Header für EIN Modul ein.

Diese "Lösung" enthält mir zuviele wenn und aber, nämlich die vielen 
#ifdefs. Ich verstehe nämlich nicht, warum Du diese drinhast. Traust Du 
Deiner eigenen Software nicht ("ich könnte das irgendwo schon mal 
definiert haben") oder hast Du dafür einen organisatorischen Grund?

Das sieht mir jedenfalls arg fehlerträchtig aus. Wenn ich etwas über den 
Preprozessor versehentlich zweimal mit #define definiert habe, will ich 
auch die Fehlermeldung dazu sehen, damit ich diesen Missstand beheben 
kann!

> Würde das Chaos der fortlaufenden Nummerierung lösen

Verstehe ich nicht. Muss da bedingt fortlaufend numeriert werden? 
Schon mal enum dazu probiert?

Mein Fazit:

Wenn MagicIds.h lediglich headerübergreifende Konstanten enthält, 
include es in den C-Quellen und nicht über komplizierte Konstruktionen 
in den Headern.

: Bearbeitet durch Moderator
von Horst S. (Gast)


Lesenswert?

Das Problem ist, wie immer, mit diesen "Halden":

Ich weiß morgen nicht mehr, was ich hinzufüge UND ich weiß morgen nicht 
mehr, was ich gelöscht habe. Wenn ich morgen Lidar.c/.h aus dem Projekt 
entferne, hätte ich gerne eine vollständige Änderungs- / Fehlerliste vom 
Compiler.

Heute Nacht kam auf jeden Fall das Kribbeln wieder zurück, als ich mir 
vorstellte, dass in den Transferfunktionen Daten für ein nicht 
vorhandenes Lidar-Modul weiter unterstützt wird, weil ich die MagicIds.h 
nicht angepasst habe. Das wäre für mich eine Leiche.

Insofern fand ich die Lösung oben jetzt gar nicht mal abwegíg.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Horst S. schrieb:
> Heute Nacht kam auf jeden Fall das Kribbeln wieder zurück, als ich mir
> vorstellte, dass in den Transferfunktionen Daten für ein nicht
> vorhandenes Lidar-Modul weiter unterstützt wird, weil ich die MagicIds.h
> nicht angepasst habe. Das wäre für mich eine Leiche.

Ich stecke da zuwenig in Deinem Source drin. Müssen die zwingend 
fortlaufend nummeriert sein oder reicht es, wenn sie alle voneinander 
unterschiedlich sind? Wenn letzteres, benutze Nummernkreise.

Das

#ifdef INCL_DRIVE_H
  ...
#endif

ist ja noch in Ordnung, aber das:

    #ifndef EE_PAGE_DRIVEPARAMS
        #define EE_PAGE_DRIVEPARAMS 0
    #endif

halte ich für gefährlich. Warum nicht einfach:

    #define EE_PAGE_DRIVEPARAMS 0

Durch das #ifdef erlaubst Du mehrdeutige Mehrfachdefinitionen. Zum 
Beispiel könnte woanders noch stehen:

    #define EE_PAGE_DRIVEPARAMS 4711

Der Compiler wird Dich wegen dem #ifdef dann nicht davor warnen, genau 
das zu tun. Und schon hast Du zwei unterschiedliche Werte für ein- und 
dieselbe Konstante. Viel Spaß bei der Fehlersuche!

Versuche immer, soviele Fehler wie möglich vom Compiler zu bekommen.

Dieser #ifdef-Nonsense erinnert mich auch immer wieder an:

#ifndef F_CPU
#define F_CPU 8000000UL
#endif

bei AVR-Projekten. In Makefile (oder noch schlimmer: in xxx.h) wird dann 
12000000 als F_CPU gesetzt und die Verwirrung ist dann für viele 
Anfänger perfekt, die den Source nutzen wollen.

"Gilt jetzt 8MHz oder was anderes"?

Richtig wäre:

#ifndef F_CPU
#error F_CPU not defined
#endif

Aber auf jeden Fall keine Wenn-Dann-Lösung.

: Bearbeitet durch Moderator
von Horst S. (Gast)


Lesenswert?

Neee, Dein #ifdef (ohne n) ist mein #ifndef (mit n), also ein
IF NOT DEFINED. (Hoffe, das ist Dein Problem).

Ich habe etwas gegrübelt, ob ich es brauche. Korrekterweise brauche ich 
es, wenn zuerst Drive.h und anschließend Lidar.h includiert wird.

Beim includieren von Lidar.h wird die Direktive für Drive.h positiv 
bewertet und EE_PAGE_DRIVEPARAMS erneut zugewiesen. Das ist zwar wieder 
der gleiche Wert, aber diese Unsauberkeit durch ein #ifndef zu 
unterdrücken, frisst kein Brot.

So zumindest mein Gedanke.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Horst S. schrieb:
> Neee, Dein #ifdef (ohne n) ist mein #ifndef (mit n), also ein
> IF NOT DEFINED. (Hoffe, das ist Dein Problem).

Ist doch egal, ob die Abfrage mit oder ohne NOT ist, das Problem bleibt 
dasselbe, siehe unten.

> Ich habe etwas gegrübelt, ob ich es brauche. Korrekterweise brauche ich
> es, wenn zuerst Drive.h und anschließend Lidar.h includiert wird.

Sorry, ich kenne Deinen Source nicht. Heisst das, wenn Du die 
Reihenfolge von Drive.h und Lidar.h rumdrehst, brauchst du sie nicht?

Das ist suboptimal und fehlerträchtig.

> Beim includieren von Lidar.h wird die Direktive für Drive.h positiv
> bewertet und EE_PAGE_DRIVEPARAMS erneut zugewiesen.

Das ist schlecht. Ändert sich mal eine Konstante, musst Du immer an zwei 
Stellen ändern. Vergisst Du eine, dann gibts Ärger.

> Das ist zwar wieder
> der gleiche Wert, aber diese Unsauberkeit durch ein #ifndef zu
> unterdrücken, frisst kein Brot.

Das ist noch viel schlechter! Wenn der Wert gleich ist, meckert der 
Preprozessor sowieso nicht bei einer Mehrfachdefinition. Wenn die Werte 
aber ungleich sind, dann meckert er - zu Recht! Und genau diesen Fall 
gilt es zu detektieren!

Durch Dein #ifndef unterdrückst Du genau diesen Mechanismus, der Dir 
eigentlich den Arsch retten würde.

> So zumindest mein Gedanke.

Vergiss ihn.

: Bearbeitet durch Moderator
von Horst S. (Gast)


Lesenswert?

Quatsch!

Überlege Folgendes:
- xxx.c includiert Lidar.h und Drive.h, benötigt aber die Konstanten aus 
MagicIDs.h
- Lidar.h includiert MagicIds.h. Beim includieren nach o.g. Pattern wird 
EE_PAGE_LIDARPARAMS definiert.
- Anschließend wird durch den Verweis auf Drive.h auch 
EE_PAGE_DRIVEPARAMS definiert. Trotzdem die Konstante INCL_LIDAR_H 
bereits definiert ist (durch die Referenz auf Lidar.h), wird die 
Konstante EE_PAGE_LIDARPARAMS NICHT ERNEUT definiert, weil sie schon da 
ist (der Frist-Kein-Brot-Mechanismus)

Das Spiel kannst Du auch umdrehen und zuerst Drive.h und anschließend 
Lidar.h einbinden. Die Reihenfolge ist egal. Eine Konstante wird
a) nur dann definiert, wenn der entsprechende Header eingebunden ist
b) nicht noch einmal definiert, wenn sie bereits definiert wurde. Sollte 
mich irgendwann mal der zweifellos dämliche Gedanke überkommen, sie 
anderswo erneut zu definieren, ist sicher die Stelle im Modulheader vor 
der #Include "MagicIds.h"-Anweisung der richtige Ort zum Überschreiben 
der Konstante in MagicIds.h.


Was ist an diesem Pattern fehlerträchtig? Das Include der MagicIds.h aus 
den einzelnen Modulheadern heraus stellt sicher, dass die Bedingung zum 
definieren der zugehörigen Konstanten VORHER erfüllt wurde.


Die andere Geschichte (die über Nacht gekribbelt hat):
Wenn ich statt Lidar.h/ Drive.h aus xxx.c auf MagicIds.h verweise, was 
passiert, wenn ich Lidar.c/ .h aus dem Projekt entferne?
Bekomme ich dann einen Compilerfehler? NEIN! Ich muss daran denken, in 
MagicIds.h die entsprechende Konstante herauszunehmen.

Kann ich vielleicht dann noch über die Anweisung
#ifdef INCL_LIDAR_H
...
#endif


in MagicIds.h abfangen, dass ohne LIDAR.H auch die Konstante 
EE_PAGE_LIDARPARAMS nicht definiert wird?
Nein, ich müsste durch #include "Lidar.h" erst sicherstellen, dass 
"INCL_LIDAR_H" auch vorher definiert wurde. Dann müsste ich also 
andersherum alle Modulheader in MagicIds.h einbinden und das, so finde 
ich, ist richtig krank.

Insofern: Der "Faustregel" "Include immer dort, wo Du das Include auch 
brauchst." als "Faustregel", stimme ich zu. Hier allerdings würde ich 
eine Ausnahme machen, einfach, weils Sinn macht.

von W.S. (Gast)


Lesenswert?

Horst S. schrieb:
> Quatsch!
>
> Überlege Folgendes:
> - xxx.c includiert Lidar.h und Drive.h, benötigt aber die Konstanten aus
> MagicIDs.h
> - Lidar.h includiert MagicIds.h. Beim includieren nach o.g. Pattern wird
> EE_PAGE_LIDARPARAMS definiert.
> - Anschließend wird durch den Verweis auf Drive.h auch
> EE_PAGE_DRIVEPARAMS definiert. Trotzdem die Konstante INCL_LIDAR_H
> bereits definiert ist (durch die Referenz auf Lidar.h), wird die
> Konstante EE_PAGE_LIDARPARAMS NICHT ERNEUT definiert, weil sie schon da
> ist (der Frist-Kein-Brot-Mechanismus)

Also erstens: du reagierst unangemessen, wenn du auf all die 
Einlassungen, die von durchaus großer Erfahrung zeugen, mit "Quatsch!" 
reagierst.

Zweitens:
Du programmierst unsauber. Das sieht man bei deiner hoffnungslos 
konfusen Erklärung, die ich grad oben zitiert habe.

Mach es besser.

Das macht man, indem man die verschiedenen Funktionsteile sauber 
voneinander kapselt, so daß sie sich nicht ins Gehege kommen können. Ja, 
das geht. Und so wie ich das sehe, ist dein gesamtes Konstrukt mit EE 
PageNums und Magics nicht wirklich gut und tragfähig. Es sieht mir eher 
so aus, als ob du mit einem sehr einfachen Programm gestartet hast und 
dann im Laufe der Entwicklung immer noch hie und da einen Rucksack 
dazugeschrieben hast. So wie das Haus der Weasleys bei Harry Potter. Da 
fehlt das Gesamtkonzept.

Also:
1. Schreib in Headerdateien nur das hinein, was die übergeordneten 
Programmteile zum Benutzen des zugehörigen Moduls wirklich brauchen - 
und nicht mehr.

2. Mache keine Import-Export-Geschäfte. Also nicht sowas:
#include "lala.h"   // dort ist juhu definiert
#ifndef XYINCLUDED
#define XYINCLUDED

#define  Blabla  (juhu+1)  // aus lala.h

#endif
Wenn du von einer 'grundlegenderen' Datei (scheußliches Wort) etwas 
importierst, dann nur für den internen Gebrauch innerhalb des Moduls. 
Jeder andere Modul soll sich diese Definition gefälligst selber holen. 
Bei dir kommt aus deinem Import-Export-Geschäft (---> 
EE_PAGE_LIDARPARAMS) eben etwas extrem fehleranfälliges bei heraus.

W.S.

von Eric B. (beric)


Lesenswert?

Horst S. schrieb:
> Quatsch!
>
> Überlege Folgendes:
> - xxx.c includiert Lidar.h und Drive.h, benötigt aber die Konstanten aus
> MagicIDs.h

Erstens: warum würde xxx.c so'ne magicId brauchen? Definitiv nicht um 
auf EEPROM Daten vom Lidar oder Drive zu zugreifen; dafür sollten die 
Lidar und Drive-Module Funktionen bereitstellen.

Falls xxx.c selber eine EEPROM Page hat, soll xxx.c auch MagicIDs.h 
includieren und sich da nicht darauf verlassen, dass andere .h-Dateien 
das schon machen. (Das ist übrigens genau der Rattenschwanz an "was man 
sich reinzieht" worauf sich W.S. weiter oben bezog)

> - Lidar.h includiert MagicIds.h. Beim includieren nach o.g. Pattern wird
> EE_PAGE_LIDARPARAMS definiert.

Lidar.h soll aber MagicIDs.h nur includieren wenn er die Konstanten aus 
MagicIDs.h selber braucht. Wenn in Lidar.h nirgendwo Text 
EE_PAGE_LIDARPARAMS auftaucht, soll Lidatr.h auch nicht MagicIDs.h 
inkludieren! Faustregel!

> Die andere Geschichte (die über Nacht gekribbelt hat):
> Wenn ich statt Lidar.h/ Drive.h aus xxx.c auf MagicIds.h verweise, was
> passiert, wenn ich Lidar.c/ .h aus dem Projekt entferne?

Nix. Ausser alle eine Reihe an Fehlermeldungen vom Linker, dass Verweise 
auf Funktionen aus Lidar.c nicht "resolved" werden können :-)

> Bekomme ich dann einen Compilerfehler? NEIN! Ich muss daran denken, in
> MagicIds.h die entsprechende Konstante herauszunehmen.

Genau. Das ist nämlich Projektverwaltung und keine Compileraufgabe.
Ich würde das über das Makefile organsieren, z.B mit einer USE_LIDAR 
Define/Parameter. In MagicIDs.h würde ich dann die EE Pages in einem 
enum packen, statt einer Reihe aus #defines. Im MagicIDs.h kommt dann
1
typedef enum {
2
#if defined(USE_DRIVE)
3
    EE_PAGE_DRIVEPARAMS,
4
#endif
5
#if defined(USE_LIDAR)
6
    EE_PAGE_LIDARPARAMS,
7
#endif
8
...
9
} EE_PAGE_MAGIC_ID;

Und so werden nur die wirklich gebrauchte EE-Pages definiert und kriegen 
automagisch die "richtige" Nummer.

von Horst S. (Gast)


Lesenswert?

So, vielleicht erst einmal, "Quatsch", korrigiere ich zu "Quatsch, Du 
musst mich komplett Missverstanden haben". Mag an meiner Schreibe 
liegen, dass dieses Missverständnis überhaupt aufgetreten ist.


Ich habe weder ein Reihenfolgen- ein Scope-, noch ein 
Verständnisproblem. Präcompilerdirektiven verstehe ich als nutzbares 
Werkzeug und ich habe überhaupt keine Angst vor Fehlern, die sich 
diesbezüglich wie von Zauberhand in meinen Code einschleichen könnten.

Jungs, in welchem Graben habt Ihr gesessen, dass Ihr das für Teufelszeug 
haltet? Nee, ehrlich und wirklich ganz freundlich gefragt: Ich hatte da 
noch nie Probleme mit. Mir ist NICHT klar, was da schief gehen könnte.

Das führt bei mir auch nicht zu include-Schlangenwürsten. Es ist eine 
gezielte, dokumentierte und einmalige Ausnahme vom Faustregelkonzept. 
Diese Ausnahme ändert funktional NICHTS in Bezug auf Scope oder 
Modularität. Sie ändert nur die SICHT auf die Konstanten so, dass ich 
sie ALS LISTE LESEN UND BEARBEITEN  kann.


WO ZUM HENKER IST DAS PROBLEM? ICH SEH'S NICHT!

(Ich weiß, ich bin das Problem. Sonst ist es mein Sohn, aber heute bin 
ich es)

von Eric B. (beric)


Lesenswert?

Erstens brauchst du nicht schreien. Kleinbuchstaben lsen sich genau so 
gut, wenn nicht sogar besser.

Zweitens, wenn du eh nicht auf Ratschläge hören möchtest, warum postest 
du dann deine Fragen hier? Dann bleib in deinem Graben und löse deinen 
Sch.... selber.

von Horst S. (Gast)


Lesenswert?

Das mit dem Enum z.B.

Wir bauen eine industrielle Ampel:
typedef enum
{
  red,
  green
} Ampel_t;


wo bauen wir morgen das neue Feature "gelb" ein? Und was passiert dann, 
wenn wir bereits den Ampelwert über eine Schnittstelle geschickt haben?
Kommt das Grün da noch richtig beim Empfänger an?

Wir können auch explizit schreiben:
typedef enum
{
  red = 0,
  green = 1
} Ampel_t;


Keine Ahnung, aber kannst Du mir erklären, warum der Großteil der 
Entwickler das yellow zwischen red und green packt und bei einer 
expliziten Deklaration auch noch green bewusst auf 2 setzt oder die 
explizite Zuordnung einfach löscht?

Das ist meine persönliche und erfahrungsbasierte Sichtweise. Seitdem 
nehme ich für solche Kontrakte wieder Konstanten.

von Eric B. (beric)


Lesenswert?

Horst S. schrieb:
> Das mit dem Enum z.B.
>
> Wir bauen eine industrielle Ampel:
> typedef enum
> {
>   red,
>   green
> } Ampel_t;
>
>
> wo bauen wir morgen das neue Feature "gelb" ein?

Am besten nach dem Grün, weil dann das hier definierte Interface so 
wenig wie möglich geändert wird.

> Und was passiert dann,
> wenn wir bereits den Ampelwert über eine Schnittstelle geschickt haben?
> Kommt das Grün da noch richtig beim Empfänger an?

Klar kommt das Grün richtig an, weil sich beim hinzufügen vom Gelb das 
Interface geändert hat, und der Empfänger dafür natürlich entsprechend 
angepasst wurde.

Falls der Empfänger am andere Ende der Welt ist und das "Rot", "Grün" 
und "Gelb" über irgendeine Leitung verschickt werden soll, darf auch in 
keinem Fall einfach die enum verschickt werden, sondern soll im 
Übertragungsprotokoll spezifiziert werden wie die Werte in Bits zu 
kodieren sind.

> Keine Ahnung, aber kannst Du mir erklären, warum der Großteil der
> Entwickler das yellow zwischen red und green packt und bei einer
> expliziten Deklaration auch noch green bewusst auf 2 setzt oder die
> explizite Zuordnung einfach löscht?

Weil sie dafür nicht von einem vorgestzen Architekten auf die Finger 
geklopft werden. :-)

> Das ist meine persönliche und erfahrungsbasierte Sichtweise. Seitdem
> nehme ich für solche Kontrakte wieder Konstanten.

Das ist dein gutes Recht. Die enum habe ich aber wegen deiner Frage 
nach eine Lösung zur Behandlung nicht vorhandender Modulen eingebracht.
Wenn das Lidar Modul nicht im Projekt miteingebunden ist, wirst du so 
Compilerfehler bekommen wenn irgendein Modul trotzdem auf die Konstante 
EE_PAGE_LIDARPARAMS versucht zuzugreifen.

Mich würde es nicht stören wenn EE_LIDAR_PARAMS einfach immer definiert 
ist, egal ob ich Lidar habe oder nicht.

: Bearbeitet durch User
von Horst S. (Gast)


Angehängte Dateien:

Lesenswert?

Ich habs noch mal auseinandergefummelt und ein Testprojekt geschrieben.

Drive.h
1
#ifndef INCL_DRIVE_H
2
    #define INCL_DRIVE_H
3
    #include "MagicIDs.h"
4
5
    extern uint8_t Drive_Init();
6
#endif
Keine Ahnung, ob hier jeder diesen "zirkularen Referenzblocker", also 
die #ifndef <Headername>-Klausel verwendet. Ich baue ihn eigentlich 
immer in einen Modulheader ein. Dabei wird automatisch eine Konstante 
erzeugt, deren Name sich an den Dateinamen anlehnt.


Analog dazu Lidar.h
1
#ifndef INCL_LIDAR_H
2
  #define INCL_LIDAR_H
3
    #include "MagicIDs.h"
4
5
    extern uint8_t Lidar_Init();
6
#endif
Ist das gleiche in grün.

Merke: Im beiden Modulheadern wird auf MagicIDs verwiesen.



MagicIDs:
1
//***************************
2
//Public: MAGIC COMMAND IDS
3
//***************************
4
#ifdef INCL_LIDAR_H
5
    #define EE_CMD_READLIDARPARAMS 1
6
    #define EE_CMD_WRITELIDARPARAMS 2
7
#endif    
8
9
#ifdef INCL_DRIVE_H
10
    #define EE_CMD_READDRIVEPARAMS 3
11
    #define EE_CMD_WRITEDRIVEPARAMS 4
12
#endif    
13
14
15
//******************************
16
//Private MAGIC EEPROM PAGE IDS
17
//******************************
18
#ifdef PRIVATEACCESS
19
    #ifdef INCL_LIDAR_H
20
            #define EE_PAGE_LIDARPARAMS 1
21
    #endif    
22
23
    #ifdef INCL_DRIVE_H
24
        #define EE_PAGE_DRIVEPARAMS 2
25
    #endif
26
#endif
27
28
#undef PRIVATEACCESS
MagicIds.h beinhaltet zwei Listen, die sich im Scope unterscheiden. 
Während die EEPROM-Seiten wirklich nur im zugehörigen Modul bekannt sein 
sollen, sind die CommandIDs auch in anderen Modulen zugänglich.
Merke:
Hier tauchen die Konstanten aus den Modulheadern wieder auf.


Wenn ich jetzt aus meinem Hauptprogramm auf die öffentlichen Konstanten 
zugreifen will, muss ich die entsprechenden Header referenzieren:
PatternTest.c
1
#include <avr/io.h> 
2
3
#include "Drive.h"
4
#include "Lidar.h"
5
//#include "MagicIDs.h"
6
7
8
int main (void) 
9
{
10
    uint8_t x = 0;
11
    //Constant from lidar
12
    x = EE_CMD_READLIDARPARAMS * 2; 
13
14
    //Constant from Drive
15
    x+= EE_CMD_READDRIVEPARAMS;
16
}
Vergesse ich hier das Einbinden des entsprechenden Headers, muckt der 
Compiler bei der entsprechenden Codezeile.
Entferne ich das Modul Lidar.c nebst Lidar.h (geht auch mit Drive.c/h) 
kräht der Compiler zuerst in der include -Zeile und anschließend auch im 
Code.
Auch das (irrtümliche) direkte Einbinden von MagicIDs.h wird nicht zum 
Erfolg führen.

Der Zugriff auf eine Konstante, bei der ich den Scope nur auf das Modul 
beschränken will, z.B. in Drive.c:
1
#include <avr/io.h> 
2
3
#define PRIVATEACCESS
4
#include "Drive.h"
5
#include "Lidar.h" // optional
6
7
8
uint8_t Drive_Init()
9
{
10
    return EE_PAGE_DRIVEPARAMS;
11
    //return EE_PAGE_LIDARPARAMS;
12
}
"#define PRIVATEACCESS" vor dem include entriegelt diesen Zugriff. Alle 
Bedingungen aus MagicIDs sind erfüllt:
- PRIVATEACCESS ist definiert
- Durch "#include Drive.h" wird "INCL_DRIVE_H" definiert.
Also wird auch EE_PAGE_DRIVEPARAMS definiert.

Versuche ich jetzt auf die private Lidarkonstante zuzugreifen 
(entkommentiere ich also die Zeile "//return EE_PAGE_LIDARPARAMS;" im 
Code), schlägt der Compiler wieder Alarm. Warum?
Ich habe zwar Lidar.h referenziert und damit INCL_LIDAR_H definiert. 
Auch habe ich PRIVATEACCESS definiert. Allerdings wird PRIVATEACCESS am 
Ende von MagicIds.h über "#undef PRIVATEACCESS" zurückgesetzt.

Wenn ich von hier aus die privaten Konstanten von Lidar nutzen wollte, 
könnte ich den Zugriff über ein erneutes definieren von PRIVATEACCESS 
direkt über "include "Lidar.h"" erzwingen.

Es geht also mit beiden Scopes (und noch etwas mehr).

Ist das schön? - naja
Geht das schöner? - Das sagt ihr mir jetzt?!?

Auf jeden Fall hänge ich mal zum Rumspielen die Dateien nebst 
AVRStudio-Projekt(4.18) an.

von Eric B. (beric)


Angehängte Dateien:

Lesenswert?

Die Vorgehensweise .h-Files mit
1
#ifndef FILENAME_H
2
#define FILENAME_H
3
...
4
#endif
zu schützen ist standard und völlig ok.

Der Rest deines Post lässt mich aber klt am Rücken werden. *Schauder!*

* Nirgendwo in Drive.h oder Lidar.h wird auf den Definitionen in 
MagicIDs.h verwiesen, also brauchen die das #include da nicht. Das 
#include gehört in den .c-Dateien.

* Dieser PRIVATEACCESS ist einfach grausam und gar nicht so privat wie 
die Name es erscheinen lässt. Jede C-Datei die PRIVATEACCESS definiert 
und dann z.B. Drive.h includiert kann dann auf EE_PAGE_DRIVEPARAMS 
zugreifen.

Ich würde es eher so angehen, dass kein Modul diese EE_PAGE_BLAHPARAMS 
braucht, ausser das Modul selber. Siehe angehänges Beispiel.

Die EEPROM Pages werden in eepages.h definiert. Der Aufwand ein Modul 
hinzuzufügen oder zu entfernen ist nicht mehr als
- ein #include hinzufügen oder entfernen
- ein #define hinzufügen oder entfernen
- ggf ein weiteres #define umdefinieren.

EDIT: Beispielcode lässt sich vom Cmdline compilieren mit
1
gcc -o main drive.c lidar.c eeprom.c main.c

: Bearbeitet durch User
von Horst S. (Gast)


Lesenswert?

Eric B. schrieb:
> Jede C-Datei die PRIVATEACCESS definiert
> und dann z.B. Drive.h includiert kann dann auf EE_PAGE_DRIVEPARAMS
> zugreifen.

Wenn ich in Lidar.c ...
#define PRIVATEACCESS
#include "Lidar.h"
#include "Drive.h"

oder ...
#include "Drive.h"
#define PRIVATEACCESS
#include "Lidar.h"

oder im Header Lidar.h...
#ifndef INCL_LIDAR_H
  #define INCL_LIDAR_H
    #include "MagicIDs.h"
    #include "Drive.h"

    extern uint8_t Lidar_Init();
    ...
#endif

...schreibe, wird der Zugriff geblockt. Gibt's sonst noch Möglichkeiten, 
die ich nicht gesehen/berücksichtigt habe?

Wenn da noch was fehlt oder eine Lücke ist, sag's, dann ist's ein Fehler 
im Pattern. Das sollte nicht sein, dann ist das Pattern falsch.

Wenn Du allerdings sagst, man kann das PRIVATEACCESS auch woanders 
hinsetzen, da hast Du zweifelsfrei recht. Dann ist's aber ein Fehler in 
der "Anwendung des Patterns". Das ist ein Unterschied!



Ich versuch's mal auf den Punkt zu bringen. Was kann passieren?
- dass ich das PRIVATEACCESS oder ein include gegen Regeln des Patterns 
an die falsche Stelle setze.
- dass ich beim Kopieren von Codezeilen vergesse, einen Konstantennamen 
anzupassen (z.B. bei Dir in
"eeprom_read(EE_PAGE_LIDARPARAMS, (char *)param_ptr, 
sizeof(lidar_param_t));)
).

Bei Deiner Lösung (Scope auf alle Konstanten in der EEPAGES.h offen für 
alle Module) muss ich allerdings nur einen Fehler machen, bei meiner 
Lösung zwei.

Ich sage nicht, dass das ideal ist, nur fällt mir nix Besseres ein.

von Eric B. (beric)


Lesenswert?

Horst S. schrieb:
> - dass ich beim Kopieren von Codezeilen vergesse, einen Konstantennamen
> anzupassen (z.B. bei Dir in
> "eeprom_read(EE_PAGE_LIDARPARAMS, (char *)param_ptr,
> sizeof(lidar_param_t));)
> ).

Das wäre dann aber auch "ein Fehler in der Anwendung des Patterns". ;-)

Grundsätzlich soll gelten: wenn eine Konstante in nur einer Datei 
benutzt werden darf, soll sie auch da und nirgendwo anders definiert 
werden.

Wenn aber mehrere solche Konstanten voneinander abhängig sind, macht es 
Sinn sie in einer Datei zusammen zu fügen und dann diese Datei zu 
includieren (MagicIDs.h / eepages.h)

Sich dann aber einen Krampf machen um zu probieren die einzelne 
Konstanten doch wieder in Scope zu beschränken, ist m.E. sinnlos und 
fehleranfällig. Da hilft nur ein bisschen Diziplin.

: Bearbeitet durch User
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.