Forum: Compiler & IDEs frage bzgl. void pointern


von gcc anfänger (Gast)


Lesenswert?

Hallo

Ich bin mittlerweile bei der Realisierung meines LCD- Menüs, schon so 
weit, dass ich das Einstellmenü entwerfe.
Hier arbeite ich mit einem großen Struct, das im EEPROM abgelegt wird, 
und wo die Daten der Menüeinstellungen gespeichert sind. Da das EEPROM 
aber nur eine begrenzte Anzahl an Schreibzugriffen pro Speicherzelle 
zulässt, kopiere ich dieses Struct beim Programmstart in seine exakte 
Entsprechung im SRAM, also in eine globale Variable mit der Funktion 
eeprom_read_block. Da das Menü auch Info- elemente enthält, die 
teilweise im sekundentakt aktualisiert werden müssen, wäre es ungünstig, 
jede Sekunde diese Daten in das EEPROM zu schreiben. Diese Daten werden 
dann vorerst ins RAM geschrieben. Nach einer einstellbaren Zeit (ca. 
5min) werden diese Daten dann ins EEPROM gesichert. So bleibt die Anzahl 
der Schreibzugriffe auf das EEPROM begrenzt.

Doch damit ich nicht zuweit abschreife hier mein Problem
1
int8_t step = 10;
2
3
void einstellen(menuStruct* value, uint8_t modus)
4
{
5
void* ptr=(menuStruct*)(&value->items[active_menu_index])->mem;
6
uint8_t datatype=(value->items[active_menu_index]).datatype;
7
8
uint32_t u32Value;
9
10
  switch (datatype)
11
  {
12
    case dt_time:
13
    u32Value = *((uint32_t *)ptr);
14
      switch(modus)
15
      {
16
        case mode_display:
17
          GLCD_Int(u32Value,0);
18
          break;
19
20
        case mode_incdec:
21
          u32Value +=step;
22
          ptr = (uint32_t*)(&u32Value);
23
          einstellen(value,mode_display);
24
          break;
25
26
      }
27
    break;
28
  }
29
  
30
}

Das ist ein Auszug aus der Routine für das Einstellungsmenü. mem ist ein 
void* Pointer auf den entsprechenden Datenwert des Structs im SRAM.
Das Auslesen der Daten (mode_display) funktioniert auch einwandfrei.
Möchte ich nun aber den Datenwert erhöhen oder erniedrigen und dann 
wieder an die Pointer Position zurückspeichern funktioniert das nicht. 
Rufe ich dann nämlich die Funktion rekursiv wieder auf nur jetzt mit dem 
modus mode_display, dann wird am Display wieder der ursprüngliche 
unveränderte Wert angezeigt.
1
ptr = (uint32_t*)(&u32Value);

Diese Codezeile hab ich schon mehrfach überprüft, hab aber meiner 
Meinung nach keine Widersprüchlichkeiten entdeckt.

Weis vll. jemand von euch Rat?

Schon mal vielen Dank dafür

MfG

von Karl H. (kbuchegg)


Lesenswert?

gcc anfänger schrieb:

1) ist der rekrusive AUfruf mehr als zweifelhaft. Die verfranst dich da
   unter Umständen in immer tiefere Aufrufhierarchien

2) Kein Wunder.
   Du änderst ja nicht den Wert in deiner Struktur selber, sondern eine
   Kopie davon, die du funktionslokal in der Variablen u32Value hältst.
   Die kannst du ändern bis du schwarz wirst, deswegen ändert sich
   ja in der Struktur nichts.



> Da das EEPROM
> aber nur eine begrenzte Anzahl an Schreibzugriffen pro Speicherzelle
> zulässt, kopiere ich dieses Struct beim Programmstart in seine exakte
> Entsprechung im SRAM, also in eine globale Variable mit der Funktion
> eeprom_read_block. Da das Menü auch Info- elemente enthält, die
> teilweise im sekundentakt aktualisiert werden müssen, wäre es ungünstig,
> jede Sekunde diese Daten in das EEPROM zu schreiben.

Das klingt nach einem Designfehler.
Wozu Info-Elemente ins EEPROM übertragen? Kostet nur Speicherplatz.

von Grrrr (Gast)


Lesenswert?

Erstmal eine Nebenbemerkung:
Wenn ptr tatsächlich ein (uint32_t*) sein soll, dann deklariere ihn 
nicht als (void *). Ich sehe keinen Grund dafür.

Aber der Hauptpunkt ist der:
Mit
1
...
2
void * ptr ... 
3
...
4
uint32_t u32Value;
5
...
6
ptr = (uint32_t*)(&u32Value);
7
...

veränderst Du den Zeiger selbst aber nicht den Inhalt, nicht das worauf 
er zeigt.

Wenn Du den Inhalt Verändern willst:
1
int8_t step = 10;
2
3
void einstellen(menuStruct* value, uint8_t modus)
4
{
5
uint32_t * ptr=(menuStruct*)(&value->items[active_menu_index])->mem;
6
uint8_t datatype=(value->items[active_menu_index]).datatype;
7
8
uint32_t u32Value;
9
10
  switch (datatype)
11
  {
12
    case dt_time:
13
    u32Value = *ptr;
14
      switch(modus)
15
      {
16
        case mode_display:
17
          GLCD_Int(u32Value,0);
18
          break;
19
20
        case mode_incdec:
21
          u32Value +=step;
22
          *ptr = u32Value;
23
          einstellen(value,mode_display);
24
          break;
25
26
      }
27
    break;
28
  }
29
  
30
}

Mit der anderen Deklaration von ptr siehts gleich ein wenig 
übersichtlicher aus, oder? :-)

von Karl H. (kbuchegg)


Lesenswert?

1
int8_t step = 10;
2
3
void einstellen( menuStruct* value, uint8_t modus )
4
{
5
  void* ptr = (menuStruct*)(&value->items[active_menu_index])->mem;
6
  uint8_t datatype = (value->items[active_menu_index]).datatype;
7
8
  switch (datatype)
9
  {
10
    case dt_time:
11
    {
12
      // bei dt_time wissen wir, dass der Pointer auf einen uint32_t zeigt
13
      uint32_t* dtTimePtr = (uint32_t *)ptr;
14
15
      switch(modus)
16
      {
17
        case mode_display:
18
          GLCD_Int( *dtTimePtr, 0 );
19
          break;
20
21
        case mode_incdec:
22
          *dtTimePtr += step;
23
          GLCD_Int( *dtTimePtr, 0 );
24
          // oder: einstellen( value, mode_display );
25
          break;
26
      }
27
    }
28
    break;
29
  }
30
}

von Karl H. (kbuchegg)


Lesenswert?

Grrrr schrieb:
> Erstmal eine Nebenbemerkung:
> Wenn ptr tatsächlich ein (uint32_t*) sein soll, dann deklariere ihn
> nicht als (void *). Ich sehe keinen Grund dafür.


er wird neben dt_time noch andere Datentypen haben, die dann eben nicht 
uint32_t sind

von Grrrr (Gast)


Lesenswert?

Karl heinz Buchegger schrieb:
> er wird neben dt_time noch andere Datentypen haben, die dann eben nicht
> uint32_t sind

Wahrscheinlich. Habe auch erst hinterher gesehen, das das eigentlich nur 
als Code-"Ausschnitt" Sinn macht.
Habe bis heute morgen gebastelt und wollte beim Kaffee mal reingucken. 
Wohl noch nicht genug Kaffee. ;-)

von Karl H. (kbuchegg)


Lesenswert?

Wobei ich noch nicht wirklich davon überzeugt bin, dass ein void-Pointer 
im Speziellen bzw. ein Pointer überhaupt an dieser Stelle eine gute Idee 
ist.

Oftmals ist es einfacher, an dieser Stelle im Menü ganz einfach einen 
uint16_t (oder einen uint32_t) vorzusehen, anstatt sich da einen 
zusätzlichen Pointer zu installieren, der dann auf massgeschneiderte 
Datenfelder zeigt. Die zusätzlichen Pointer kosten mehr Speicherplatz, 
als wie wenn man einen uint16_t ganz einfach in einem uint32_t speichert 
und ein paar Bytes nicht benutzt.
Will man keinen uint32_t als generelle Speicherfläche nutzen, dann geht 
man auf einen uint16_t als größtes gemeinsames Vielfaches und sieht zu, 
dass man größere Daten anders speichert. Man muss ja nicht Uhrzeit und 
Datum in einem einzigen uint32_t ablegen, sondern kann ja auch Zeit und 
Datum getrennt in jeweils einen eigenen uint16_t ablegen.

universeller ist es natürlich mit dem mem-Pointer.
Nur kann man sich auf einem µC zuviel Universalität oftmals aus 
Resourcengründen nicht leisten.

von gcc anfänger (Gast)


Lesenswert?

Hallo

Vielen dank für die vielen Antworten in so kurzer Zeit!!

Karl heinz Buchegger schrieb:
> ist der rekrusive AUfruf mehr als zweifelhaft. Die verfranst dich da
>    in immer tiefere Aufrufhierarchien, die dann niemals wieder
>    aufgelöst werden

Die Rekursion ist hier nur als schnelle Testmethode zu verstehen. Ich 
weis das hab ich vergessen zu erwähnen. In der Endversion wird die 
Funktion von einer anderen Funktion aufgerufen.

Karl heinz Buchegger schrieb:
> Das klingt nach einem Designfehler.
> Wozu Info-Elemente ins EEPROM übertragen? Kostet nur Speicherplatz.

Sehe ich nicht so. Es geht darum Drehzahlwerte und Geschwindigkeiten zu 
erfassen und dann noch aus diesen Werten z.B. Durchschnittswerte 
Höchstwerte Mindestwerte usw. zu bilden.

Grrrr schrieb:
> Wahrscheinlich. Habe auch erst hinterher gesehen, das das eigentlich nur
> als Code-"Ausschnitt" Sinn macht.
> Habe bis heute morgen gebastelt und wollte beim Kaffee mal reingucken.
> Wohl noch nicht genug Kaffee. ;-)

Hab bis zu 12 verschiedene Datentypen, mit denen ich unterschiedlich 
verfahren muss.

von Grrrr (Gast)


Lesenswert?

gcc anfänger schrieb:
> Grrrr schrieb:
>> Wahrscheinlich. Habe auch erst hinterher gesehen, das das eigentlich nur
>> als Code-"Ausschnitt" Sinn macht.
>> Habe bis heute morgen gebastelt und wollte beim Kaffee mal reingucken.
>> Wohl noch nicht genug Kaffee. ;-)
>
> Hab bis zu 12 verschiedene Datentypen, mit denen ich unterschiedlich
> verfahren muss.

Is ja gut.

von gcc anfänger (Gast)


Lesenswert?

Karl heinz Buchegger schrieb:
> Oftmals ist es einfacher, an dieser Stelle im Menü ganz einfach einen
> uint16_t (oder einen uint32_t) vorzusehen, anstatt sich da einen
> zusätzlichen Pointer zu installieren, der dann auf massgeschneiderte
> Datenfelder zeigt. Die zusätzlichen Pointer kosten mehr Speicherplatz,
> als wie wenn man einen uint16_t ganz einfach in einem uint32_t speichert
> und ein paar Bytes nicht benutzt.
> Will man keinen uint32_t als generelle Speicherfläche nutzen, dann geht
> man auf einen uint16_t als größtes gemeinsames Vielfaches und sieht zu,
> dass man größere Daten anders speichert. Man muss ja nicht Uhrzeit und
> Datum in einem einzigen uint32_t ablegen, sondern kann ja auch Zeit und
> Datum getrennt in jeweils einen eigenen uint16_t ablegen.
>
> universeller ist es natürlich mit dem mem-Pointer.
> Nur kann man sich auf einem µC zuviel Universalität oftmals aus
> Resourcengründen nicht leisten.

Hab noch ca 50% Platz auf meinem MEGA32. Und die anderen 50% werden 
nicht nur von der Menüstruktur belegt :) Die braucht so ca. 20%. Sie Ist 
aber auch nicht unbedingt die kleinste.

von Karl H. (kbuchegg)


Lesenswert?

gcc anfänger schrieb:

> Sehe ich nicht so. Es geht darum Drehzahlwerte und Geschwindigkeiten zu
> erfassen und dann noch aus diesen Werten z.B. Durchschnittswerte
> Höchstwerte Mindestwerte usw. zu bilden.

und warum müssen die ins EEPROM übertragen werden?

(Ein Maximalwert wird zwar sekündlich aktualisiert, sprich 
nachgerechnet, abr nach kurzer Zeit hat der doch seinen Höchststand 
erreicht und wird doch meistens nur noch ganz selten höher, wenn er 
schon im EEPROM überdauern muss)

Aber ist egal. Das musst sowieso du entscheiden bzw. so machen wie du es 
für richtig hältst.

von gcc anfänger (Gast)


Lesenswert?

Karl heinz Buchegger schrieb:
> und warum müssen die ins EEPROM übertragen werden?

Ganz einfach.

Die Drehzahlwerte werden hin hintergrund von der Hardware+ AVR 
mitgeloggt. Es geht um ein Hobbyprojekt einer Motorrad Cockpitanzeige. 
Der Benutzer sieht am Display nur einen Balken der die Drehzahl anzeigt, 
da dies der wichtigste Wert ist.  Wenn der Benutzer nun genaueres über 
die während der Fahrt gesammelten Daten wissen will, dann geht  er ins 
Menü und die gesammelten Daten wie Motorlaufzeit, max. Drehzahl, 
durchschnittliche Drehzahl etc. werden ihm in den verschiedenen 
auswählbaren Menüpunkten angezeigt. Damit ich diese Werte eben auch 
nachdem der Strom abgeschaltet wurde anzeigen kann, sozusagen als 
Logbuch, müssen die ins EEPROM.

MfG

von gcc anfänger (Gast)


Lesenswert?

Karl heinz Buchegger schrieb:
1
int8_t step = 10;
2
3
void einstellen( menuStruct* value, uint8_t modus )
4
{
5
  void* ptr = (menuStruct*)(&value->items[active_menu_index])->mem;
6
  uint8_t datatype = (value->items[active_menu_index]).datatype;
7
8
  switch (datatype)
9
  {
10
    case dt_time:
11
    {
12
      // bei dt_time wissen wir, dass der Pointer auf einen uint32_t zeigt
13
      uint32_t* dtTimePtr = (uint32_t *)ptr;
14
15
      switch(modus)
16
      {
17
        case mode_display:
18
          GLCD_Int( *dtTimePtr, 0 );
19
          break;
20
21
        case mode_incdec:
22
          *dtTimePtr += step;
23
          GLCD_Int( *dtTimePtr, 0 );
24
          // oder: einstellen( value, mode_display );
25
          break;
26
      }
27
    }
28
    break;
29
  }
30
}

Funktioniert einwandfrei! Danke!

Jetzt hab ich nur noch eine Frage:

Gibt es auch eine Möglichkeit die gesamte Wertzuweisung in einer Zeile 
unterzubringen. Also ich meine, dass man nicht zuerst einen extra 
uint32_t* Pointer deklarieren muss, den man dann in der Funktion dann 
verwendet. Es wäre nämlich eine elegante Lösung wenn das ginge.

MfG

von Karl H. (kbuchegg)


Lesenswert?

gcc anfänger schrieb:

> Gibt es auch eine Möglichkeit die gesamte Wertzuweisung in einer Zeile
> unterzubringen.

Natürlich.

> Also ich meine, dass man nicht zuerst einen extra
> uint32_t* Pointer deklarieren muss, den man dann in der Funktion dann
> verwendet.

Vertrau deinem Compiler. Nur weil du eine Variable schreibst, heist das 
noch lange nicht, das der Optimizer sie nicht wieder rauswirft, wenn er 
draufkommt, das er sie nicht braucht

> Es wäre nämlich eine elegante Lösung wenn das ginge.

Das wäre nicht wirklich elegant.
Du würdest den einen Cast über den Code verstreuen. Und fehleranfälliger 
gegenüber Änderungen wirds ausserdem.
1
    case dt_time:
2
    {
3
      switch(modus)
4
      {
5
        case mode_display:
6
          GLCD_Int( *(uint32_t *)ptr, 0 );
7
          break;
8
9
        case mode_incdec:
10
          *(uint32_t *)ptr += step;
11
          GLCD_Int( *(uint32_t *)ptr, 0 );
12
          // oder: einstellen( value, mode_display );
13
          break;
14
      }
15
    }

Aber wie gesagt: Vertrau deinem Compiler.
Der hat schon langst geschnallt, dass dtTimePtr im Grunde dasselbe wie 
ptr ist und sich nur im Datentyp unterscheidet. Da sich das auch nicht 
ändert kann er in diesem Codeabschnitt intern so tun als ob ptr ein 
uint32_t Pointer gewesen wäre und den direkt benutzen anstelle einer 
zusätzlichen Pointervariablen.

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.