Folgendes Problem:
Ich habe eine Sammlung Quellmodule, die per #define gesteuert Funktionen ein- oder ausschließen.
Namen und Adressen dieser Funktionen sollen in eine Namenstabelle eingetragen werden, wenn die jeweilige Funktion im Objektcode enthalten ist. Die Tabelle soll im Flasspeicher angelegt werden. Das soll automatisch zur Generierungszeit passieren.
Die Tabelle wird zur Laufzeit von einer Routine durchsucht, die die Funktionsadressen in Namensstrings umsetzt. Es muss also auch die variable Länge der Tabelle zur Laufzeit feststellbar sein.
Alternativ: überall da, wo via #define irgendwas hinzu kommt, kommt auch gleich der Tabelleneintrag mit dazu (bspw. in Form einer eigenen memory section).
Das spart es, den ganzen Salat via Postprocessing nachzuverarbeiten, was ja am Ende auf zweimaliges Linken hinaus läuft (einmal, um zu wissen, was alles "an Bord" ist, das zweite Mal, um die mit diesem Wissen gebaute Tabelle dazu zu linken).
Alternativ: überall da, wo via #define irgendwas hinzu kommt, kommt auch
gleich der Tabelleneintrag mit dazu (bspw. in Form einer eigenen memory
section).
Genau sowas schwebt mir vor. Wie fange ich das an, ohne zusätzlich ein Linkerscript bauen zu müssen??
Ich würde das über #defines machen, und mir den ganzen Tabellen-Zirkus sparen.
Der Linker macht das ja für dich.
Wenn du tatsächlich unterschiedliche Memory-Regions brauchst -was bei den 8bit AVR eher die Ausnahme ist- kannst du ja einfach alle denkbaren Memory-Regions im Linkerscript eintragen, und via #define kontrollieren, welche tatsächlich genutzt werden.
Ungenutzte Zuweisungen im Linkerscript, tun ja nicht weh.
Ist zwar kein AVR, aber in diesem Projekt kannst du sehen, wie man in einer config.h mit nem Haufen #define kontrolliert, was da gerade gebaut wird.:
Ich würde das über #defines machen, und mir den ganzen Tabellen-Zirkus
sparen.
Der Linker macht das ja für dich.
Mir scheint, ich habe mich nicht klar genug ausgedrückt…
Ich habe einen Scheduler als Quelltextbibliothek.
Für die Task-Tabelle des Schedulers gibt es eine Dumpfunktion, die im Moment eine Adresse-nach-Namensumsetzungsroutine in der Anwendung aufruft. Dafür muss eine Tabelle gepflegt werden, die den Task-Adressen den Klarnamen zuordnet. Die Pflege der Tabelle will ich umgehen, indem ich einfach in jeder Task-Routine einen Macro aufrufe, der den zugehörigen Umsaetzungstabelleneintrag generiert.
Die Tabelleneinträge müssen hintereinander in einer Flash-Section landen – das ist Linkers Job.
Damit die Suchroutine weiß, wo die Tabelle endet, muss hinter dieser Section eine weitere leere liegen, die die Adresse hinter der Tabelle liefert.
Ich habe etwas herum experimentiert: der Compiler nimmt Deklarationen wie diese an:
1
#define N(name) { name, FSTR(#name) }
2
staticconstName__flash1NameTable[]={
3
N(powerBankRefreshed),
4
N(powerBankRefresher),
5
};
6
#undef N
wobei __flash1 der der allgemeinen Form __flashN entspricht.
Wenn ich dahinter einen leeren Array in __flash2 anlege, könnte das funktionieren, wenn die Sections hintereinander liegen.
Nur leider finde ich im .map-File dazu überhaupt nichts, weder Sectionnamen, noch Variablennamen. (Dieser .map-File scheint mir überhaupt nicht zu allzuviel nutze zu sein…)
wieso eigenes Linker-Script? Du brauchst doch nur eine weitere Memory section für Deine Tabelle. Und der Linker liefert Dir nachher Adresse und Länge der Section. Also nur ein paar Zeilen im Script.
Wieso brauchst Du überhaupt getrennte Quellen? Wenn Du in ein Sammelfile alle Header einbinden kannst, kann das der Precompiler für Dich erledigen.
Wenn es nur eine Handvoll Teilprojekte sind, kannst du auch eine Liste von (Teilprojekt-)Tabellen anlegen.
Wenn es Dir nur um das Listen-/Tabellenende geht, mache sie "Nullterminiert" (natürlich nicht wenn der Linker die anordnet).
wieso eigenes Linker-Script? Du brauchst doch nur eine weitere Memory
section für Deine Tabelle. Und der Linker liefert Dir nachher Adresse
und Länge der Section. Also nur ein paar Zeilen im Script.
In welchem Skript?
Wieso brauchst Du überhaupt getrennte Quellen? Wenn Du in ein Sammelfile
alle Header einbinden kannst, kann das der Precompiler für Dich
erledigen.
Ganz simpel: ich will den Scheduler einfach in ein Projekt aufnehmen, also scheduler.c und scheduler.h
In der Anwendung sind dann die Task-Funktionen verstreut. Wenn jede Taskfunktion es schafft, den zugehörigen Umsetzungstabellen-Eintrag in eine spezielle Section zu bugsieren und der Name des Section-Anfangs und -Endes dem Scheduler bekannt ist, dann muss man sich um die Tabelle nicht mehr weiter kümmern.
Wenn es nur eine Handvoll Teilprojekte sind, kannst du auch eine Liste
von (Teilprojekt-)Tabellen anlegen.
Nein, solches Zeug will ich tunlichst vermeiden – das wird schnell furchtbar unübersichtlich und ist bald unpflegbar…
Nur leider finde ich im .map-File dazu überhaupt nichts, weder
Sectionnamen, noch Variablennamen. (Dieser .map-File scheint mir
überhaupt nicht zu allzuviel nutze zu sein…)
Das Map-File ist nützlich zum Debuggen, wenn man Probleme mit dem Linker hat. Das ist ganz sicher nicht dafür da, irgendwie maschinenlesbar zu sein.
Die Symboltabelle wäre vielleicht interessanter für dich.
Nein, solches Zeug will ich tunlichst vermeiden – das wird schnell
furchtbar unübersichtlich und ist bald unpflegbar…
Ich hab nen Kollegen, der ist Fan von überall im Code verteilten sscanf() zum Parsen. Mit dem Erfolg, daß gerne mal Schreibfehler passieren oder sogar Namen doppelt vergeben werden bzw. fehlen. Für einen anderen Leser ist es sehr unübersichtlich, wenn die Strings und Aufrufe überall wahllos verteilt sind. Außerdem erzeugt jedes weitere sscanf() auch massig Codeoverhead.
In einer Tabelle kann man viel besser auf einheitliche Syntaxregeln achten und Fehler erkennen.
Außerdem erzeugt jedes weitere
sscanf() auch massig Codeoverhead.
Wieso das? sscanf wird, wie jede Library-Funktion, einmal zum Code dazugelinkt. Ob man es an drei, zehn oder fünfzig Stellen im Code verwendet, ändert an dessen Größe nichts. Ist doch keine inline-Funktion ...
Ob man es an drei, zehn oder fünfzig Stellen im Code
verwendet, ändert an dessen Größe nichts.
sscanf() Aufrufe bekommen oft sehr viele Parameter übergeben, die auf dem Stack abgelegt werden müssen. Man kann sich mal im Listing anschauen, was für ein Bohei vor jedem Aufruf veranstaltet wird.
Mit dem Erfolg, daß gerne mal Schreibfehler
passieren oder sogar Namen doppelt vergeben werden bzw. fehlen.
Im Prinzip hast du Recht, aber hier liegt ein spezieller Fall vor, für den das eben gerade nicht gilt, während es für die global zu pflegende Tabelle zutrifft.
In schedule.h gibt es bereits einen Macro SCHEDULER_TASK(name), der eigentlich nur syntaktischer Zucker ist und die Lesbarkeit des Codes verbessert:
1
#define SCHEDULER_TASK(name) void name()
Wenn es gelingt, diesen Macro so umzubauen, dass er gleich den entsprechenden Namenstabelleneintrag generiert, dann braucht man keine globale Tabelle mehr, der Code bleibt lesbar und Namensänderungen schlagen sofort auf die Tabelle durch.
Zusätzlich kann man auch zwei Versionen davon bauen, eine mit eingebautem Tabellen-Dump und einen ohne und die ganze Chose über ein in der Kommandozeile des Compilers angebbares Symbol steuern.
Dann wäre das alles völlig automatisiert und trotzdem transparent.
Die Tabelleneintragsgenerierung kann man mit dem Trick mit der verzeigerten Liste aus https://www.mikrocontroller.net/articles/C_Linkerhacks_%2B_Modularisierung dezentralisieren. (Das mit den Sections funktioniert beim AVR nach dem Artikel nicht, weil die Reihenfolge der Sections nicht definiert ist.)
Wenn es gelingt, diesen Macro so umzubauen, dass er gleich den
entsprechenden Namenstabelleneintrag generiert
Das Problem an C-Macros ist, daß es nur eine Textersetzung durchführt.
Ein Macro kann also nicht weitere Macros erzeugen und es sind auch keine nested Macros möglich.
Sowas kann nur der AVR Assembler.
Das mit den Sections funktioniert beim AVR nach dem Artikel nicht, weil
die Reihenfolge der Sections nicht definiert ist
Das eigentliche Problem beim AVR ist das linker script. Die custom sections kommen nicht in .text oder .data rein, die Objekte landen dann nicht auf dem AVR.
Und wenn man seine Sections z.B. .data.bla nennt, dann sollte es zwar in .data rein kommen, aber dann generiert der Linker die _start und _stop symbole nicht mehr, die gibts nur wenn der Sektionsname ein gültiger c identifier wäre, und "." kann man da halt nicht haben. Eine echt blöde Situation...
Ein Problem könnte es geben, wenn in zwei Quellmodulewn der Macro zufällig auf gleichen Zeilennummern aufgerufen wird. Das könnte man aber leicht beheben, z.B. mit einer für jeden Quellfile individuellen Modul-ID – das habe ich in meinem Projekt auch bereits eingebaut, um fatale Fehler mit einer LED blinkend zu kommunizieren.
Das Problem an C-Macros ist, daß es nur eine Textersetzung durchführt.
Das wird in dem zitierten Artikel mit dem alten CPP CONCAT-Trick umgangen. Womöglich kannst du da noch was lernen ;-)
Neue Macros in einem Macro erzeugen geht nicht, aber das braucht man hier auch nicht. Für solche Schweinereien muss dann der olle m4 her… Dessen Syntax ist allerdings gewöhnungsbedürftig.
Normalerweise sollte das man-Kommando natürlich auf die englische Version zurück fallen, wenn keine in der Regionalsprache installiert ist. Da scheint also generell was bei dem System kaputt zu sein.
Für das avr-*-Zeug hilft das nichts.
Das wiederum dürfte an demjenigen liegen, der die avr-* Pakete gebaut hat. Aber von avr-libc abgesehen (die via "avr-man" bei Bedarf einen Sack voller eigener man pages installieren kann), sind die man pages von GCC und binutils natürlich eh die gleichen wie die ohne "avr-".
sscanf() Aufrufe bekommen oft sehr viele Parameter übergeben, die auf
dem Stack abgelegt werden müssen. Man kann sich mal im Listing
anschauen, was für ein Bohei vor jedem Aufruf veranstaltet wird.
Mit ziemlicher Sicherheit gibts noch viel mehr Bohei, wenn man das, was ein sscanf-Aufruf macht, zu Fuß in einzelne Funktionsaufrufe auflöst.
Aber wegen einem einzigen sscanf den ganzen Modul zu laden, sollte man sich im Zweifelsfall schon gut überlegen.
Das sieht schon mal ganz gut aus… nur finde ich die Funktion initReadSensors_Schd_NameTableEntry_ctor() weder in der Symboltabelle, noch im .map-File. Irgend was scheint da mit der Behandlung von Konstruktoren nicht zu stimmen.
Ursache für die fehlende initReadSensors_Schd_NameTableEntry_ctor() ist Linker-Optimierung. Wenn ich die Option -flto entferne, sind sie im Objekt enthalten.
Gibt es eine Möglichkeit, dem Linker mitzuteilen, dass er eine Funktion von der Optimierung ausnehmen soll?