Forum: Mikrocontroller und Digitale Elektronik C - String aus Konstanten zusammensetzen


von Rangi J. (rangi)


Lesenswert?

Moin Forum,
ich stehe gerade aufm Schlauch und brauch mal Hilfe.
Ich möchte ein Array aufbauen, in dem n structs enthalten sind.
1
typedef struct tstConfigParam {
2
    uint8_t Register;
3
    uint8_t Length;
4
    const char * Content;
5
} tstConfigParam;
6
7
tstConfigParam MyParamList[] = {
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?

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Rangi J. schrieb:
> /*2*/ { 0x67, 2, {0x55, 0x66}}    /* <<<--- Fehler */

Weil ein const char* erwartet wird, du aber gar kein Array hast, von dem 
du einen Zeiger bilden kannst.  Das müsstest du schon erst noch bauen:
1
const char dummy1[] = {0x55, 0x66};
2
3
// …
4
/*2*/ { 0x67, 2, dummy1 },

… sollte gehen.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

ps: Oder du schreibst deine #defines gleich als Strings, dann geht es 
auch
1
 /*2*/ { 0x67, 2, "\x55" "\x66" },

von N. M. (mani)


Lesenswert?

So frisst es der online GCC:
1
#include <stdint.h>
2
3
typedef struct tstConfigParam {
4
    uint8_t Register;
5
    uint8_t Length;
6
    union {
7
        const char * Text;
8
        const uint8_t * Data;
9
    } Content;
10
} tstConfigParam;
11
12
void main()
13
{
14
  tstConfigParam a = { 0x01, 5, .Content.Text = "Hello" };
15
  tstConfigParam b = { 0x02, 2, .Content.Data = (uint8_t[]){0xAA, 0xBB} };
16
}

Wobei ich vorsichtig wäre mit strlen o.ä. zu hantieren wenn der Text 
abgeschlossen ist, die Byte Sequenz aber nicht.

von Rangi J. (rangi)


Lesenswert?

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.

von Obelix X. (obelix)


Lesenswert?

Rangi J. schrieb:
> /*2*/ { 0x67, 2, {0x55, 0x66}}    /* <<<--- Fehler */

Jörg W. schrieb:
> /*2*/ { 0x67, 2, "\x55" "\x66" },

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 
:
1
 /*2*/ { 0x67, 2, "\x55\x66\0" },

Beitrag #7965553 wurde von einem Moderator gelöscht.
Beitrag #7965557 wurde vom Autor gelöscht.
von Rangi J. (rangi)


Lesenswert?

N. M. schrieb:
> So frisst es der online GCC:

Das ist super, das war ein guter Tipp.
1
tstConfigParam MyParamList[] = {
2
 /*0*/ { 0x23, 4, "abcd"},
3
 /*1*/ { 0x45, 6, "efghij"},
4
 /*2*/ { 0x67, 2, .Content = (char[]){0xAA, 0xBB}}
5
};

so gehts.
Danke

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Funktioniert leider nicht in allen Situationen:
1
int main (void)
2
{
3
  static tstConfigParam b = { 0x02, 1, (const char[]) { 1, 2 } };
4
  return b.Content[0];
5
}
1
error: initializer element is not constant
2
   12 |   static tstConfigParam b = { 0x02, 1, (const char[]) { 1, 2 } };
3
      |                                        ^

von Bruno V. (bruno_v)


Lesenswert?

Wenn es wichtig ist, dass es wirklich ein String ist (also mit 
\0-Endung), dann geht auch sowas:
1
#define v2s(x) v2s_(x)
2
#define v2s_(x) #x
3
4
tstConfigParam MyParamList[] = {
5
 /*0*/ { 0x23, 4, "abcd"},
6
 /*1*/ { 0x45, 6, "efghij"},
7
 /*2*/ { 0x67, 2, v2s(0x55) v2s(Value2) }    /* <<<--- OK */
8
};

von Oliver S. (oliverso)


Lesenswert?

Nicht wirklich...

0x55 0x66 soll wohl den String "Uf" ergeben, nicht "0x550x66".

Oliver

von Bruno V. (bruno_v)


Lesenswert?

Oliver S. schrieb:
> 0x55 0x66 soll wohl den String "Uf" ergeben, nicht "0x550x66".

sorry, ja. Mit 0x geht es nicht so einfach. Und wenn es formatspezifisch 
ist, kann man es eh vergessen.

von Daniel A. (daniel-a)


Lesenswert?

Johann L. schrieb:
> Funktioniert leider nicht in allen Situationen:

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:
1
int main(void){
2
  static tstConfigParam b = { 0x02, 1, (static const char[]) { 1, 2 } };
3
  return b.Content[0];
4
}

C23 ist aber doch noch etwas sehr neu.

von Harald K. (kirnbichler)


Lesenswert?

Oliver S. schrieb:
> Nicht wirklich...
>
> 0x55 0x66 soll wohl den String "Uf" ergeben, nicht "0x550x66".

Darauf bin ich vorhin auch reingefallen ... aber ich hab's dann noch 
gemerkt ("stringify" ...)

von Rbx (rcx)


Lesenswert?

Rangi J. schrieb:
> 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.)

von Oliver S. (oliverso)


Lesenswert?

Bruno V. schrieb:
> Oliver S. schrieb:
>> 0x55 0x66 soll wohl den String "Uf" ergeben, nicht "0x550x66".
>
> sorry, ja. Mit 0x geht es nicht so einfach. Und wenn es formatspezifisch
> ist, kann man es eh vergessen.

Wobei sich da schon die nach dem Warum für diese Forderung stellt.

Oliver

von Bruno V. (bruno_v)


Lesenswert?

Oliver S. schrieb:
> 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.

von Oliver S. (oliverso)


Lesenswert?

Was ich meine: wenn er „Uf“ haben will, das schon zur Compilezeit weiß, 
warum definiert er dann nicht „U“ und „f“.

Oliver

von Harald K. (kirnbichler)


Lesenswert?

Oliver S. schrieb:
> 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.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Harald K. schrieb:
> Ein Beispiel wären Sonderzeichen auf den üblichen HD44780-Displays.

Da kann man sich aber auch anders helfen, ich habe hier beispielsweise 
sowas:
1
/* Character slots for CGRAM symbols */
2
#define BATLEFT_EMPTY "\x01"
3
#define BATLEFT_FULL  "\x02"
4
#define BATMID_EMPTY  "\x03"
5
#define BATMID_50     "\x04"
6
#define BATMID_FULL   "\x05"
7
#define BATRIGHT_FULL "\x06"
8
#define BATRIGHT_EMPTY "\x07"
9
10
/*
11
 * Battery symbol strings
12
 */
13
#define BAT_100  BATLEFT_FULL BATMID_FULL BATMID_FULL BATRIGHT_FULL
14
#define BAT_83   BATLEFT_EMPTY BATMID_FULL BATMID_FULL BATRIGHT_FULL
15
#define BAT_67   BATLEFT_EMPTY BATMID_50 BATMID_FULL BATRIGHT_FULL
16
#define BAT_50   BATLEFT_EMPTY BATMID_EMPTY BATMID_FULL BATRIGHT_FULL
17
#define BAT_33   BATLEFT_EMPTY BATMID_EMPTY BATMID_50 BATRIGHT_FULL
18
#define BAT_17   BATLEFT_EMPTY BATMID_EMPTY BATMID_EMPTY BATRIGHT_FULL
19
#define BAT_0    BATLEFT_EMPTY BATMID_EMPTY BATMID_EMPTY BATRIGHT_EMPTY

oder
1
#define MY "\xe4"
2
const char units_lcd[] = " m" MY "npf";
3
const char units_iso[] = " mµnpf";

von Harald K. (kirnbichler)


Lesenswert?

Jörg W. schrieb:
> Da kann man sich aber auch anders helfen

Im Prinzip ja, wenn man aber den numerischen Wert auch noch braucht ... 
obwohl, den hat man ja:
1
#define BATLEFT_EMPTY "\x01"
2
3
#define BATLEFT_EMPTY_VALUE BATLEFT_EMPTY[0]

Und auf die Weise lässt sich des Threadstarters Problem dann doch lösen:
1
#define Value1_str "\x55"
2
#define Value2_str "\x66"
3
4
#define Value1  Value1_str[0]
5
#define Value2  Value2_str[0]
6
7
...
8
9
 /*2*/ { 0x67, 2, Value1_str Value2_str}

Ha!

von Rangi J. (rangi)


Angehängte Dateien:

Lesenswert?

Bruno V. schrieb:
> 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.

Oliver S. schrieb:
> 0x55 0x66 soll wohl den String "Uf" ergeben
Ja, korrekt, aber es ist halt nur ein Platzhalter für irgendwelche 
Daten. "Uf" hat keinen Textbezug.

Daniel A. schrieb:
> 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:
1
typedef struct tstConfigParam {
2
    uint8_t Register;
3
    uint8_t Length;
4
    const uint8_t * Content;
5
} tstConfigParam;
6
tstConfigParam MyParamList[] = {
7
 /*0*/ { 0x23, 4, "abcd"},
8
 /*1*/ { 0x45, 6, "efghij"},
9
 /*2*/ { 0x67, 2, .Content = (const uint8_t[]){0x55, 0x66}}
10
};

Rbx schrieb:
>> Ich würde aber auch gerne vermeiden noch extra Variablen auzulegen.
>> Gibts da eine Möglichkeit?
>
> Was generell schon mal ungünstig ist.
Warum? Macht in meinem Fall gar keinen Sinn, aber "generell" ...

Oliver S. schrieb:
> 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.

Harald K. schrieb:
> 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:
1
{ADXL355_ADDR_DEVID_AD,   3, .Content = (const uint8_t[]){ADXL355_DEVID_AD_VALUE, ADXL355_DEVID_MST_VALUE, ADXL355_PARTID_VALUE}},

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.

Gelöst

von Harald K. (kirnbichler)


Lesenswert?

Rangi J. schrieb:
> a gerade vermeiden. Damit habe ich
> viele Zeilen Code mit Inhalt verteilt über unterschiedliche Sektionen.

Nö, Du hättest nur exakt zwei #defines statt einem, und der numerische 
Wert stünde auch nur in einem davon.

Das ist der einzige Unterschied.
1
#define ADXL355_DEVID_AD_STR     "\xAD" //Analog Devices ID
2
#define ADXL355_DEVID_AD_VALUE   ADXL355_DEVID_AD_STR[0]
3
4
#define ADXL355_DEVID_MST_STR    "\x1D" //Analog Devices MEMS ID
5
#define ADXL355_DEVID_MST_VALUE  ADXL355_DEVID_MST_STR[0]
6
7
#define ADXL355_PARTID_STR       "\xED" //Device ID 0xED (355 octal)
8
#define ADXL355_PARTID_VALUE     ADXL355_PARTID_STR[0]
9
10
...
11
12
{ADXL355_ADDR_DEVID_AD,   3, ADXL355_DEVID_AD_VALUE_STR ADXL355_DEVID_MST_VALUE_STR ADXL355_PARTID_VALUE_STR },

Das funktioniert dann auch mit älteren C-Dialekten (es gibt immer noch 
freilaufende Systeme, die kein C99 verstehen).

Aber gut, der andere Ansatz ist natürlich auch einer, wenn man sich 
seines Compilers sicher ist.

von Bauform B. (bauformb)


Lesenswert?

Jörg W. schrieb:
> Harald K. schrieb:
>> Ein Beispiel wären Sonderzeichen auf den üblichen HD44780-Displays.
>
> Da kann man sich aber auch anders helfen, ich habe hier beispielsweise
[... Zeichen im CG-RAM ...]

Jörg W. schrieb:
> 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 ;)

von Bruno V. (bruno_v)


Lesenswert?

Rangi J. schrieb:
> 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.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Bauform B. schrieb:
> Macht man das so?

Kann man zumindest.
1
#define oe "\x1234"
2
3
const char x[] = "Das ist nicht sch"oe"n";

finde ich durchaus noch lesbar.

von Daniel A. (daniel-a)


Lesenswert?

Rangi J. schrieb:
> Daniel A. schrieb:
>> 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.

von Rbx (rcx)


Lesenswert?

Rangi J. schrieb:
>> Was generell schon mal ungünstig ist.
> 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.:
1. Code-Übersetzung
2. Variablensicherheit
3. 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.

von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

Rangi J. schrieb:
> const char * Content;

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.

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.