C: Tabelle mit Zeiger auf unterschiedlich lange Variablen

OP #7663506
Lesenswert?

Hallo,

ich habe ein Problem und hoffe Ihr könnt mir weiterhelfen. Ich habe ein Tabelle, wo die Adressen von Parametern abgelegt sind. Es sind 8, 16 und 32-Byte-Parameter. Für die Definition der Tabelle habe ich die größten Wert verwendet (32 Bit).

1
struct knx_dp_def
2
{
3
  UINT32 u32_ID;
4
  UINT8 u8_Len;
5
  UINT8 u8_Sonder;
6
  UINT32* p32_Parameter;  // Zeiger auf Parameter
7
};
8
extern struct knx_dp_def knx_dp[KNX_MAX_DP];

Damit der Compiler nicht meckert, habe ich vor jeder Variablen einen Cast vorgesetzt (auch bei den 32-er Variablen - schadet ja nicht):

1
struct knx_dp_def knx_dp[KNX_MAX_DP] = {
2
// Array        ID    LEN    Sonder    Parameter
3
/*  0   */        DP_KEINER    ,  0,    NEIN,    NULL_PTR_GEN,    
4
/*  1   */        DP_TEST_RELAIS_SCHALTEN    ,  32,    NEIN,    (UINT32*)&test.u32_Para_1,    
5
/*  2   */        DP_TEST_RELAIS_STATUS    ,  32,    NEIN,    (UINT32*)&knx_io.u8_Object_knx_test,    
6
/*  3   */        DP_TEST_NR3_1_BIT    ,  8,    NEIN,    (UINT32*)&test.u8_Para_1,    
7
/*  4   */        DP_TEST_NR4_1_BIT    ,  8,    NEIN,    (UINT32*)&test.u8_Para_2,    
8
/*  5   */        DP_TEST_NR5_1_BYTE    ,  8,    NEIN,    (UINT32*)&test.u8_Para_1,    
9
/*  6   */        DP_TEST_NR6_1_BYTE    ,  8,    NEIN,    (UINT32*)&test.u8_Para_2,    
10
/*  7   */        DP_TEST_NR7_2_BYTE    ,  16,    NEIN,    (UINT32*)&test.u16_Para_1,    
11
/*  8   */        DP_TEST_NR8_2_BYTE    ,  16,    NEIN,    (UINT32*)&test.u16_Para_2,    
12
/*  9   */        DP_TEST_NR9_4_BYTE    ,  32,    NEIN,    (UINT32*)&test.u32_Para_1,    
13
/*  10   */        DP_TEST_NR10_4_BYTE    ,  32,    NEIN,    (UINT32*)&test.u32_Para_2,

Soweit so gut. Nun will ich aber Werte in die entsprechenden Parameter schreiben (in meinem Fall empfangene KNX-Daten). Damit ich weiß, wie groß der Parameter ist, habe ich eine Längenangabe in der Tabelle abgelegt (8, 16, 32). Die Info verwende ich auch, um den Wert entsprechend zu casten:

1
// Zugriff auf die Tabelle
2
if(knx_dp[uiDatapoint].p32_Parameter != NULL_PTR_GEN)  // Parameter enthalten
3
{
4
  if(knx_dp[uiDatapoint].u8_Len == 32)
5
    *knx_dp[uiDatapoint].p32_Parameter = ulWert;
6
  else if(knx_dp[uiDatapoint].u8_Len == 8)
7
    *knx_dp[uiDatapoint].p32_Parameter = (UINT8)ulWert;
8
  else if(knx_dp[uiDatapoint].u8_Len == 16)
9
    *knx_dp[uiDatapoint].p32_Parameter = (UINT16)ulWert;
10
}

Das funktioniert prinzipiell: aber der Zeiger verändert 32-Bit im RAM, auch wenn ich 8 oder 16 Bit habe. Wie bringe ich den Zeiger dazu, dass er nur 8 oder 16 Bit verändert? Geht das überhaupt irgendwie mit einem Cast? Ich habe schon selbst herumexperimentiert, aber keine Lösung gefunden. Mit Zeigern habe ich immer mal wieder etwas Verständnis-Schwierigkeiten.

Gruß Micha

Moderator Persönliche Seite #7663529
Lesenswert?

Du musst den Zeiger für den Zugriff selbst casten. Natürlich muss (je nach Architektur) der Zeiger selbst passend ausgerichtet sein für die jeweilige Objektgröße.

Sinnvoller wäre es, eine union aus Zeigern der unterschiedlichen Größen zu machen und dann das passende union tag auszuwählen.

1
union knx_param_ptr {
2
  uint8_t *ptr8;
3
  uint16_t *ptr16;
4
  uint32_t *ptr32;
5
};
6

7
struct knx_dp_def
8
{
9
  uint32_t u32_ID;
10
  uint8_t u8_Len;
11
  uint8_t u8_Sonder;
12
  union knx_param_ptr parameter;
13
};
14
// …
15
if(knx_dp[uiDatapoint].p32_Parameter != NULL)  // Parameter enthalten
16
{
17
  if(knx_dp[uiDatapoint].u8_Len == 32)
18
    *knx_dp[uiDatapoint].paramter.ptr32 = ulWert;
19
  else if(knx_dp[uiDatapoint].u8_Len == 8)
20
    *knx_dp[uiDatapoint].parameter.ptr8 = (uint8_t)ulWert;
21
  else if(knx_dp[uiDatapoint].u8_Len == 16)
22
    *knx_dp[uiDatapoint].parameter.ptr16 = (uint16_t)ulWert;
23
}

Sonstige Anmerkungen: uint8_t, uint16_t und uint32_t sind seit C99 mit diesen Namen standardisiert. Benutze sie, statt eigene zu erfinden.

NULL ist seit Anbeginn standardisiert. Benutze es, statt eigene Namen zu erfinden.

Die Fragwürdigkeit der "ungarischen" Notation ist hinlänglich diskutiert worden. Deine Sache, ob du sowas trotzdem unbedingt brauchst.

OP #7663747
Lesenswert?

Jörg W. schrieb:

Sinnvoller wäre es, eine union aus Zeigern der unterschiedlichen Größen zu machen und dann das passende union tag auszuwählen.

Vielen Dank für den Tipp. In meiner Tabelle kommen nun aber Warnungen bei allen definierten Parametern, die mehr als 8 Bit haben (siehe Bild). Was muss ich da angeben, damit es passt?

Angehängte Dateien:
#7663895
Lesenswert?

Micha schrieb:

Mit Zeigern habe ich immer mal wieder etwas Verständnis-Schwierigkeiten.

Mach halt ein paar Inputs auf Nachfrage, dann bekommst du das schon irgendwie mit.

Beim PC Asm muss man auch angeben, ob man einen Byte-Pointer oder einen Word-Pointer haben will, wenn Daten aus dem Speicher gelesen werden.

#7663919
Lesenswert?

Ich würde einen void* Pointer nehmen, und den Typ statt die Grösse speichern:

1
enum knx_dp_param_type { // in C23, we could use `enum knx_dp_param_type : uint8_t`
2
  KNX_FP_PT_NONE,
3
  KNX_FP_PT_U8,
4
  KNX_FP_PT_U16,
5
  KNX_FP_PT_U32,
6
};
7

8
struct knx_dp_def {
9
  uint32_t id;
10
  uint8_t type; // enum knx_dp_param_type would be larger 
11
  uint8_t sonder;
12
  void* parameter;  // Zeiger auf Parameter
13
};
14

15
extern struct knx_dp_def knx_dp[KNX_MAX_DP];
16

17
...
18
  switch((enum knx_dp_param_type)knx_dp[uiDatapoint].type){ // The cast allows the compiler to warn us about forgotten cases.
19
    case KNX_FP_PT_NONE: break;
20
    case KNX_FP_PT_U8: *(uint8_t*)knx_dp[uiDatapoint].parameter = ulWert; break;
21
    case KNX_FP_PT_U16: *(uint16_t*)knx_dp[uiDatapoint].parameter = ulWert; break;
22
    case KNX_FP_PT_U32: *(uint32_t*)knx_dp[uiDatapoint].parameter = ulWert; break;
23
  }

Und bei der Initialisierung kann man den Typ dann weglassen, weil Konvertierung von X* nach void* geht immer.

1
struct knx_dp_def knx_dp[KNX_MAX_DP] = {
2
// Array        ID    LEN    Sonder    Parameter
3
/*  0   */ { DP_KEINER },
4
/*  1   */ { DP_TEST_RELAIS_SCHALTEN,  KNX_FP_PT_U8  , NEIN, &knx_io.u8_Object_knx_test },
5
  ...
6
}

Die zusätzlichen {} würde ich hier sehr empfehlen, das mögen die Compiler, und macht alles etwas einfacher.

Zuletzt noch zur id. Stimmt dessen Wert mit dem Offset des Eintrags überein? (also ist uiDatapoint == knx_dp[uiDatapoint].id?) Den dann könnte man den aus knx_dp_def weg lassen:

1
struct knx_dp_def knx_dp[KNX_MAX_DP] = {
2
  [DP_KEINER] = {0},
3
  [DP_TEST_RELAIS_SCHALTEN] = {KNX_FP_PT_U8, NEIN, &knx_io.u8_Object_knx_test},
4
};

Und zum Pointer, braucht es den Pointer wirklich? Falls der Pointer mindestens 32bit braucht, und man in knx_dp_def einfach uint32_t parameter nehmen würde, würde das sogar Speicherplatz sparen, und man bräuchte keine Casts mehr.

Moderator Persönliche Seite #7664032
Lesenswert?

Jörg W. schrieb:

Micha schrieb:

Was muss ich da angeben, damit es passt?

Du musst das passende union tag initialisieren. Stichwort: named initializers

1
struct knx_dp_def knx_dp[KNX_MAX_DP] = {
2
/*  0   */ .u32_ID=DP_KEINER,               .u8_Len=0,  .u8_Sonder=NEIN, /* 1) */
3
/*  1   */ .u32_ID=DP_TEST_RELAIS_SCHALTEN, .u8_Len=32, .u8_Sonder=NEIN, .parameter.ptr32=&test.u32_Para_1,
4
/*  2   */ .u32_ID=DP_TEST_RELAIS_STATUS,   .u8_Len=32, .u8_Sonder=NEIN, .parameter.ptr32=&knx_io.u8_Object_knx_test,    
5
/*  3   */ .u32_ID=DP_TEST_NR3_1_BIT,       .u8_Len=8,  .u8_Sonder=NEIN, .parameter.ptr8=&test.u8_Para_1,
6
/*  4   */ .u32_ID=DP_TEST_NR4_1_BIT,       .u8_Len=8,  .u8_Sonder=NEIN, .parameter.ptr8=&test.u8_Para_2,
7
/*  5   */ .u32_ID=DP_TEST_NR5_1_BYTE,      .u8_Len=8,  .u8_Sonder=NEIN, .parameter.ptr8=&test.u8_Para_1,
8
/*  6   */ .u32_ID=DP_TEST_NR6_1_BYTE,      .u8_Len=8,  .u8_Sonder=NEIN, .parameter.ptr8=&test.u8_Para_2,
9
/*  7   */ .u32_ID=DP_TEST_NR7_2_BYTE,      .u8_Len=16, .u8_Sonder=NEIN, .parameter.ptr16=&test.u16_Para_1,
10
/*  8   */ .u32_ID=DP_TEST_NR8_2_BYTE,      .u8_Len=16, .u8_Sonder=NEIN, .parameter.ptr16=&test.u16_Para_2,
11
/*  9   */ .u32_ID=DP_TEST_NR9_4_BYTE,      .u8_Len=32, .u8_Sonder=NEIN, .parameter.ptr32=&test.u32_Para_1,
12
/*  10  */ .u32_ID=DP_TEST_NR10_4_BYTE,     .u8_Len=32, .u8_Sonder=NEIN, .parameter.ptr32=&test.u32_Para_2,

Du siehst schon, Weglassen der "ungarischen" Notation würde die Zeilen etwas verkürzen …

  1. Du kannst natürlich .parameter.ptr32=NULL explizit schreiben, aber bei einer teilweisen Initialisierung einer struct werden implizit alle nicht genannten struct tags eh mit 0 bzw. NULL bzw. 0.0 gefüllt, insofern ist die Angabe dort unnötig, zumal in diesem Falle ja "parameter" eh nicht ausgewertet werden wird.
#7664042
Lesenswert?

Hier braucht es die klammern nun aber wirklich. Entweder

1
struct knx_dp_def knx_dp[KNX_MAX_DP] = {
2
  { .u32_ID=DP_KEINER },

Oder:

1
struct knx_dp_def knx_dp[KNX_MAX_DP] = {
2
  [0].u32_ID=DP_KEINER,

aber

1
struct knx_dp_def knx_dp[KNX_MAX_DP] = {
2
  .u32_ID=DP_KEINER,

wird nicht gehen.

Moderator Persönliche Seite #7664062
Lesenswert?

Daniel A. schrieb:

Hier braucht es die klammern nun aber wirklich.

Stimmt, danke für die Korrektur.

Sinnvoll finde ich die Variante, die den Array-Index explizit benennt:

1
struct knx_dp_def knx_dp[KNX_MAX_DP] = {
2
[0] = { .u32_ID=DP_KEINER,               .u8_Len=0,  .u8_Sonder=NEIN, /* 1) */ },
3
[1] = { .u32_ID=DP_TEST_RELAIS_SCHALTEN, .u8_Len=32, .u8_Sonder=NEIN, .parameter.ptr32=&test.u32_Para_1 },
4
[2] = { .u32_ID=DP_TEST_RELAIS_STATUS,   .u8_Len=32, .u8_Sonder=NEIN, .parameter.ptr32=&knx_io.u8_Object_knx_test },
5
[3] = { .u32_ID=DP_TEST_NR3_1_BIT,       .u8_Len=8,  .u8_Sonder=NEIN, .parameter.ptr8=&test.u8_Para_1 },
6
[4] = { .u32_ID=DP_TEST_NR4_1_BIT,       .u8_Len=8,  .u8_Sonder=NEIN, .parameter.ptr8=&test.u8_Para_2 },
7
[5] = { .u32_ID=DP_TEST_NR5_1_BYTE,      .u8_Len=8,  .u8_Sonder=NEIN, .parameter.ptr8=&test.u8_Para_1 },
8
[6] = { .u32_ID=DP_TEST_NR6_1_BYTE,      .u8_Len=8,  .u8_Sonder=NEIN, .parameter.ptr8=&test.u8_Para_2 },
9
[7] = { .u32_ID=DP_TEST_NR7_2_BYTE,      .u8_Len=16, .u8_Sonder=NEIN, .parameter.ptr16=&test.u16_Para_1 },
10
[8] = { .u32_ID=DP_TEST_NR8_2_BYTE,      .u8_Len=16, .u8_Sonder=NEIN, .parameter.ptr16=&test.u16_Para_2 },
11
[9] = { .u32_ID=DP_TEST_NR9_4_BYTE,      .u8_Len=32, .u8_Sonder=NEIN, .parameter.ptr32=&test.u32_Para_1 },
12
[10] = {.u32_ID=DP_TEST_NR10_4_BYTE,     .u8_Len=32, .u8_Sonder=NEIN, .parameter.ptr32=&test.u32_Para_2 },

Damit erübrigt sich gleich noch der Kommentar zum Array-Index.

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