Forum: Mikrocontroller und Digitale Elektronik Struct als Argument (C++)


von Rahald (Gast)


Lesenswert?

Hallo,

ich versuche, eine Sruct beliebiger Ausprägung als Argument an eine 
Funktion zu übergeben. Mit einem Struct-Type für einen bestimmte Struct 
gehts, analog mit void für beliebige Structs nicht. Der Compiler kann 
doch die Adresse einer Variablen dem Typ zuordnen, also müsste das doch 
gehen? Was mache ich falsch?

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Rahald schrieb:
> analog mit void für beliebige Structs nicht

Wenn schon "void*". Das ist aber untypisierter unsicherer C-Stil. In C++ 
gibt es dafür std::variant, oder die ganze Funktion zum template machen.

Rahald schrieb:
> Der Compiler kann
> doch die Adresse einer Variablen dem Typ zuordnen, also müsste das doch
> gehen? Was mache ich falsch?
structs werden nicht per Adresse übergeben, sondern kopiert! Wenn du 
Adressen übergeben willst, musst du einen Pointer-Typ verwenden. C++ 
verbietet absichtlich das Umwandeln von Pointer-Typen ineinander, weil 
das den Code fehleranfällig macht.

Beschreibe mal genau wozu du das brauchst, dann kann man dir zeigen wie 
man so etwas richtig macht.

von Rahald (Gast)


Lesenswert?

Niklas G. schrieb:
> Beschreibe mal genau wozu du das brauchst, dann kann man dir zeigen wie
> man so etwas richtig macht.

Gut. Ich versuchs. Hoffentlich krieg ichs verständlich hin

Ich versuche in einer Funktion EEPROM.put mittels EEPROM.update zu 
realisieren.

EEPROM.put nimmt als '*Buffer' eine beliebige Datenstruktur entgegen und 
schreibt den Inhalt in das EEPROM.

EEPROM.update macht das nur Byte-weise, schreibt aber nur bei 
Veränderung der Daten.

Beides will ich nun miteinander kombinieren und dazu muss ich 'eine 
beliebige Datenstruktur' an meine Funktion, nennen wir sie 'EEPROM_put', 
übergeben. Nur das gelingt mir nicht mittels void *. Weiß nicht, was ich 
sonst als type für Buffer angeben soll.

Ich könnte natürlich einen Blick in die EEPROM.h werfen. Ha, das tue ich 
auch

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Rahald schrieb:
> Ich versuche in einer Funktion EEPROM.put mittels EEPROM.update zu
> realisieren.

Was sind das für Funktionen und wo kommen sie her?

Rahald schrieb:
> Nur das gelingt mir nicht mittels void *

Besser wäre char*, damit du auf die Bytes zugreifen kannst. Ohne Code 
kann man aber nicht sagen wo der Fehler liegt.

Siehe auch Serialisierung - insbesondere wenn du im EEPROM ein 
bestimmtes Format haben möchtest und nicht zur das, was der Compiler 
zufällig an Speicherlayout produziert.

von Rahald (Gast)


Lesenswert?

Niklas G. schrieb:
>
> Was sind das für Funktionen und wo kommen sie her?

EEPROM.h

> Ohne Code kann man aber nicht sagen wo der Fehler liegt.


Ich habe leider noch nichts lauffähiges. Weil das ja nicht funzt.

Der Blick in die EEPROM.h verwirrt mich noch mehr. '&t' verstehe ich ja 
(hatte ich auch im Test), aber da steht auch 'T' , wo ein Type stehen 
soll. Und das geht über meine Kenntnisse.
1
    //Functionality to 'get' and 'put' objects to and from EEPROM.
2
    template< typename T > T &get( int idx, T &t ){
3
        EEPtr e = idx;
4
        uint8_t *ptr = (uint8_t*) &t;
5
        for( int count = sizeof(T) ; count ; --count, ++e )  *ptr++ = *e;
6
        return t;
7
    }
8
    
9
    template< typename T > const T &put( int idx, const T &t ){
10
        EEPtr e = idx;
11
        const uint8_t *ptr = (const uint8_t*) &t;
12
        for( int count = sizeof(T) ; count ; --count, ++e )  (*e).update( *ptr++ );
13
        return t;
14
    }

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Rahald schrieb:
> EEPROM.h

Und wo kommt die hier?

Rahald schrieb:
> Der Blick in die EEPROM.h verwirrt mich noch mehr.

Das sind templates. So könntest du deine Funktion auch umsetzen.

Rahald schrieb:
> Ich habe leider noch nichts lauffähiges. Weil das ja nicht funzt.
Ohne Code kann man nicht bei der Fehlersuche helfen...

von Rahald (Gast)


Lesenswert?

Niklas G. schrieb:
> Das sind templates. So könntest du deine Funktion auch umsetzen.

Ja genau. Die Gedanken hatte ich auch gerade.

Und nochwas ist mir aufgefallen: EEPROM.put scheint bereits 'update' zu 
verwenden. So richtig schlau bin ich aber noch nicht geworden. In der h 
sind zu viele Dinge, die ich noch nicht kenne.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Das ist die EEPROM.h von Arduino? Reicht die Doku nicht?

https://www.arduino.cc/en/Tutorial/EEPROMUpdate

von Rahald (Gast)


Lesenswert?

Niklas G. schrieb:

> https://www.arduino.cc/en/Tutorial/EEPROMUpdate

Ja die habe ich. Ich habe versucht, put und update zu kombinieren. Ist 
aber offenbar in der h schon realisiert. Das wußte ich nicht und habe 
ich auch nicht vermutet.

Immerhin kenne ich jetzt auch templates :-) Danke für die Hilfe.

von A. S. (Gast)


Lesenswert?

Wenn Du beliebige Strukturen irgendwo hinschreiben oder byteweise 
verlgeichen willst, dann nimmt man einen (void *)-Pointer und gibt die 
Länge an. Beispiel:
1
void DoUpdate(void *p, int len) /* hier kein int/size_t-Krieg am Anfang bitte */
2
{
3
unsigned char *pc=(unsigned char*) p; /* Pointer auf 1 Byte-Typ */
4
int i;
5
6
   for(i=0;i<len;i++)
7
   {
8
       /* mache etwas mit den Daten */
9
       foo(p[i]);
10
   }
11
}
12
13
/* beliebige Struktur */
14
struct 
15
{
16
   int a;
17
   int b;
18
]myStruct;
19
20
void myFunc(void)
21
{
22
   /* und verarbeitung. Du brauchst hier keinen Cast, wenn die Funktion void* erwartet! */
23
   DoUpdate(&myStruct, sizeof(myStruct));
24
}

von Rahald (Gast)


Lesenswert?

@ A. S.
Danke, aber das 'sizeof(myStruct)' ist ungeil. Es muss ohne gehen.

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

A. S. schrieb:
> dann nimmt man einen (void *)-Pointer und gibt die Länge an

In C macht man das so. In C++ kann man andere Mechanismen wie o.g. 
zitierten Template-Mechanismus nutzen.

von A. S. (Gast)


Lesenswert?

Rahald schrieb:
> Danke, aber das 'sizeof(myStruct)' ist ungeil. Es muss ohne gehen.

Dann macht man das in C mit einem Makro:
1
#define myDoUpdate(s) DoUpdate(&s, sizeof(s))
2
3
   ...
4
   myDoUpdate(myStruct);
5
   ...
(Hier ein Makro, dass nicht in Großbuchstaben + _ gesetzt ist, da es wie 
eine Funktion gehandhabt wird. Auch das kann endlose Style-Kämpfe 
auslösen!)

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

A. S. schrieb:
> Dann macht man das in C

Wieso C, er hat doch schon C++...

An sich geht so etwas so:
1
template <typename T>
2
void update (const T& obj) {
3
   const char* raw = reinterpret_cast<const char*> (&obj);
4
   for (std::size_t i = 0; i < sizeof(T); ++i) {
5
       /* mache etwas mit den Daten */
6
       foo (raw [i]);
7
   }
8
}
9
10
DoUpdate (myStruct);
Man muss noch einen sinnvollen EEPROM-Zugriff einbauen. Portabel bzw. 
kompatibel ist das Format so natürlich nicht.

von Rahald (Gast)


Lesenswert?

Danke wieder viel gelernt. Templates z.B. :-) Diese Typenstrenge hat mir 
an anderen Stellen auch schon graue Haare gemacht.

Kompatibel ist der EEPROM-Zugriff wohl eh nicht. Das Allignment könnte 
auf anderen Plattformen anders sein.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Rahald schrieb:
> Kompatibel ist der EEPROM-Zugriff wohl eh nicht. Das Allignment könnte
> auf anderen Plattformen anders sein.

Ja, da könnte man mittels Serialisierung etwas gegen tun, falls 
gewünscht; da es ja aber anscheinend um den internen EEPROM des Arduino 
geht, wird man den wohl kaum ausbauen und an eine andere Plattform 
anschließen, weshalb Kompatibilität hier nicht so wichtig ist...

von Rahald (Gast)


Lesenswert?

Nur fürs Protokoll bzw. etwaige interessierte Nachwelt: Ich habe das 
jetzt so gemacht.
1
/*************************************************************/
2
/* EM_WriteAny                                               */
3
/*                                                           */
4
/* EM: Schreibt eine beliebige Datenstruktur in das EEPROM   */
5
/*                                                           */
6
/* iAdresse : Adresse der Speicherzelle                      */
7
/*************************************************************/
8
9
template <typename tAnyType> void EM_WriteAny(word iAdresse, tAnyType &AnyData) {
10
  byte *p = (byte*) &AnyData;
11
  for(word n=sizeof(tAnyType); n; n--, p++)
12
    {EEPROM.update(iAdresse++, *p);}
13
}
14
15
// Template-Muster aus EEPROM.h
16
// template< typename T > const T &put( int idx, const T &t ){
17
//   EEPtr e = idx;
18
//   const uint8_t *ptr = (const uint8_t*) &t;
19
//   for( int count = sizeof(T) ; count ; --count, ++e )  (*e).update( *ptr++ );
20
//   return t;
21
// }

Nur zur Dokumentation für mich selbst und weniger aus der Notwendigkeit 
(in EEPROM.h ist das update tatsächlich schon implementiert). Es ging 
mir auch noch darum, eine griffbereiten Vektor zu haben, wo Zugriffe 
aufs EEPROM zur Entwicklungszeit gepatcht werden können, damit das 
EEPROM nicht über Gebühr gequält wird.

von Rahald (Gast)


Lesenswert?

PS: ist noch nicht getestet, nur i.p. Allignment. Kann sein, dass die 
Daten rückwärts geschrieben werden oder anderes noch klemmt. Wenn das so 
ist, melde ich das noch nach.

von Rahald (Gast)


Lesenswert?

Nochwas: Die Notwendigkeit eines 'const' vor Datentypen ist mir noch 
nicht schlüssig (ich habe das const erstmal weggelassen). Wer dazu etwas 
schreiben will, würde mich erfreuen.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Rahald schrieb:
> byte *p = (byte*) &AnyData;

Nimm lieber reinterpret_cast<byte*> (...). Der Compiler macht da genau 
das gleiche draus, aber so ist dem Leser sofort klar, dass hier ein 
Low-Level-Zugriff geschieht. Das vereinfacht die Fehlersuche.

Alignment ist hier tatsächlich egal; char* hat keine 
Alignment-Anforderungen. Die AVR's haben außerdem sowieso generell 
keine.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Rahald schrieb:
> Nochwas: Die Notwendigkeit eines 'const' vor Datentypen ist mir noch
> nicht schlüssig

Dadurch wird die Variable konstant, man kann nicht schreibend darauf 
zugreifen. Falls du also versehentlich einen Schreibzugriff in die 
Funktion schreibst, gibt der Compiler dir sofort einen Fehler. Außerdem 
ist dem Leser der Funktion sofort klar, dass sie das referenzierte 
Objekt (struct) nicht verändert; hilft also bei der Fehlervermeidung und 
Lesbarkeit des Codes.

von Rahald (Gast)


Lesenswert?

Niklas G. schrieb:
> Nimm lieber reinterpret_cast<byte*> (...). Der Compiler macht da genau
> das gleiche draus, aber so ist dem Leser sofort klar, dass hier ein
> Low-Level-Zugriff geschieht. Das vereinfacht die Fehlersuche.

Das reinterpret_cast kenne ich noch nicht. Ich bin doch kein C++ Profi 
;)

von Rahald (Gast)


Lesenswert?

Niklas G. schrieb:
> Dadurch wird die Variable konstant, man kann nicht schreibend darauf
> zugreifen. Falls du also versehentlich einen Schreibzugriff in die
> Funktion schreibst, gibt der Compiler dir sofort einen Fehler. Außerdem
> ist dem Leser der Funktion sofort klar, dass sie das referenzierte
> Objekt (struct) nicht verändert; hilft also bei der Fehlervermeidung und
> Lesbarkeit des Codes.

Ja, so kannte ich das eigentlich auch.

Aber hier wird ptr z.B. verändert (*ptr++), trotz const. Habe ich was 
übersehen?
1
//   const uint8_t *ptr = (const uint8_t*) &t;
2
//   for( int count = sizeof(T) ; count ; --count, ++e )  (*e).update( *ptr++ );

von Einer K. (Gast)


Lesenswert?

Rahald schrieb:
> Habe ich was übersehen?

Ja!
Der Pointer zeigt auf einen konstanten Datenbereich.
Er selber ist nicht konstant.
1
const uint8_t * const ptr = (const uint8_t*) &t;
So ist auch der Pointer selbst konstant

von Rahald (Gast)


Lesenswert?

ok. Und wozu wird ein Datentyp als const deklariert ?
1
(const uint8_t*)

von Niklas Gürtler (Gast)


Lesenswert?

Rahald schrieb:
> Und wozu wird ein Datentyp als const deklariert ?

Damit der mit dem Datentyp versehene Wert - hier das Ziel des zu 
castenden Pointers - const ist.

von Axel S. (a-za-z0-9)


Lesenswert?

Rahald schrieb:
> @ A. S.
> Danke, aber das 'sizeof(myStruct)' ist ungeil. Es muss ohne gehen.

Nicht, wenn man einen void* verwenden will.

Rufus Τ. F. schrieb:
> In C macht man das so. In C++ kann man andere Mechanismen wie o.g.
> zitierten Template-Mechanismus nutzen.

Man könnte. Aber will man das wirklich? Denn so erzeugt der Compiler für 
jede verwendete struct eine Variante (sprich: ein Duplikat) der 
Funktion. Gerade in einem µC will das das eher nicht.

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Axel S. schrieb:
> Man könnte.

Ich schrieb ja auch kann, nicht muss. Embedded C++ sieht eh' anders 
aus als das unter Akademikern beliebte Wettrennen um den jeweils 
neuesten C++-Standard.

von Niklas Gürtler (Gast)


Lesenswert?

Axel S. schrieb:
> Denn so erzeugt der Compiler für jede verwendete struct eine Variante
> (sprich: ein Duplikat) der Funktion.

Diese kann man inline machen und darin nur die untypisierte Variante mit 
void* aufrufen. Dann braucht das so viel Speicher wie die C-Variante mit 
sizeof, ist aber fehlersicherer und einfacher.

Rufus Τ. F. schrieb:
> das unter Akademikern beliebte Wettrennen um den jeweils neuesten
> C++-Standard.

Gerade die neueren C++ Standards haben Vorteile für Embedded, 
insbesondere constexpr.

von A. S. (Gast)


Lesenswert?

Niklas Gürtler schrieb:
> Dann braucht das so viel Speicher wie die C-Variante mit sizeof, ist
> aber fehlersicherer und einfacher.

Wieso? Der Aufruf ist in beiden Fällen gleich, der Overhead (ein define) 
in C minimal.und auch wenn Makros sonst übel sind, wegen mehrfacher 
Auswertung der Argumente z.B., sehe ich hier kein wirkliches Problem.

von Niklas Gürtler (Gast)


Lesenswert?

Du hast es doch schon gesagt - die üblichen Probleme mit Makros. Die 
kann man mit dem Template vermeiden. Und da es 0 Overhead hat, spricht 
da auch nichts gegen.

von Axel S. (a-za-z0-9)


Lesenswert?

Niklas Gürtler schrieb:
> Axel S. schrieb:
>> Denn so erzeugt der Compiler für jede verwendete struct eine Variante
>> (sprich: ein Duplikat) der Funktion.
>
> Diese kann man inline machen und darin nur die untypisierte Variante mit
> void* aufrufen. Dann braucht das so viel Speicher wie die C-Variante mit
> sizeof, ist aber fehlersicherer und einfacher.

Hmm. Habe es gerade mal selber mit avr-g++ ausprobiert und in der Tat 
inlined der gleich die ganze (spezialisierte) Template-Funktion. 
Zumindest für eine ganz simple Funktion wie diese. Und schon in 
Optimierungsstufe -Os. C++ Compiler sind doch besser geworden. Ich 
sollte vielleicht mal wieder etwas mit C++ machen, statt immer nur C und 
Perl ;)

von Niklas Gürtler (Gast)


Lesenswert?

Die Optimierungs Algorithmen von GCC und G++ sind a auch logischerweise 
die selben...

von S. R. (svenska)


Lesenswert?

Niklas Gürtler schrieb:
> Die Optimierungs Algorithmen von GCC und G++
> sind a auch logischerweise die selben...

Besonders die, die mit Templates zu tun haben...

von Niklas Gürtler (Gast)


Lesenswert?

Die Optimierung kommt ja erst nach der Instanzierung von Templates ins 
Spiel.

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.