Moin Forum,
ich stehe gerade aufm Schlauch und brauch mal Hilfe.
Ich möchte ein Array aufbauen, in dem n structs enthalten sind.
1
typedefstructtstConfigParam{
2
uint8_tRegister;
3
uint8_tLength;
4
constchar*Content;
5
}tstConfigParam;
6
7
tstConfigParamMyParamList[]={
8
/*0*/{0x23,4,"abcd"},
9
/*1*/{0x45,6,"efghij"},
10
/*2*/{0x67,2,{0x55,0x66}}/* <<<--- Fehler */
11
};
Bei der Initialisierung der Werte würde ich aber gerne den String aus Konstanten aufbauen lassen. Also der Compiler soll die 0x55 und 0x66 in den Flash legen und den Zeiger darauf hier in die Liste eintragen, so wie es mit den Zeichenfolgen "abcd" auch funktioniert.
Ja, ich könnte auch
1
"\x55\x66"
schreiben, aber das geht leider nicht wenn ich die 0x55 aus einem
1
2
#define Value1 0x55
3
#define Value2 0x66
entnehmen will.
Ich würde aber auch gerne vermeiden noch extra Variablen auzulegen.
Gibts da eine Möglichkeit?
Ja, das geht natürlich, aber ich wollte ja gerade vermeiden, noch extra Variablen anzulegen. Ziel ist, das in diesem Array in einigermaßen übersichtlicher Art eine Liste von Konfigurationsparametern als Tabelle zusammengestellt werden. Für eine Handvoll Parameter mag das gehen, aber dann wirds schnell unübersichtlich.
Und bei den Strings "abcd" gehts ja auch. Die müssen auch nicht extra irgendwo angelegt werden.
Früher oder später wird das Programm abstürzen, weil der Content Pointer an eine Funktion übergeben wird, die auf einen String-Terminator angewiesen ist. Das sind die typischen Fehler die erst in einem halben Jahr auftreten.
Also wenn schon denn schon, das eine Byte für den Terminator investieren :
Das Compound Literal befindet sich dort im Scope der Funktion. Das ist ein bisschen wie eine anonyme lokale Variable.
Ab C23 kann man auch im Compound Literal einen Storage Class Specifier angeben, dann geht das:
Ich würde aber auch gerne vermeiden noch extra Variablen auzulegen.
Gibts da eine Möglichkeit?
Was generell schon mal ungünstig ist.
Zeiger bringen eine gewisse Flexibilität, aber man sollte sich damit auskennen.
Für die Stack-Navigation z.B. braucht man immer auch eine Schrittbreite. Ohne geht einfach nicht.
(Verschachtelte Schleifen können auch hilfreich sein.)
Wobei sich da schon die nach dem Warum für diese Forderung stellt.
Was ich meine: Für Dezimal oder Octal geht das define ja mit "" am Anfang. Aber wenn jemand 0x55 schreiben will und dafür ein anderes #define braucht, dann ist meine Lösung immer murx.
Was ich meine: wenn er „Uf“ haben will, das schon zur Compilezeit weiß,
warum definiert er dann nicht „U“ und „f“.
Vielleicht ist die Anwendung die Ausgabe von Sonderzeichen, die nicht im Quelltextzeichenvorrat enthalten sind oder in diesem eine andere Codierung haben, als für die Anwendung gebraucht wird.
Ein Beispiel wären Sonderzeichen auf den üblichen HD44780-Displays.
Da kann man im Quelltext nicht "Ä" schreiben, wenn "Ä" auf dem Display erscheinen soll.
Wenn es wichtig ist, dass es wirklich ein String ist (also mit
\0-Endung),
Nein, im Gegenteil, in diesem Fall sind in dem "String" Gerätekonfigurationsdaten enthalten. Da kommen sehr häufig 0x0 vor, ein Null-terminierter String ist hier nicht sinnvoll.
Was ich meine: wenn er „Uf“ haben will, das schon zur Compilezeit weiß,
warum definiert er dann nicht „U“ und „f“.
Ja, exakt, da steht z.B. für einen ADXL355-Sensor folgendes:
1
/*! register values */
2
#define ADXL355_DEVID_AD_VALUE (0xAD) //Analog Devices ID
3
#define ADXL355_DEVID_MST_VALUE (0x1D) //Analog Devices MEMS ID
4
#define ADXL355_PARTID_VALUE (0xED) //Device ID 0xED (355 octal)
Für andere Geräte steht da natürlich ganz was anderes. Und in dem Beispiel muss im Flash zum Schluss eine Zeichenkette "AD 1D ED" stehen. In der Liste ein Zeiger auf diese 3 Bytes.
Und auf die Weise lässt sich des Threadstarters Problem dann doch lösen:
Ja und nein, genau sowas wollte ich ja gerade vermeiden. Damit habe ich viele Zeilen Code mit Inhalt verteilt über unterschiedliche Sektionen. Dadurch schleichen sich ganz schnell Copy n Paste Fehler ein. Ja bei kleinen Listen mag das schleichen, aber wenn da über 50 Zeilen stehen, blickt da keiner mehr durch. In dem Beispiel von oben mit dem ADXL355 ist das ein Einzeiler:
TLDR:
Mit dem Cast "const uint8_t[]" funktioniert es genau wie gewünscht. Auf dem Bild sieht man, das die Liste im Flash steht, der Zeiger demzufolge auch. Und auch die Daten stehen im Flash. Wahlweise kann der Zeiger auch auf den Ram zeigen, und auch das geht.
oder
#define MY "\xe4"
const char units_lcd[] = " m" MY "npf";
const char units_iso[] = " mµnpf";
Für die Batterie-Icons ist das ja ok. Aber für Text? Muss das sein? Macht man das so? Wenn man das "ganz unten" im Treiber macht (ja, zur Laufzeit), bleibt der Quelltext doch ein wenig lesbarer. Soviel Zeit und Flash muss sein ;)
Für andere Geräte steht da natürlich ganz was anderes. Und in dem
Beispiel muss im Flash zum Schluss eine Zeichenkette "AD 1D ED" stehen.
In der Liste ein Zeiger auf diese 3 Bytes.
Ganz generell: Wenn es immer 3 Byte sind, kann sich das ruhig im Datentyp widerspiegeln. Also z.B. ein 3-Byte-Array.
Wenn Du 50 verschiedene dieser Structs hast, ggf. auf mehrere Files verteilt, dann macht es durchaus Sinn, diese Structs in den Files zusammenzufassen und nur den ptr zu veröffentlichen. Es ist sogar möglich, diese Liste vom Linker erstellen zu lassen. Das erfordert aber einige Erfahrung mit Linker-Sections.
Kurz gesagt: Es gibt 2 Möglichkeiten.
A) Du brauchst diese #defines (z.B. ADXL355_DEVID_AD_STR) im ganzen Code, dann stehen die in einer Header und Du packst sie irgendwo zusammen. Aber nicht zentral, sondern im jeweiligen Modul.
B) Du greifst auf diese Werte eher über Strukturen zu, dann brauchst Du die #defines gar nicht bzw. wenn, nur lokal.
Ab C23 kann man auch im Compound Literal einen Storage Class Specifier
angeben
Ich verwende die Cube-IDE. Und das nimmt der Compiler so ohne zu
meckern:
Das liegt daran, dass da der Scope und storage duration anders ist, als im ursprünglichen Beispiel.
Dort hatte man im localen Scope eine statische Variable (also mit static storage duration), die initialisiert wurde, aber das Compound literal hatte kein static, war damit weil lokal im Funktionsscope mit automatic storage duration. Das geht nicht, denn die statische Variable wird beim Programmstart initialisiert, aber das comound literal existiert es erst später, wenn die Funktion aufgerufen wird.
Ab C23 kann man auch bei einem Compound literal static dazuschreiben, also static storage duration geben, und das Problem entfällt.
In deinem Beispiel ist gar kein static. Ausserhalb einer Funktion haben dann beide, die Variable und das Compound Literal, static storage duration. Oder wenn es in einer Funktion wäre, hätten beide automatic storage duration. Das ist auch alles kein Problem.
Warum? Macht in meinem Fall gar keinen Sinn, aber "generell" ...
Denkblockade, Premature Optimization. Sieht man doch hier auch an der Diskussion. Mir sind die im Hintergrund lauernden Fallstricke einigermaßen bewusst - dir auch?
Z.B.:
Code-Übersetzung
Variablensicherheit
Speicher bzw. Stacknavigation.
Die im Hintergrund lauernden Fallstricke sind nicht trivial - weswegen erstmal ein Entwurf, der einigermaßen funktioniert hilfreich wäre.
So könnte man die Verbesserung dann Schrittweise und genauer machen.
Das sieht wie ein C-String aus!
Ist aber keiner, wie sich später dann zeigt.
Dann ergibt sich, eine paar Postings weiter, dass es Zahlen sind und keine Charakter.
Da es Implementation Defined ist, ob char unsigned oder signed ist, wird es später dann evtl Probleme geben beim rechnen (signed overflow wird UB) oder Überraschungen beim vergleichen.
const unsigned char * Content;
Oder gleich
const uint8_t * Content;
Wenn es denn uint8_t auf dem Kesselchen gibt.
Das gleiche gilt natürlich auch für Arrays.
Mein Rat:
Wenn es kein C-String ist oder sein soll, dann auch bitte die Verwechselungsgefahr ausschließen.