Hallo, gibt es ein Best Practice in C für konstante Variablen bei Microcontroller? Als simples Beispiel definieren welcher Sensor an welchen ADC Eingang hängt. An Möglichkeiten die mir so eingefallen sind: * Eine Variable in file1.c zu definieren und initialisieren und in file2.c mit 'extern' deklarieren, * mit 'static const' in der Header-Datei initialisieren, * oder in der Header-Datei deklarieren und in der zugehörigen .c-Datei dann initialisieren mit einer init()-Funktion (natürlich hier nicht const). Ich habe alle drei Möglichkeiten schon in verschiedenen Projekten verwendet. Jetzt wollte ich einfach mal fragen, ob es dort ein "Standard" etabliert hat. Zur kurzen Einordnung und meine bisherige Präferenz ist, dass ich immer .c/.h-Dateipaare verwende und, wenn es möglich ist, die zweite oder dritte Möglichkeit benutze. Vielen Dank schon mal für die Antworten. Gruß Vincent
:
Verschoben durch Moderator
Sorry, keine Möglichkeit ist irgendwie üblich oder gut. Extern declaration in .h nur dann, wenn in >= 2 c Files verwendet, sonst static in dem einen. Und es gibt für const Variablen in C viel weniger Gründe als in C++.
Vincent W. schrieb: > gibt es ein Best Practice in C für konstante Variablen bei > Microcontroller? "konstante Variable" ist eigentlich ein Widerspruch in sich. ;-) (Allerdings ist das C "const" tatsächlich leider genau das.) > An Möglichkeiten die mir so eingefallen sind: > * Eine Variable in file1.c zu definieren und initialisieren und in > file2.c mit 'extern' deklarieren, Schlechte Idee. Außer mit link-time-Optimization führt das zu unnützem Speicher- und Rechenzeitverbrauch, da der Compiler nicht mehr in allen Übersetzungseinheiten erkennen kann, dass die Variable tatsächlich ihren Wert nie ändert. > * mit 'static const' in der Header-Datei initialisieren, Zumindest dahingehend besser. Für C gerade im Controller-Umfeld wären auch klassische Präprozessormakros hier praktikabel, denn auch dort kann der Compiler bereits entscheiden, dass bestimmte Code-Teile konstant sind. > * oder in der Header-Datei deklarieren und in der zugehörigen .c-Datei > dann initialisieren mit einer init()-Funktion (natürlich hier nicht > const). Noch schlechter als die erste Variante.
Jörg W. schrieb: > Schlechte Idee. Außer mit link-time-Optimization führt das zu unnützem > Speicher- und Rechenzeitverbrauch, da der Compiler nicht mehr in allen > Übersetzungseinheiten erkennen kann, dass die Variable tatsächlich ihren > Wert nie ändert. Sobald es komplett kompiliert ist, müsste es vermutlich nicht so große Unterschiede ausmachen? 'extern' verwende ich hauptsächlich für CubeMx-Variablen, die einem nicht die Wahl lassen. > Für C gerade im Controller-Umfeld wären auch klassische > Präprozessormakros hier praktikabel, denn auch dort kann der Compiler > bereits entscheiden, dass bestimmte Code-Teile konstant sind. Die verwende ich auch, allerdings ist das bei komplexeren Dingen, wie structs nicht mehr möglich. > Noch schlechter als die erste Variante. Ist das nach der Setup-Phase so problematisch? Da Konstanten in der Setup-Phase erstellt werden, ist es mir nicht so wichtig, wie lang der STM32 zum "hochfahren" braucht. Relevanter ist es im Loop und in den Interrupts. Ich habe fast noch nie einen verwendeten STM32 bis zu 100% ausgereizt. Ich versuche immer drauf zu achten, dass der Loop und die Interupts effizent laufen.
Vincent W. schrieb: > Sobald es komplett kompiliert ist, müsste es vermutlich nicht so große > Unterschiede ausmachen? Ohne LTO sieht der Compiler immer nur das jeweilige Source-File. Er muss also davon ausgehen, dass die externe Variable auch wirklich variabel ist, und Du willst bestimmt nicht, dass bei I/O-Zugriffen etwas wie 1<<somebit tatsächlich zu unnötigen Bitschiebereien führt.
Vincent W. schrieb: > Da Konstanten in der Setup-Phase erstellt werden, ist es mir nicht so > wichtig, wie lang der STM32 zum "hochfahren" braucht. Du müsstest ein typisches Beispiel / Anwendungsfall beschreiben. Und wenn der gleiche Speicherbereich für einige const ist, für andere variabel, gibt es u.U plattformspezifische Einschränkungen. Am einfachsten ist das mit einer static Variablen/Struktur und dann nach extern nur ein const XYtype * const ptrXY.
Vincent W. schrieb: > gibt es ein Best Practice in C für konstante Variablen bei > Microcontroller? Als simples Beispiel definieren welcher Sensor an > welchen ADC Eingang hängt. The "best practice" in C ist der Preprozessor. Funktioniert seit 50 Jahren ohne Probleme. Funktioniert auch mit Structs, Strings, und "inline" Funktionen. Die Defines kommen in "config.h", das die C Files inkludieren.
Udo K. schrieb: > The "best practice" in C ist der Preprozessor. Nein, der ist eher "best compromise" als "best practice". Wenn es eine Lösung ohne Präprozessor gibt, die das gleiche Ergebnis erzielt, würde ich sie jederzeit bevorzugen.
Unter Best Practice versteht man nicht die denkbar beste Lösung, sonder die gängige Praxis, die funktioniert. Unter C ist der Präprozessor ein wichtiger Sprachbestandteil, ohne den es nicht geht. Der ist superflexibel, und dabei einfach in der Benutzung. Kein Vergleich zu den Templates in C++, die in ihrer Komplexität einfach nur krank sind. Aber wer meint, der Präprozessor ist zu kompliziert, oder altbacken, der kann "static int i = 100" ins Header File schreiben. Oder die Weicheier schreiben "static const int i = 100". Dem Compiler ist es wurscht.
Vincent W. schrieb: > gibt es ein Best Practice in C für konstante Variablen bei > Microcontroller? Konstanten definiere ich in C im #define, vorzugsweise in einer separaten Datei, die ich gerne hardware.h oder config.h nenne. Das sind keine Variablen, und zwar mit Absicht, damit sie kein RAM belegen. Beim arm-gcc sorgt das Schlüsselwort const schon dafür, dass kein RAM belegt wird. Aber beim avr-gcc geht das nicht so einfach, deswegen #define. Was nicht konstante Variablen angeht: Diese vermeide ich wo immer es geht, da man sich danach durch copy/paste Aktionen sehr schnell Konflikte/Fehler einhandelt die erst später auffallen. In meinen Projekten findest du globale Variablen fast ausschließlich in der main.c. Ab und zu nutze ich statische globale Variablen in Bibliotheken und eine dazu passende öffentlich sichtbare getter-Funktion, zum Beispiel um den Status einer Resource bekannt zu geben (z.B. ob eine SD Karte gerade bereit oder belegt ist). Udo K. schrieb: > The "best practice" in C ist der Preprozessor. C ist ein bisschen angestaubt und der Preprozessor noch mehr. Ich habe 15 Jahre PC's in C programmiert ohne zu wissen, was ein Preprozessor ist. Ich kannte nur #define, #ifdef und #include. Durch dieses Forum hier wurde ich darauf aufmerksam, dass man sehr viel mehr mit dem Preprozessor machen kann und dies auch in übermäßigem Eifer in einem meiner komplexesten Projekte intensiv genutzt - und wieder bereut. Ich kann nur davon abraten, Makros tief zu verschachteln, weil man ihre Wirkung dann nur noch mühsam nachvollziehen kann. Hier wurde mal ein Konstrukt für AVR veröffentlicht, dass Aufrufe wie "SET(PD0,1)" ermöglichte. So elegant das nach außen hin aussah, so schrecklich komplex war das unter der Haube. Wenn komplex verschachtelte Makros mal eine Syntax-Fehler im Compiler auslösen, bekommt man oft sehr unverständliche Fehlermeldungen. Und bitte immer daran denken, dass der GNU C Compiler inzwischen ziemlich gut optimieren kann. Einfache C-Funktionen wie:
1 | void set_led(uint8_t value) |
2 | {
|
3 | if (value) |
4 | PORTD |= (1<<4); |
5 | else
|
6 | PORTD &= ~(1<<4); |
7 | }
|
werden in den allermeisten Fällen auf den simplen Port-Zugriff reduziert, so dass im Maschinencode weder ein Funktionsaufruf noch die if()-Abfrage stattfindet. Deswegen ist einiges an traditioneller Marko-Magie, was früher mal der manuellen Optimierung diente, schlicht überflüssig geworden.
Unter Berücksichtigung, dass im Header-File nur eine Bekanntmachung stattfinden sollte, würde ich es so machen: *.c: const (Definition und Initialisierung) *.h: extern const Im Header-File mit "static const" initialisieren ist imho Murks ;). Datenstrukturen sollten schon in dem Modul definiert und initialisiert werden, dem sie logisch/funktional zugeordnet werden können. Und dann eben im Header-File dieses Moduls lediglich der Öffentlichkeit bekannt gemacht werden. Den Präprozessor würde ich, wenn möglich, vermeiden z.B. wegen fehlender Typ-Überprüfung.
:
Bearbeitet durch User
P. S. schrieb: > Unter Berücksichtigung, dass im Header-File nur eine Bekanntmachung > stattfinden sollte, würde ich es so machen: > > *.c: const (Definition und Initialisierung) > *.h: extern const > > Im Header-File mit "static const" initialisieren ist imho Murks ;). Das gibt dem Compiler allerdings die Möglichkeit, die Variable rauszuoptimieren, weil der Compiler dann bei jeder Benutzung den Wert kennt. Bei deinem Vorschlag muss da immer ein extra Speicherzugriff durchgeführt und dieser gelesene Wert dann verwendet werden. Der oben genannte shift (1<<pin_number) wäre so ein Beispiel. Auf einem AVR muss der zu schiebende Wert aus dem Speicher gelesen und dann mangels Schiebe-Instruktion in einer Schleife bitweise mit sich selbst addiert werden, statt dass der Shift vollständig zur Compilezeit berechnet und nur noch das Ergebnis eingetragen wird. > Den Präprozessor würde ich, wenn möglich, vermeiden z.B. wegen fehlender > Typ-Überprüfung. Allerdings gibt es wiederum auch Stellen, wo es gar nicht anders geht, weil eine echte Compilezeit-Konstante benötigt wird, und eine const-Variable ist auf C-Ebene keine solche.
Udo K. schrieb: > Unter C ist der Präprozessor ein wichtiger Sprachbestandteil, > ohne den es nicht geht. Sicher, schon allein wegen der #includes. Allerdings ist der Präprozessor eben auch für manch leidige Überraschung zuständig. Unsere Compiler sind mittlerweile viel leistungsfähiger als vor 50 Jahren, sie erkennen viel mehr Optimierungsfälle. Daher kann man vieles heutzutage ohne Präprozessor schreiben, was zu Zeiten von Brian Kernighan und Dennis Ritchie so nicht denkbar war.
Jörg W. schrieb: > Daher kann man > vieles heutzutage ohne Präprozessor schreiben, was zu Zeiten von Brian > Kernighan und Dennis Ritchie so nicht denkbar war. Ganz richtig. Nur muss man für die meisten Ersatzkonstrukte bei jeder Plattform in jeder Version schauen, ob denn der Ballast wirklich weggelassen wird. Also, dass z.B.
1 | static const struct sMyCfg MyCfg={1,2,3,4,5...} |
nicht zu einer Instanz je c-File führt und nicht zu Flash-Read-Sonderfunktionen zur Laufzeit, wenn ein Element gebraucht wird. Während der Präprozessor relativ verlässlich ist, sind es Optimierungen nicht. Und in den seltensten Fällen geht (in C):
1 | int myXYArray[MyCfg.nXY] |
Rolf M. schrieb: > Das gibt dem Compiler allerdings die Möglichkeit, die Variable > rauszuoptimieren, weil der Compiler dann bei jeder Benutzung den Wert > kennt. Bei deinem Vorschlag muss da immer ein extra Speicherzugriff > durchgeführt und dieser gelesene Wert dann verwendet werden. Die oberste Prämisse sollte aber Wartbarkeit sein. Code zu warten, bei dem der Autor offensichtlich viel Freude daran hatte jede unnötige Instruction rauszuoptimieren, ist meist ein Alptraum. Und wenn man sich das Disassembly anschaut, waren die meisten "Optimierungen" eh erfolglos oder - unter Berücksichtigung der zur Verfügung stehenden Ressourcen - völlig unnötig. Heutige Compiler und Architekturen sind gut genug, sodass man da i. d. R. nicht selber Hand anlegen braucht.
Rolf M. schrieb: > Das gibt dem Compiler allerdings die Möglichkeit, die Variable > rauszuoptimieren, weil der Compiler dann bei jeder Benutzung den Wert > kennt Ein C++ Compiler darf und kann das auch so. Wenn man sowas will, dann einfach C++ nehmen, egal ob mit oder ohne OOP, Templates oder was auch immer.
A. S. schrieb: > Rolf M. schrieb: >> Das gibt dem Compiler allerdings die Möglichkeit, die Variable >> rauszuoptimieren, weil der Compiler dann bei jeder Benutzung den Wert >> kennt > > Ein C++ Compiler darf und kann das auch so. Es geht aber um C. Wobei auch ein C++-Compiler zumindest mal LTO braucht, um das hinzubekommen. Denn auch der kann einen Wert, den er nicht kennt, nicht direkt einfügen. :)
P. S. schrieb: > Die oberste Prämisse sollte aber Wartbarkeit sein. Code zu warten, bei https://www.mikrocontroller.net/articles/AVR-GCC-Codeoptimierung#Prinzipien_der_Optimierung
Rolf M. schrieb: > A. S. schrieb: >> Rolf M. schrieb: >>> Das gibt dem Compiler allerdings die Möglichkeit, die Variable >>> rauszuoptimieren, weil der Compiler dann bei jeder Benutzung den Wert >>> kennt >> >> Ein C++ Compiler darf und kann das auch so. > > Es geht aber um C. Wobei auch ein C++-Compiler zumindest mal LTO > braucht, um das hinzubekommen. Denn auch der kann einen Wert, den er > nicht kennt, nicht direkt einfügen. :) Das ist richtig. Du wirst aber zugegben, dass Dein Konzept hier ein gefährlicher Ritt auf der der Rasierklinge ist: Eine Struktur X-fach lokal zu instanziieren erlaubt es dem Compiler, dies auch X-fach zu tun. Dann hast Du auf einmal 1000 statt 1 Instanz, mit 1000 statt einer Adresse (wenn man Pointer darauf bildet und die verwendet). Und das nur, damit der Compiler prinzipiell die Möglichkeit hat, den Wert direkt zu benutzten und den Rest gar nicht erst anzulegen.
P. S. schrieb: > Rolf M. schrieb: >> Das gibt dem Compiler allerdings die Möglichkeit, die Variable >> rauszuoptimieren, weil der Compiler dann bei jeder Benutzung den Wert >> kennt. Bei deinem Vorschlag muss da immer ein extra Speicherzugriff >> durchgeführt und dieser gelesene Wert dann verwendet werden. > > Die oberste Prämisse sollte aber Wartbarkeit sein. Code zu warten, bei > dem der Autor offensichtlich viel Freude daran hatte jede unnötige > Instruction rauszuoptimieren, ist meist ein Alptraum. Das kommt drauf an. Super wartbarer Code, der aber zu langsam ist und damit die Echtzeit-Vorgaben verletzt oder der zu groß ist und daher nicht in den Speicher passt, bringt einem auch nichts. Abgesehen davon finde ich sowas wie ein
1 | #define LED_PORT PORTC
|
nicht wirklich unwartbar - auch nicht, wenn es in einem Header steht. A. S. schrieb: > Das ist richtig. Du wirst aber zugegben, dass Dein Konzept hier ein > gefährlicher Ritt auf der der Rasierklinge ist: Eine Struktur X-fach > lokal zu instanziieren erlaubt es dem Compiler, dies auch X-fach zu tun. > Dann hast Du auf einmal 1000 statt 1 Instanz, mit 1000 statt einer > Adresse (wenn man Pointer darauf bildet und die verwendet). Naja, wenn du 1000 separate Source-Files hast, die alle auf die selbe globale Variable (bzw. Konstante) zugreifen und die das alle jeweils unbedingt über einen Zeiger tun müssen, hast du ganz andere Probleme.
:
Bearbeitet durch User
Rolf M. schrieb: > und die das alle jeweils unbedingt über einen Zeiger tun müssen, hast du > ganz andere Probleme. Das ist richtig. Aber zum einen ging es dem TO um eine Art config, und nicht um einzelne skalare, zum anderen fällt man halt bei einem Element nach Jahren drauf rein. Rolf M. schrieb: > Super wartbarer Code, der aber zu langsam ist und damit die > Echtzeit-Vorgaben verletzt oder der zu groß ist und daher nicht in den > Speicher passt, bringt einem auch nichts. Meist ist wartbarer Code auch schneller, lesbarer und zuverlässiger. Dein define-beispiel ist doch meist ein gutes Beispiel.
Kann man die globale nicht als Rückgabewert einer Funktion definieren. Entweder als Wert oder als Pointer?
Toby P. schrieb: > Kann man die globale nicht als Rückgabewert einer Funktion definieren. > Entweder als Wert oder als Pointer? Kann man, habe ich doch oben geschrieben.
Vincent W. schrieb: > gibt es ein Best Practice in C für konstante Variablen bei > Microcontroller? Als simples Beispiel definieren welcher Sensor an > welchen ADC Eingang hängt. Das sind zwei völlig verschiedene Dinge. 1. konstante Variablen: die fügt man am ehesten als const ...= wert; ein und verweist per .h auf diese typisierte Konstante.. WENN: es wirklich eine sein muß. Wenn nicht, dann nimm eher ein #define xxx in der .h Datei. 2. Welcher Sensor an welchem ADC-Eingang hängt, sollte eben nicht über irgend welche 'konstanten Variablen', sondern über einen passenden Low-Level-Treiber erledigt werden, denn der Wert des Sensors ist ja nicht wertfrei, sondern hat irgend eine Bedeutung, eine Kalibrierung, ergibt irgend einen Systemzustand der behandelt werden muß oder nicht usw. Das wiederum ist Sache des Treibers. W.S.
W.S. schrieb: > 2. Welcher Sensor an welchem ADC-Eingang hängt, sollte eben nicht über > irgend welche 'konstanten Variablen', sondern über einen passenden > Low-Level-Treiber erledigt werden, denn der Wert des Sensors ist ja > nicht wertfrei, sondern hat irgend eine Bedeutung, eine Kalibrierung, > ergibt irgend einen Systemzustand der behandelt werden muß oder nicht > usw. Das wiederum ist Sache des Treibers. Ob du das C-File, das den Wert einliest, jetzt "Treiber" nennst oder nicht, ändert an der Frage erst mal nichts. Auch bei einem Treiber möchte man ggf. konfigurieren können, an welchem Port der Sensor hängt.
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.