Forum: Compiler & IDEs AVR EEMEM Initialisierung C++14


von Thomas (Gast)


Angehängte Dateien:

Lesenswert?

Hallo Zusammen,

ich stolpere gerade über ein kleines Problem mit dem :
1
GNU C++14 (GCC) version 5.4.0 (avr)
2
  compiled by GNU C version 6.4.0 20170724, GMP version 6.1.2, MPFR version 3.1.5, MPC version 1.0.3
3
warning: MPFR header version 3.1.5 differs from library version 3.1.6.
4
5
-std=c++14

Ich versuche die Instanz einer Klasse in den EEMEM eines AVR zu legen.
Das .eep wird, bei vorhandensein von Methoden, mit Nullen angelegt.
Eigentlich sollte die Initialisierung im .eep stehen, wie auch bei jedem 
Struct denn es ist im Prinzip nichts anderes.

Die Instanz im RAM kann aber per Blockoperation ohne Probleme in das 
EEPROM gesichert und ausgelesen werden.

Interessant ist, das ein gleich aufgebautes Struct ein korrektes .eep 
erzeugt und ich dieses sogar in die Instanz der Klasse laden kann.
Natürlich ist das nicht besonders schön...


Ich habe ein kurzes "Testprogramm" mit Kommentaren angehängt.
Vielleicht findet jemand meinen Denkfehler, oder steckt da ein Fehler im 
Compiler ?

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Nimm ein POD.

Mit einer Klasse (die mehr ist als POD) kann sowas nicht funktionieren:
1
#include <type_traits>
2
3
static_assert (std::is_pod<DATAClass>::value, "...");

Und POD ist nur eine notwendige Bedingung, hinreichend ist POD nicht.

: Bearbeitet durch User
von Thomas W. (wagneth)


Lesenswert?

Entschuldige,

PlanOldData ?


Dann wäre es wohl sinnvoller ein Struct in die Klasse zu packen und 
darüber den Zugriff ab zu wickeln.


Aber warum sollte das nicht funktionieren ?
Eine Klasse unterscheidet sich von einem Struct doch nur um den this 
Zeiger ?
Die Instanz lässt sich bei meinen Versuchen in das EEProm sichern und 
auch wieder auslesen, quasi wie bei einem Struct.
Habe auch schon Zähler hochlaufen lassen etc.

Nur die Initialisierung per .eep funktioniert nicht.

von mh (Gast)


Lesenswert?

Thomas schrieb:
> Ich versuche die Instanz einer Klasse in den EEMEM eines AVR zu legen.
> Das .eep wird, bei vorhandensein von Methoden, mit Nullen angelegt.

Funktioniert es, wenn du keine Methoden in der Klasse hast?

Unabhängig davon:
1
while (1) {}
solltest du ändern, da undefined behaviour in C++.

von Thomas Wagner (Gast)


Lesenswert?

Genau,
Ohne Methoden stimmt das .eep.

Vermutlich wird dann auch class als struct behandelt.
Schreibe ich Methoden in das struct geht es  wieder schief.

Ich vermute das hier die Fallunterscheidung greift.

Aus meiner sicht mache ich doch nichts anderes als variablen zwischen 
ram und eemem zu verschieben. Das scheint ja auch zu funktionieren.
Speicherobjekte muss ich doch auch zwischen ram heap und stack kopieren 
können.
So wie es aussieht funktioniert das auch.

Nur wird die eemem sektion bei der klasseninstanz nicht verarbeitet.

von Rolf M. (rmagnus)


Lesenswert?

Thomas W. schrieb:
> Entschuldige,
>
> PlanOldData ?

Ja.

> Dann wäre es wohl sinnvoller ein Struct in die Klasse zu packen und
> darüber den Zugriff ab zu wickeln.

Struct und Klasse sind in C++ das gleiche.

> Aber warum sollte das nicht funktionieren ?

Ich vermute, sobald du einen Konstruktor definierst, wird die 
Initialisierung anders gemacht.

> Eine Klasse unterscheidet sich von einem Struct doch nur um den this
> Zeiger ?

Nein. Der this-Zeiger hat damit überhaupt nichts zu tun. Der einzige 
Unterschied zwischen Klasse und Struct sind die default-Zugriffsrechte 
und Vererbung. Die sind bei Klassen private und bei Struct public.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Thomas W. schrieb:
> PlanOldData ?

plaIn old data

> Dann wäre es wohl sinnvoller ein Struct in die Klasse zu packen und
> darüber den Zugriff ab zu wickeln.

Und wie soll das funktionieren?  Du kannst nicht einen Teil der Struktur 
im EEprom haben und einen Teil im RAM.


> Aber warum sollte das nicht funktionieren ?

Ei weil EEMEM nur die Ablage des Codes regelt, aber nicht der Zugriff. 
Wenn du zum Beispiel Code bastelst — wie oben geschehen — der die 
Struktur zur Laufzeit initialisiert (ausgeführt in einem impliziten, 
statischen Konstruktor — oder wie auch immer C++ sowas nennt, wenn es 
denn überhaupt einen Namen dafür hat) dann schreibt dieser Code ins RAM 
anstatt den EEprom zu initialisieren.

Jeglicher Schreib- oder Lesezugriff muss via der eeprom_-Funktionen aus 
avr/eeprom.h (oder Entsprechungen davon) erfolgen.  Du kannst nicht g++ 
verwenden, um diese Zugriffe für dich generieren zu lassen.

Wenn du sicherstellen willst, dass du nicht versehentlich C++-Features 
mit EEMEM oder PROGMEM kombinierst, die nicht kompatibel mit C++ sind, 
dann pack alles EEMEM und PROGMEM Zeugs in C-Module, übersetz diese mit 
avr-gcc anstatt avr-g++, und schreib Interfaces, welche die Zugriffe 
abwickeln.

> Die Instanz lässt sich bei meinen Versuchen in das EEProm sichern und
> auch wieder auslesen, quasi wie bei einem Struct.

Gründe deine Programmierung auf Wissen über die Sprache, nicht auf 
"Versuche", die mal "funktioniert" haben.

Aus dem Sprachstandard kann man auf ein Progamm schließen, aber aus 
einem in einer konkreten Situation funktionierenden Binärcode lässt sich 
NICHT schließen, dass man gültige Konstrukte benutzt hat.

Ich kenne keinen anderen Denkfehler, der in der Programmierung auch nur 
ansatzweise so häufig gemacht wird wie dieser nicht-gültige 
Umkehrschluss.

: Bearbeitet durch User
von Kaj G. (Firma: RUB) (bloody)


Lesenswert?

mh schrieb:
> Unabhängig davon:while (1) {}
> solltest du ändern, da undefined behaviour in C++.
Koenntest du das mal naeher erklaeren, eventuell mit einer Verlinkung 
auf die passende Stelle im C++ Standard?

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Kaj G. schrieb:
> mh schrieb:
>> Unabhängig davon:while (1) {}
>> solltest du ändern, da undefined behaviour in C++.
> Koenntest du das mal naeher erklaeren, eventuell mit einer Verlinkung
> auf die passende Stelle im C++ Standard?
1
Progress guarantee
2
3
In a valid C++ program, every thread eventually does one of the following:
4
5
*  terminate 
6
*  makes a call to an I/O library function
7
*  performs an access through a volatile glvalue
8
*  performs an atomic operation or a synchronization operation 
9
10
No thread of execution can execute forever without performing any of
11
these observable behaviors.

von mh (Gast)


Lesenswert?

Kaj G. schrieb:
> mh schrieb:
>> Unabhängig davon:while (1) {}
>> solltest du ändern, da undefined behaviour in C++.
> Koenntest du das mal naeher erklaeren, eventuell mit einer Verlinkung
> auf die passende Stelle im C++ Standard?

Aus dem C++17 draft (N4687)
1
4.7.2 Forward progress [intro.progress]
2
The implementation may assume that any thread will eventually do one of the following:
3
(1.1) — terminate,
4
(1.2) — make a call to a library I/O function,
5
(1.3) — perform an access through a volatile glvalue, or
6
(1.4) — perform a synchronization operation or an atomic operation.
7
[ Note: This is intended to allow compiler transformations such as removal of empty loops, even when
8
termination cannot be proven. — end note ]

"may assume" = alles andere ist UB. Das gleiche oder zumindest etwas 
sehr ähnliches sollte auch in c++11 und c++14 stehen.

von Kaj G. (Firma: RUB) (bloody)


Lesenswert?

Okay. Das UB ist also die leere Schleife? Interessant. :0

von mh (Gast)


Lesenswert?

Nein! Es ist UB, weil es eine Endlosschleife ohne Seiteneffekte ist.

von mh (Gast)


Lesenswert?

C11 sagt übrigens etwas sehr ähnliches, macht aber eine Ausnahme für 
"while(1)" und co.

Aus dem C11 draft (N1570)
1
6.8.5 Iteration statements
2
...
3
6) An iteration statement whose controlling expression is not a constant expression, that performs no input/output operations, does not access volatile objects, and performs no synchronization or atomic operations in its body, controlling expression, or (in the case of a for statement) its expression-3, may be assumed by the implementation to terminate.

von Kaj G. (Firma: RUB) (bloody)


Lesenswert?

mh schrieb:
> Nein! Es ist UB, weil es eine Endlosschleife ohne Seiteneffekte ist.
genau das meinte ich ja, nur schlecht ausgedrueckt.

von Thomas W. (wagneth)


Lesenswert?

Johann L. schrieb:
> Thomas W. schrieb:

>> Aber warum sollte das nicht funktionieren ?
>
> Ei weil EEMEM nur die Ablage des Codes regelt, aber nicht der Zugriff.
> Wenn du zum Beispiel Code bastelst — wie oben geschehen — der die
> Struktur zur Laufzeit initialisiert (ausgeführt in einem impliziten,
> statischen Konstruktor — oder wie auch immer C++ sowas nennt, wenn es
> denn überhaupt einen Namen dafür hat) dann schreibt dieser Code ins RAM
> anstatt den EEprom zu initialisieren.

Ich vermute da habe ich Dir mein Problem nicht richtig erklärt :

1. Eine Instanz wird im RAM angelegt :
1
DATABLOCK dataRAM;

2. Eine Instanz wird im EEMEM angelegt, natürlich lässt sich die nicht 
"benutzen"...
1
DATABLOCK EEMEM dataEEP;
3. Jetzt muss ich doch den Inhalt von dataRAM <> dataEEP in beide 
Richtungen kopieren können.
Dazu muss ich doch nur deren "Speicherobjekte" im Block schreiben oder 
lesen.
1
eeprom_update_block  (&dataRAM, &dataEEP, sizeof (DATABLOCK));
2
3
eeprom_read_block  (&dataRAM, &dataEEP, sizeof (DATABLOCK));


Das muss doch im Sprachstandard von C++ erlaubt sein?
Die Sprache kann mir doch nicht den direkten Zugriff auf Speicherobjekte 
"verbieten"?
Die ganzen LIBs wie Boost etc. müssen unter der Haube doch auch solche 
Konstrukte verwenden.

...und das funktioniert in meinem Test auch.
Vielleicht wäre es mit Copy Constructor schöner anzusehen aber mir ging 
es nur darum das Problem möglichst einfach zu umreissen.


Das einzige Problem was ich jetzt habe ist das bei einem blanken Struct 
eine korrekte .eep für die Vorinitialisierung des EEMEM erzeugt wird.
Bei einer Klasseninstanz geht es schief, da stehen nur Nuller aber es 
wird wenigstens der richtige Speicherbereich im EEMEM reserviert.

Ich denke nicht dass das .eep verhalten im C++ Standard festgehalten ist 
?

>> Die Instanz lässt sich bei meinen Versuchen in das EEProm sichern und
>> auch wieder auslesen, quasi wie bei einem Struct.
>
> Gründe deine Programmierung auf Wissen über die Sprache, nicht auf
> "Versuche", die mal "funktioniert" haben.

Ich lege kein "Versuch und Fehler" Taktik an den Tag.
Es ist nur der Versuch herauszufinden was der Compiler macht, denn auch 
er hat Spielraum.
Besonders bei einer Implementierung wie auf dem AVR.


Ich habe auch erfolgreich versucht aus dem Struct, was ja ein valides 
.eep erzeugt, in das RAM Objekt zu lesen.
Aber das wäre eindeutig so ein Ding was unter "Dirty hack" fällt und ich 
aus den Gründen die Du in Deinem letzten Absatz beschreibst nicht 
machen würde...

von Thomas W. (wagneth)


Lesenswert?

Johann L. schrieb:
> Nimm ein POD.
>
> Mit einer Klasse (die mehr ist als POD) kann sowas nicht funktionieren:
>
>
1
> #include <type_traits>
2
> 
3
> static_assert (std::is_pod<DATAClass>::value, "...");
4
>
>
> Und POD ist nur eine notwendige Bedingung, hinreichend ist POD nicht.

Ahhh, damit gibst Du mir das Werkzeug zum Testen in die Hand.

von Carl D. (jcw2)


Lesenswert?

C++ kennt die AVR-Spielchen mit mehreren Adressräumen nicht. Der GCC 
hilft ein bischen, indem er über Attribute die Adressräume zu trennen 
erlaubt und die AVR-binutils diese dann in getrennte Files ablegen.
Bei Flash gibt es noch den Trick, daß die Lesefunktionen auf speziellen 
Assemblerbefehlen beruhen, die wie auch immer 
(Compiler-Bachend/AVRlibc/...) im Befehlstrom landen.
Für das initialisieren einer EEProm-Klasse müßte das Backend schreibend 
auf den EeProm-Bereich zugreifen (das wird bei Flash auch nicht 
unterstützt), was rein mit Mitteln des Instruction-Set nicht möglich 
ist. Dazu müßte es Library-Unterstützung geben, die aber, anders als 
"multipliziere 2 int's", nicht nur von der CPU-Architektur abhängig 
wäre, sonder von explizitem Chip. All diese Chip-spezifischen Dinge sind 
in der AVRLibc versteckt, z.B. EEPron-Zugriffe.
Was der Compiler macht, ist die Adresse in dem alternativen Adressraum 
auf Anfrage (&) rauszurücken.
Ich würde die EEProm-Struktur wie gehabt anlegen (mit Initialwerten, die 
in .eep wandern) und eine "Proxy"-Klasse verwenden, die die Adresse der 
EEProm-Klasse im Konstruktor übergeben bekommt. Damit kann sie dann 
Lesen/Schreiben und bei Bedarf auch in einem lokalen Cache halten. Wenn 
man die EEProm-Daten tatsächlich als Struktur ablegt, was auch für ein 
bekanntes Layout EEProm sorgt, wenn man das direkt beackern will, dann 
ist der "eine Adresse" Overhead verkraftbar. Oder man legt die 
EEProm-Daten gleich an fixe Adressen, dann darf die EEPromCache-Klasse 
auch ein Template mit Parametern "Typ" und "Adresse" sein.

von mh (Gast)


Lesenswert?

Thomas W. schrieb:
> Das muss doch im Sprachstandard von C++ erlaubt sein?
> Die Sprache kann mir doch nicht den direkten Zugriff auf Speicherobjekte
> "verbieten"?
> Die ganzen LIBs wie Boost etc. müssen unter der Haube doch auch solche
> Konstrukte verwenden.
Das muss nicht erlaubt und du musst keinen direkten Zugriff haben. Was 
passiert mit dem Destruktor wenn du einfach so ein Objekt überkopierst? 
Was ist mit geschützten (private) Datamembern?

Thomas W. schrieb:
> Ich lege kein "Versuch und Fehler" Taktik an den Tag.
> Es ist nur der Versuch herauszufinden was der Compiler macht, denn auch
> er hat Spielraum.
Es bleibt "Versuch und Fehler" solange du Versuchst herauszufinden was 
der Compiler macht. Du musst herausfinden was der Standard sagt.

von Thomas W. (wagneth)


Lesenswert?

Carl,

ich vermute jetzt hat es bei mir klick gemacht, danke !!!

In meiner Naiven Sichtweise ist eine Klasseinstanz nur eine Ansammlung 
von Variablen auf die halt über "Sicherungsmechanismen" zugegriffen 
wird.
Das entspricht der Erfahrung mit dem Struct das ich in die Instanz laden 
konnte. (Sehr schlecht, sollte man unter keinen Umständen machen)

Der vom Compiler gebaute Standardkonstruktor initialisiert bei der 
Erzeugung alle Variablen wie ich sie in der Klassendefinition vorbelegt 
habe.
Das klappt im RAM aber nicht im EEPROM, soweit klar.
Aber vom Prinzip her wäre es doch ein Const Objekt und der "Konstruktor" 
könnte in die .eep schreiben... ;)
(Was derzeit nur bei einem POD gemacht wird)

Bitte nicht falsch verstehen,
ich will die Instanz nicht im EEPROM verwenden, nur speichern.
Welche Regel sollte mir das verbieten ?

Wenn die Vorinitialisierung per .eep nicht unterstützt wird, so muss 
doch mein Speichern und Laden "vom Standard gedeckt" sein.


mh schrieb:
> Das muss nicht erlaubt und du musst keinen direkten Zugriff haben. Was
> passiert mit dem Destruktor wenn du einfach so ein Objekt überkopierst?
> Was ist mit geschützten (private) Datamembern?


Ich denke das ich mich dann direkt darum kümmern muss.
Es geht mir hier Hauptsächlich um Kapselung und den Konstruktor.
Aber auf einem AVR sind bei mir 99% meiner Destruktoren leer.
Der ganze Käse muss Initialisert werden aber ein loslassen von Resourcen 
gibt es da nicht.

...und wenn ich in C++17 keine Instanzen mehr Kopieren darf,
dann will ich das nicht haben.

Wofür legt man denn einen CopyConstructor an ?
Dort beschreibe ich wie ein Objekt von einem Speicherbereich in den 
anderen kopiert wird.
Das macht am ende immer eine Speicherroutine wie memcpy oder z.b. 
eeprom_read_block, ausserdem kann ich dann noch die ID hochzählen.

Oder stimmt das nicht ?
In den grossen Libs wird das doch auch ständig gemacht !?

Dem C++ ist es doch egal wo die Daten in der Maschine gespeichert sind.
Ich wette man darf ein Objekt (dessen Daten) auch in eine Datei 
speichern ?

Ich will nicht als Beratungsressistent erscheinen, will es nur 
verstehen.


> Es bleibt "Versuch und Fehler" solange du Versuchst herauszufinden was
> der Compiler macht. Du musst herausfinden was der Standard sagt.

Bis jetzt ist es Forschung, wenn ich es im Projekt einsetzte dann ist 
es... ;)

Aber zeig mir mal die Stelle im C++ Standard die das kopieren eines 
Objekt aus einem anderen Speicher(bereich) verbietet.
Ich bin doch derjenige der den CopyConstructor schreibt bzw. absichtlich 
=0 setzt ?!?

von Wilhelm M. (wimalopaan)


Lesenswert?

Man will ja typischerweise auch nicht bei jedem(!) Zugriff der Firmware, 
dass alles ins EEProm zurückgeschrieben oder neu gelesen wird. Also habe 
ich dafür ein Template, was parametriert wird mit dem DT, dessen Daten 
ins EEProm persistiert werden sollen, mit dem konkreten µC, etc. Dieses 
Template agiert im wesentlichen als Cache, und schreibt auch die Daten 
wenn nötig in definierten, zeitlichen Abständen ins EEProm zurück. Damit 
bin ich alle Sorgen los.

Die Initialisierung des EEProms mache ich entweder durch einmaliges 
Starten der Firmware in einem besonderen Modus (könnte ja sein, das an 
der Peripherie auch mal einmalig was gemacht werden muss), oder ich 
"verlasse" mich darauf, dass im Auslieferungszustand der µC 0xff drin 
steht, und schreibe anschließend eine magic number bspw. an den Anfang 
des EEproms. Dies ist auch ein generation-stamp, damit man 
unterschiedliche Versionen verwalten kann ...

von Thomas W. (wagneth)


Lesenswert?

Das ich kein .eep bekomme, damit kann ich mich abfinden.

Vielleicht hat noch jemand einen Grund der gegen das Kopieren in / aus 
dem EEMEM spricht.
Ich sehe gerade keinen.

@ Wilhelm M. :

Wie sähe das denn aus ?

Mein naiver Ansatz war Snapshots mit einer ID über das ganze eeprom zu 
verteilen.
Nach jedem Copy wird die ID erhöht, so finde ich den letzten Datensatz.
Geschrieben wird nur auf Anfrage von aussen oder bei Abfall der 
Spannung.
Ich wollte nicht jeden einzelnen Wert aktualisieren.

Eigentlich soll es im EEMEM zwei Datenbereiche geben.
(Parametrierung und Telemetrie)

PS zu mh :
Es gibt in C++ auch verschiedene Möglichkeiten das Private aufzuweichen.
Mit fallen da protected, friend und static ein.
(und static ist ne ganz üble Nummer)

von Wilhelm M. (wimalopaan)


Lesenswert?

Thomas W. schrieb:

> PS zu mh :
> Es gibt in C++ auch verschiedene Möglichkeiten das Private aufzuweichen.
> Mit fallen da protected, friend und static ein.

Wozu sollte das nötig sein?

> (und static ist ne ganz üble Nummer)

Was hat static mit Sichtbarkeit zu tun?

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Thomas W. schrieb:
> Johann L. schrieb:
>> Thomas W. schrieb:
>
>>> Aber warum sollte das nicht funktionieren ?
>>
>> Ei weil EEMEM nur die Ablage des Codes regelt, aber nicht der Zugriff.
>> Wenn du zum Beispiel Code bastelst — wie oben geschehen — der die
>> Struktur zur Laufzeit initialisiert (ausgeführt in einem impliziten,
>> statischen Konstruktor — oder wie auch immer C++ sowas nennt, wenn es
>> denn überhaupt einen Namen dafür hat) dann schreibt dieser Code ins RAM
>> anstatt den EEprom zu initialisieren.
>
> Ich vermute da habe ich Dir mein Problem nicht richtig erklärt :
>
> 1. Eine Instanz wird im RAM angelegt :
>
1
> DATABLOCK dataRAM;
2
>
>
> 2. Eine Instanz wird im EEMEM angelegt, natürlich lässt sich die nicht
> "benutzen"...
>
1
> DATABLOCK EEMEM dataEEP;
2
>

Es wird also ein Constructor "ausgeführt".

Wann wird dieser "ausgeführt"? D.h. wie sorgt die abstrakte Maschine 
dafür, dass dataEEP beim Betreten von main mit den geforderten 
Initalwerten belegt ist? Wir wissen es nicht.  Es kann zur Load-Time 
sein oder zur Laufzeit.  Ich bin wahrlich kein C++ Experte, aber ich 
kann mir schwerlich vorstellen, dass C++ diese Konzepte überhaupt kennt 
oder gar eine Aussage bzw. Zusicherung darüber macht.  Selbst wenn alle 
Komponenten physisch mit 0 initialisiert werden darf eine Implementation 
das zur Laufzeit initialisieren — ich lass mich aber gerne eines 
besseren belehren.

> 3. Jetzt muss ich doch den Inhalt von dataRAM <> dataEEP in beide
> Richtungen kopieren können.

Nur falls std::is_trivially_copyable erfüllt ist.

> Das muss doch im Sprachstandard von C++ erlaubt sein?

Es geht hier doch nicht nur um C++ sondern auch um attribute "section" 
(hier konkret EEMEM) und dass dieses Attribut den Code in einen Bereich 
mit Eigenschaften legt, die sich nicht in C++ formulieren lasse.

> Die Sprache kann mir doch nicht den direkten Zugriff auf Speicherobjekte
> "verbieten"?

EEMEM gehört nicht zu C++ :-)

> Die ganzen LIBs wie Boost etc. müssen unter der Haube doch auch solche
> Konstrukte verwenden.

Nein, Boost unterstützt bestimmt nicht sowas wie attribute((section)) 
und bastelt dir entsprechende Zugriffe oder garantiert dir, dass 
Speicher ausschließlich zur Load-Time angefasst wird, etc.

> ...und das funktioniert in meinem Test auch.

Wie gesagt:  Das ist keine Garantie dafür, dass es korrekt ist.  Ob 
das funktioniert kann z.B: vom Optimierungen abhängen: Wenn der Compiler 
sieht, dass er ertwas nicht in einem statischen Constructor abhandeln 
muss, dann dar er dass zur Load-Time machen.  Aber er MUSS NICHT.

Noch mal: "Ich hab XXX ausprobiert und es ging" ist KEIN Argument :-)

> Copy Constructor schöner anzusehen

Vergiss (schönes) C++.  Bei Sachen wie EEMEM lässt es dich im Regen 
stehen.

> Das einzige Problem was ich jetzt habe ist das bei einem blanken Struct
> eine korrekte .eep für die Vorinitialisierung des EEMEM erzeugt wird.

> Ich denke nicht dass das .eep verhalten im C++ Standard festgehalten ist
> ?

Korrekt.

Thomas W. schrieb:
> Johann L. schrieb:
>> Nimm ein POD.
>>
>> Mit einer Klasse (die mehr ist als POD) kann sowas nicht funktionieren:
>>
>>
1
>> #include <type_traits>
2
>>
3
>> static_assert (std::is_pod<DATAClass>::value, "...");
4
>>
>>
>> Und POD ist nur eine notwendige Bedingung, hinreichend ist POD nicht.
>
> Ahhh, damit gibst Du mir das Werkzeug zum Testen in die Hand.

Nein.  Das wäre der Fall, wenn ich "hinreichend" geschrieben hätte.

https://de.wikipedia.org/wiki/Notwendige_und_hinreichende_Bedingung

Zudem ist is_pod ab C++20 deprecated.  Zwar kann man es ersetzen durch 
is_trivial && is_standard_layout, aber das genügt eben nicht.

Ich finde auch keine Trait, die wirklich passen würde und strikt genug 
wäre.   Etwa könnte ein Struct eine nicht-statische Methode haben und 
würde immer noch POD sein.  Und auch wenn is_trivially_copyable erfüllt 
ist, funktionieren Zuweisungen mit = eben NICHT. Und wenn es ein 
Initializer ist, steht der C++ Implementation frei, diesen zu Laufzeit 
abzuhandeln [*].  Was hier nicht nur dazu führt, dass der struct nicht 
initialisiert wird, sondern auch dazu, dass in den RAM / SFR-Bereich 
Schrott geschrieben wird :-)

Wenn also ein Datum im Static Storage physisch mit 0 initialisiert wird, 
ist dann die Implementation dazu verpflichtet, diese zur Load-Time 
abzuhandeln?  Ich denke nicht [*], d.h. selbst wenn dein .eep wie 
gewünscht erzeugt wird, ist dein Programm Schrott :-)

[*] Mit C++ kenne mich aber wie gesagt nicht genug aus und lasse mich da 
gerne eines Besseren belehren.

: Bearbeitet durch User
von Kaj G. (Firma: RUB) (bloody)


Lesenswert?

Thomas W. schrieb:
> ich will die Instanz nicht im EEPROM verwenden, nur speichern.
Warum willst du denn die Instanz selber speichern? Reicht es nicht nur 
die Werte der Attribute zu speichern?

Wie waere es mit serialisierung?
https://isocpp.org/wiki/faq/serialization
1
What’s this “serialization” thing all about?
2
3
It lets you take an object or group of objects, put them on a disk or
4
send them through a wire or wireless transport mechanism, then later,
5
perhaps on another computer, reverse the process: resurrect the original
6
object(s). The basic mechanisms are to flatten object(s) into a
7
one-dimensional stream of bits, and to turn that stream of bits back
8
into the original object(s).
9
10
Like the Transporter on Star Trek, it’s all about taking something
11
complicated and turning it into a flat sequence of 1s and 0s, then
12
taking that sequence of 1s and 0s (possibly at another place, possibly
13
at another time) and reconstructing the original complicated “something.”

https://de.wikipedia.org/wiki/Serialisierung
1
Die Serialisierung ist in der Informatik eine Abbildung von
2
strukturierten Daten auf eine sequenzielle Darstellungsform.
3
...
4
Übliche Speichermedien sind nur in der Lage, Datenströme zu speichern.
5
Um Persistenz für ein Objekt zu erreichen, kann es serialisiert werden.
6
Hier wird der komplette Zustand des Objektes, inklusive aller
7
referenzierten Objekte, in einen Datenstrom umgewandelt, der
8
anschließend auf ein Speichermedium geschrieben wird.
Koennte dir das helfen?

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

Wenn man das so macht wie ich oben beschrieben habe, fällt auch die POD 
Forderung weg.

Entweder gibt man seinem EEProm-Typ einen Typumwandlungs-Ctor von etwa 
std::array<std::byte, sizeof(DT)>: dieses Array muss natürlich schon im 
RAM liegen, d.h. man muss es vorher durch plattformspezifische 
Funktionen wie etwa eeprom_read_block(...) füllen, oder man benutzt ein 
placement-new, oder, da man in C++ ja über uchar* <-> type* ein Objekt 
"füllen" kann ohne UB zu bekommen, schreibt man die Repräsentation des 
Objektes irgendwo ins RAM und castet (Vorsicht, dass man keine 
trap-representation dabei generiert).

von mh (Gast)


Lesenswert?

Thomas W. schrieb:
> Aber auf einem AVR sind bei mir 99% meiner Destruktoren leer.
Warum schreibst du dann den Destroktor?

Thomas W. schrieb:
> ...und wenn ich in C++17 keine Instanzen mehr Kopieren darf,
> dann will ich das nicht haben.
>
> Wofür legt man denn einen CopyConstructor an ?
> Dort beschreibe ich wie ein Objekt von einem Speicherbereich in den
> anderen kopiert wird.
Ja genau dafür ist ein CopyConstructor da, also schreibe ihn.

Thomas W. schrieb:
> In den grossen Libs wird das doch auch ständig gemacht !?
Hast du ein Beispiel wo das genau so gemacht wird?

Thomas W. schrieb:
> Bis jetzt ist es Forschung, wenn ich es im Projekt einsetzte dann ist
> es... ;)
>
> Aber zeig mir mal die Stelle im C++ Standard die das kopieren eines
> Objekt aus einem anderen Speicher(bereich) verbietet.
Wenn es deine Forschung ist, solltest auch du im Standard nachlesen, ob 
es erlaubt ist.

> Ich bin doch derjenige der den CopyConstructor schreibt bzw. absichtlich
> =0 setzt ?!?
Du hast (in deinem Beispiel) keinen CopyConstructor geschrieben und auch 
nicht =0 gesetzt (was auch immer das bedeuten mag)!?

Thomas W. schrieb:
> PS zu mh :
> Es gibt in C++ auch verschiedene Möglichkeiten das Private aufzuweichen.
> Mit fallen da protected, friend und static ein.
> (und static ist ne ganz üble Nummer)
Kannst du das etwas genauer erklären?



Nicht weil du so nett gefragt hast, sonder weil es mich selbst 
interessiert, habe ich 5 Minuten investiert, um im Standard (C++17 Draft 
N4687) zu lesen.
1
6.8 Object lifetime [basic.life]
2
...
3
5) A program may end the lifetime of any object by reusing the storage which the object occupies or by explicitly calling the destructor for an object of a class type with a non-trivial destructor. For an object of a class type with a non-trivial destructor, the program is not required to call the destructor explicitly before the storage which the object occupies is reused or released; however, if there is no explicit call to the destructor or if a delete-expression (8.3.5) is not used to release the storage, the destructor shall not be implicitly called and any program that depends on the side effects produced by the destructor has undefined behavior.

Auf den ersten Blick ist es also erlaubt, das ganze Objekt zu kopieren 
(mit der expliziten Erlaubnis sich in den Fuss zu schießen). Andere 
Stellen des Standards sind sicherlich auch relevant für die Frage, aber 
ich keinen Grund gefunden der gegen das Kopieren spricht.

von Carl D. (jcw2)


Lesenswert?

Thomas W. schrieb:
>
> Bitte nicht falsch verstehen,
> ich will die Instanz nicht im EEPROM verwenden, nur speichern.
> Welche Regel sollte mir das verbieten ?

Bitte richtig verstehen, ich sage (mit meinen Worten und eventuell 
vereinfacht) genau das gleiche, was Johan und Wilhelm auch sagen. C++ 
kennt die AVR-Hardware-Besonderheiten nicht und wird sie auch nicht 
kennenlernen, da "modernere" HW versucht, alle Datenarten in einen 
Adressraum zu mappen.

Mach einen "cachenden Proxy", der mit der EEProm-Adresse deiner Struktur 
initialisiert. Oder falls die Adresse constexpr ist, was eine 
EEProm-Adresse eventuell nicht ist, wegen Link-Step, benutze ein 
Template, dann steckt die Adresse im Typ und braucht keinen 
Speicherplatz im RAM. Notfalls macht man das EEProm Layout von Hand, 
dann ist die Strukturadresse constexpr ;-)

Ich verstehe dein Problem sehr gut, weil ich genau an der selben Stelle 
nach einer eleganten Lösung suche. Allerdings ohne Termindruck, also 
niedrige Prio.

von Wilhelm M. (wimalopaan)


Lesenswert?

Carl D. schrieb:
> Thomas W. schrieb:
>>
>> Bitte nicht falsch verstehen,
>> ich will die Instanz nicht im EEPROM verwenden, nur speichern.
>> Welche Regel sollte mir das verbieten ?
>
> Bitte richtig verstehen, ich sage (mit meinen Worten und eventuell
> vereinfacht) genau das gleiche, was Johan und Wilhelm auch sagen. C++
> kennt die AVR-Hardware-Besonderheiten nicht und wird sie auch nicht
> kennenlernen, da "modernere" HW versucht, alle Datenarten in einen
> Adressraum zu mappen.

Richtig. Wobei das nichts mit moderner HW zu tun hat, sondern damit, 
dass C++ HW-agnostisch ist. So etwas kann also nicht Bestandteil des 
Sprachkerns sein.

Mein Ansatz ist wie gesagt recht simpel: ich modelliere das (gesamte) 
EEProm als einen heterogenen Container: entweder als eine 
Aggregat-Klasse mit unterschiedlichen Elementen für unterschiedliche 
Aufgaben der Firmware, oder gleich als std::tuple<>. Dann gibt es ein 
generisches Cache-template, was mit diesem Typ parametriert wird. Das 
kümmert sich dann um die Serialisierung: und dies ist dann 
plattform-spezifisch (bei avr kann man dann eeprom-update-block(), ... 
benutzen).

von Thomas W. (wagneth)


Lesenswert?

Entschuldigt meine Späte Antwort ich hatte viel zu lesen.

Ich vermute ich weiss jetzt wer der Johann ist... 8)


>Aus dem Sprachstandard kann man auf ein Progamm schließen, aber aus
>einem in einer konkreten Situation funktionierenden Binärcode lässt sich
>NICHT schließen, dass man gültige Konstrukte benutzt hat.
>
>Ich kenne keinen anderen Denkfehler, der in der Programmierung auch nur
>ansatzweise so häufig gemacht wird wie dieser nicht-gültige
>Umkehrschluss.

Das muss(te) ich mir erst verinnerlichen, da ich, das bisschen, was ich 
über C++ weiss nicht aus dem Standard gelernt habe ging ich da auch 
blauäugig ran.

Ist schon interessant wie sich (m)ein Weltbild verändern kann.
Dank euch verstehe ich jetzt ein bisschen mehr, danke!

> Die ganzen LIBs wie Boost etc. müssen unter der Haube doch auch solche
> Konstrukte verwenden.

>Nein, Boost unterstützt bestimmt nicht sowas wie attribute((section))
>und bastelt dir entsprechende Zugriffe oder garantiert dir, dass
>Speicher ausschließlich zur Load-Time angefasst wird, etc.

Ich habe den Source durchgeackert. (grep etc) Ich konnte bei Boost jede 
Menge memcpy auf POD Daten finden aber nicht einmal auf eine Klasse !

>[*] Mit C++ kenne mich aber wie gesagt nicht genug aus und lasse mich da
>gerne eines Besseren belehren.

Das glaube ich Dir nicht;)

mh
>Nein! Es ist UB, weil es eine Endlosschleife ohne Seiteneffekte ist.

Also wenn ich das richtig verstehe würde es schon reichen wenn die 
(leere-)Schleife Terminieren würde.
Zb vergleich gegen eine Volatile Variable.
Oder im Rumpf ein Effekt nach aussen auftritt.

> In den grossen Libs wird das doch auch ständig gemacht !?
>Hast du ein Beispiel wo das genau so gemacht wird?

Hab nachgeschaut, Boost nutzt es nur für POD.

Es ist schade das es keine libC++ gibt.
(Ja, ich weiss, selber schreiben, aber ich habe nicht die Fähigkeiten 
dafür...)

>> Ich bin doch derjenige der den CopyConstructor schreibt bzw. absichtlich
>> =0 setzt ?!?
>Du hast (in deinem Beispiel) keinen CopyConstructor geschrieben und auch
>nicht =0 gesetzt (was auch immer das bedeuten mag)!?

Naja, wenn man ihn pure virtual macht (=0) kann man ihn nicht nutzen.
Der Compiler müsste abbrechen.

>> PS zu mh :
>> Es gibt in C++ auch verschiedene Möglichkeiten das Private aufzuweichen.
>> Mit fallen da protected, friend und static ein.
>> (und static ist ne ganz üble Nummer)
>Kannst du das etwas genauer erklären?


In einer Klassendefinition führt static dazu das die Variable zwischen 
allen Instanzen der Klasse geteilt wird.
Inkrementiert man im CTor und dekrementiert im DTor, könnte man 
mitzählen wieviele Instanzen der klasse existieren.
Ich behaupte wenn eine static Variable im private Teil steht weicht dass 
das Private auf.
Den eine andere Instanz bekommt Zugriff auf das Datum.

Friend erlaubt einer anderen Methode den Zugriff auf die Variablen einer 
Instanz als würde sie in der Klasse stehen.
Also ist wieder das privat unterwandert.
Ich nutze das um aus einer ISR an die Variablen einer Klasseninstanz zu 
kommen.

Protected ist vielleicht nicht das beste Beispiel.

>Nicht weil du so nett gefragt hast, sonder weil es mich selbst
>interessiert, habe ich 5 Minuten investiert, um im Standard (C++17 Draft
>N4687) zu lesen.

Entschuldige!!!

>> Aber auf einem AVR sind bei mir 99% meiner Destruktoren leer.
>Warum schreibst du dann den Destroktor?
Hmmm, der DTor muss sein wenn man virtual Methoden nutzt. Jedenfalls war 
das früher so.
Damit kann man sehr schöne Konstrukte bauen.
Ich leite von einer Basisklasse, welche eine einfach verkettete Liste 
bildet, Klassen ab.
Deren Instanzen kann ich nun in die Liste einhängen und ein Ereignis 
auslösen. Jetzt rufen sich alle Überschriebenen Methoden der Reihe nach 
auf.



Carl,
So habe ich es jetzt auch gemacht.

Zwei arrays vom Typ Struct, vorinitialisiert, erzeugen das .eep.
Eine Klasse kümmert sich um das richtige lesen und Speichern.
Der Getter reicht die Daten als "const" durch.
Die Handvoll Setter manipulieren und lösen das schreiben aus.

Leider keine super hübsche Variante aber sicher, schnell, einfach.


Wilhelm,

ich wollte nicht nur config Daten sondern auch "Telemetriedaten" 
erfassen.
Die sollten schon im Block geschrieben werden.
Meine Absicht ist über ein array von structs zu rotieren, sonst könnte 
ich zu viele Schreibzugriffe bekommen. (spätestens bei der Telemetrie)

Wobei Dein Ansatz einzelne Variablen zu sichern natürlich Super ist.
Um das "schön" umzusetzen fehlt mir im Moment aber die Kenntnis.
Ich müsste meinen Horizont halt mal erweitern.
Das geht im Moment aber nicht so gut...
Ich sehe gerade schlimmere Lücken als Templates. ;)

>> Es gibt in C++ auch verschiedene Möglichkeiten das Private aufzuweichen.
>> Mit fallen da protected, friend und static ein.
>
>Wozu sollte das nötig sein?
>
>> (und static ist ne ganz üble Nummer)
>
>Was hat static mit Sichtbarkeit zu tun?


In einer Klassendefinition führt static dazu das die Variable zwischen 
allen Instanzen der Klasse geteilt wird.
Inkrementiert man im CTor und dekrementiert im DTor, könnte man 
mitzählen wieviele Instanzen der klasse existieren.

Ich behaupte wenn eine static Variable im private Teil steht weicht dass 
das Private auf.
Den eine andere Instanz bekommt Zugriff auf das Datum.


>Entweder gibt man seinem EEProm-Typ einen Typumwandlungs-Ctor von etwa
>std::array<std::byte, sizeof(DT)>: dieses Array muss natürlich schon im
>RAM liegen, d.h. man muss es vorher durch plattformspezifische
>Funktionen wie etwa eeprom_read_block(...) füllen, oder man benutzt ein
<placement-new, oder, da man in C++ ja über uchar* <-> type* ein Objekt
>"füllen" kann ohne UB zu bekommen, schreibt man die Repräsentation des
>Objektes irgendwo ins RAM und castet (Vorsicht, dass man keine
>trap-representation dabei generiert).

Uiuiui, in einem Jahr... Liegt nicht im Bereich meiner Fähigkeiten.


Kaj,

ja da ist so eine Ding. Serialisierung kenne ich von Funkstrecken ;)
Aber leider wird mir das zu Kompliziert um es in kurzer Zeit zu 
implementieren.




Ein wichtiger "Merksatz" für mich wäre :
* Instanzen von Klassen sind nur im Speicher Speicherobjekte,
* für den Programmierer sind sie Black Boxen mit Seiteneffekten.

von mh (Gast)


Lesenswert?

Thomas W. schrieb:
> Entschuldigt meine Späte Antwort ich hatte viel zu lesen.
Fürs Weiterbilden muss man sich nicht entschuldigen. Vor allem wenn 
dabei Weltbilder verändert werden.

Thomas W. schrieb:
> Also wenn ich das richtig verstehe würde es schon reichen wenn die
> (leere-)Schleife Terminieren würde.
> Zb vergleich gegen eine Volatile Variable.
> Oder im Rumpf ein Effekt nach aussen auftritt.
Ja, es reicht, wenn es in der Schleife (Rumpf oder Bedingung) einen 
Seiteneffeckt gibt, oder wenn es keine Endlosschleife ist.


Thomas W. schrieb:
> Naja, wenn man ihn pure virtual macht (=0) kann man ihn nicht nutzen.
> Der Compiler müsste abbrechen.
Dafür müsste der Construktor allerdings virtual sein, was nicht erlaubt 
ist. Du solltest dir =delete anschauen. 
http://en.cppreference.com/w/cpp/language/copy_constructor

Thomas W. schrieb:
> Hmmm, der DTor muss sein wenn man virtual Methoden nutzt. Jedenfalls war
> das früher so.
Das ist nicht ganz richtig. Der Destruktor muss virtual sein, wenn man 
ein Objekt "polymorph" zerstört, also über einen Pointer oder eine 
Referenz, wenn dynamischer und statischer Typ nicht übereinstimmen. Auf 
ein "sollte virtual sein" gehe ich jetzt nicht ein.

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.