Struct als Argument (C++)

Gast #5665766
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?
Persönliche Seite #5665773
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.
Gast #5665786
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
Persönliche Seite #5665790
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.
Gast #5665798
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
    }
Gast #5665809
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.
Gast #5665823
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
}
Gast #5665862
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!)
Persönliche Seite #5665867
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.
Gast #5665898
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.
Persönliche Seite #5665902
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...
Gast #5665923
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.
Persönliche Seite #5665935
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.
Persönliche Seite #5665938
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.
Gast #5665944
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 
;)
Gast #5665948
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++ );
#5666069
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.
Gast #5666076
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.
Gast #5666093
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.
#5666189
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 ;)

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