C - String aus Konstanten zusammensetzen

#7965531
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?

#7965543
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.

#7965545
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.

#7965549
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.
#7965607
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
};
#7965694
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.

#7965703
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.)

#7965762
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.

Moderator Persönliche Seite #7965850
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";
#7965862
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!

#7965940
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

Angehängte Dateien:
#7965963
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.

#7965996
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 ;)

#7966035
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.

#7966055
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.

#7966270
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.

(Firma: Gast) #7966277
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.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren