Struct als Argument (C++)

Gast #5665766
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?
Persönliche Seite
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.
Gast
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
Persönliche Seite
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.
Gast
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
    }
Gast
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.
Gast
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
}
Gast
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!)
Persönliche Seite
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.
Gast
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.
Persönliche Seite
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...
Gast
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.
Gast
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.
Gast
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.
Persönliche Seite
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.
Persönliche Seite
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.
Gast
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 
;)
Gast
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++ );
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.
Gast
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.
Gast
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.
Gast
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.
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 ;)

Antwort schreiben

Eine eigene Frage? Dieser Thread ist über 7 Jahre alt. Stell sie in einem eigenen Thread – dort lesen mehr Leute mit, und sie wird öfter und schneller beantwortet.

Neuen Thread starten

Eine Lösung, Ergänzung oder Rückmeldung zu diesem Thread? Dann antworte hier.

Formatierung
  • [c]C-Code[/c]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] – Link zu einem Artikel
  • Verweis auf einen anderen Beitrag: Rechtsklick auf dessen Beitrags-ID (#8078628), „Adresse kopieren“, in den Text einfügen

Mehr zur Formatierung

Längeren Sourcecode als Anhang, nicht in den Text. JPG nur für Fotos und Scans; Zeichnungen und Screenshots als PNG oder GIF. Siehe Bildformate.

Schon registriert? Anmelden

Wird nicht angezeigt. Wir schicken dir einen Link; sobald du damit bestätigst, erscheint dein Beitrag.

Mit dem Abschicken legst du einen kostenlosen Account an und erkennst die Nutzungsbedingungen und die Datenschutzerklärung an.