Forum: Mikrocontroller und Digitale Elektronik C: Tabelle mit Zeiger auf unterschiedlich lange Variablen


von Micha (michael_schwaer)


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

von Franko P. (sgssn)


Lesenswert?

Was hast denn da für ein Zauberwerk? Microkontroller oder PC? Wieviele 
Bit hat das Ding? Und was für nen Compiler?

Gruß

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


Lesenswert?

Die C Zeigerwirtschaft ist sowieso schon recht gruselig.
Ein steter Quell besonderer Freuden.
Du machst es nicht besser, nur noch kritischer.

Mein Rat:
Keine falschen Zeigertypen verwenden!
Und wenn schon untypisierte Zeiger, dann doch bitte void Zeiger.

: Bearbeitet durch User
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


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.

: Bearbeitet durch Moderator
von Micha (michael_schwaer)


Angehängte Dateien:

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?

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


Lesenswert?

Micha schrieb:
> Was muss ich da angeben, damit es passt?

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

Ich muss jetzt erstmal zum Mittagessen. :)

von Rbx (rcx)


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.

von Daniel A. (daniel-a)


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.

: Bearbeitet durch User
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


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.

von Daniel A. (daniel-a)


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.

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


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.

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.