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?
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.
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
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.
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.
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...
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.
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.
Wenn Du beliebige Strukturen irgendwo hinschreiben oder byteweise
verlgeichen willst, dann nimmt man einen (void *)-Pointer und gibt die
Länge an. Beispiel:
1
voidDoUpdate(void*p,intlen)/* hier kein int/size_t-Krieg am Anfang bitte */
2
{
3
unsignedchar*pc=(unsignedchar*)p;/* Pointer auf 1 Byte-Typ */
4
inti;
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
inta;
17
intb;
18
]myStruct;
19
20
voidmyFunc(void)
21
{
22
/* und verarbeitung. Du brauchst hier keinen Cast, wenn die Funktion void* erwartet! */
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.
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.
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...
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.
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.
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.
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.
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.
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
;)
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?
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.
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.
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.
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.
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.
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 ;)
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...