Ich bringe meinen Schülern den Umgang mit dem Mikrocontroller STM32F411RE bei, an dessen Bord sich bis zu 512 kB ROM und 128 kB RAM befinden
Normalerweise wird auf diesem Mikrocontroller ein Programm in den
ROM- Speicher geschrieben, und im
RAM sind die zu
ändernden Daten sehr oft erforderlich, um die Konstanten im
ROM zu erstellen.
Im STM32F411RE,
ROM- Mikrocontroller, befindet sich der Speicher an Adressen mit
0x08000000 ... 0x0807FFFF und
RAM mit
0x20000000 ... 0x2001FFFF.Und wenn alle Linker-Einstellungen korrekt sind, berechnet der Schüler, dass in einem so einfachen Code seine Konstante im
ROM liegt:
class WantToBeInROM { private: int i; public: WantToBeInROM(int value): i(value) {} int Get() const { return i; } }; const WantToBeInROM myConstInROM(10); int main() { std::cout << &myConstInROM << std::endl ; }
Sie können auch versuchen, die Frage zu beantworten: Wo befindet sich die Konstante myConstInROM im
ROM oder im
RAM ?
Wenn Sie diese Frage im
ROM beantwortet haben, gratuliere ich Ihnen, wahrscheinlich liegt die Konstante im Allgemeinen im
RAM, und um herauszufinden, wie Sie Ihre Konstanten korrekt und korrekt im
ROM platzieren können - willkommen bei cat.
Einführung
Zunächst ein kleiner Exkurs, warum sollte man sich überhaupt darum kümmern?
Bei der Entwicklung sicherheitskritischer Software für Messgeräte, die IEC 61508-3: 2010 oder einem inländischen Äquivalent von
GOST IEC 61508-3-2018 entsprechen , muss eine Reihe von Punkten berücksichtigt werden, die für herkömmliche Software nicht kritisch sind.
Die Hauptbotschaft dieses Standards lautet, dass die Software jeden Fehler erkennen muss, der die Zuverlässigkeit des Systems beeinträchtigt, und das System in den "Absturz" -Modus versetzen mussZusätzlich zu offensichtlichen mechanischen Fehlern, z. B. Sensorausfall oder -verschlechterung und Ausfall elektronischer Komponenten, sollten Fehler erkannt werden, die durch den Ausfall der Softwareumgebung verursacht werden, z. B.
RAM- oder
ROM- Mikrocontroller.
Und wenn es in den ersten beiden Fällen möglich ist, einen Fehler nur auf ziemlich verwirrte indirekte Weise zu erkennen (es gibt Algorithmen, die den Sensorausfall bestimmen, beispielsweise eine
Methode zur Beurteilung des Zustands eines Widerstandsthermokonverters ), kann dies im Falle eines Softwareumgebungsausfalls viel einfacher durchgeführt werden, beispielsweise kann ein Speicherfehler sein Überprüfen Sie dies durch eine einfache Überprüfung der Datenintegrität. Wenn die Integrität der Daten verletzt wird, interpretieren Sie dies als Speicherfehler.
Wenn die Daten für eine lange Zeit im RAM liegen, ohne sie zu überprüfen und zu aktualisieren, steigt die Wahrscheinlichkeit, dass ihnen aufgrund eines
RAM- Fehlers etwas passiert, mit der Zeit. Ein Beispiel sind einige Kalibrierungskoeffizienten zur Berechnung der Temperatur, die werkseitig eingestellt und in ein externes EEPROM geschrieben wurden. Beim Start werden sie gelesen und in den
RAM geschrieben und sind dort, bis die Stromversorgung ausgeschaltet wird. Im Leben kann der Temperatursensor über den gesamten Zeitraum des Interkalibrierungsintervalls bis zu 3-5 Jahre arbeiten. Offensichtlich müssen solche
RAM- Daten geschützt und regelmäßig auf ihre Integrität überprüft werden.
Es gibt aber auch Daten, wie eine Konstante, die nur zur besseren Lesbarkeit deklariert wurde, ein Objekt eines LCD-Treibers, SPI oder I2C, das nicht geändert werden sollte, einmal erstellt und erst nach dem Ausschalten gelöscht werden.
Diese Daten werden am besten im
ROM gespeichert. Es ist aus technologischer Sicht zuverlässiger und viel einfacher zu überprüfen. Es reicht aus, die Prüfsumme des gesamten Nur-Lese-Speichers in einer Aufgabe mit niedriger Priorität regelmäßig zu lesen. Wenn die Prüfsumme nicht übereinstimmt, können Sie einfach den
ROM- Fehler melden und das Diagnosesystem zeigt einen Unfall an.
Wenn diese Daten im
RAM liegen , wäre es problematisch oder sogar unmöglich, ihre Integrität zu bestimmen, da nicht klar ist, wo sich die unveränderlichen Daten im RAM befinden und wo sie veränderbar sind. Der Linker platziert sie wie gewünscht und schützt jedes RAM-Objekt mit einer Prüfsumme Paranoia.
Daher ist es am einfachsten, 100% sicher zu sein, dass sich die konstanten Daten im
ROM befinden . Wie das geht, möchte ich versuchen zu erklären. Aber zuerst müssen Sie über die Organisation des Speichers in ARM sprechen.
Speicherorganisation
Wie Sie wissen, verfügt der ARM-Kern über eine Harvard-Architektur - die Daten- und Codebusse sind getrennt. Normalerweise bedeutet dies, dass angenommen wird, dass es einen separaten Speicher für Programme und einen separaten Speicher für Daten gibt. Tatsache ist jedoch, dass ARM eine modifizierte Harvard-Architektur ist, d.h. Der Zugriff auf den Speicher erfolgt über einen Bus, und die Speicherverwaltungsvorrichtung bietet bereits die Trennung von Bussen mithilfe von Steuersignalen: Lesen, Schreiben oder Auswählen eines Speicherbereichs.
Somit können sich Daten und Code im selben Speicherbereich befinden. In diesem einzelnen Adressraum können sich
ROM- Speicher sowie
RAM und Peripheriegeräte befinden. Und das bedeutet, dass sowohl der Code als auch die Daten auch dort ankommen können, wo es vom Compiler und Linker abhängt.
Um zwischen den Speicherbereichen für
ROM (Flash) und
RAM zu unterscheiden, werden diese normalerweise in den Linker-Einstellungen angezeigt, z. B. in IAR 8.40.1 sieht dies folgendermaßen aus:
define symbol __ICFEDIT_region_ROM_start__ = 0x08000000; define symbol __ICFEDIT_region_ROM_end__ = 0x0807FFFF; define symbol __ICFEDIT_region_RAM_start__ = 0x20000000; define symbol __ICFEDIT_region_RAM_end__ = 0x2001FFFF; define region ROM_region = mem:[from __ICFEDIT_region_ROM_start__ to __ICFEDIT_region_ROM_end__]; define region RAM_region = mem:[from __ICFEDIT_region_RAM_start__ to __ICFEDIT_region_RAM_end__];
Der RAM in diesem Mikrocontroller befindet sich bei
0x20000000 ... 0x2001FFF und der
ROM bei
0x008000000 ... 0x0807FFFF .
Sie können die Startadresse ROM_start einfach in die RAM-Adresse ändern, z. B. RAM_start und die Endadresse ROM_end__ in RAM_end__, und Ihr Programm befindet sich vollständig im RAM.
Sie können sogar das Gegenteil tun und
RAM im
ROM- Speicherbereich angeben, und Ihr Programm wird erfolgreich zusammengestellt und geflasht, obwohl es nicht funktioniert :)
Einige Mikrocontroller, wie z. B. AVR, verfügen zunächst über einen separaten Adressraum für Programmspeicher, Datenspeicher und Peripheriegeräte. Daher funktionieren solche Tricks dort nicht, und das Programm wird standardmäßig in das
ROM geschrieben.
Der gesamte Adressraum in CortexM ist einzeln, und Code und Daten können sich überall befinden. Mit den Linker-Einstellungen können Sie die Region für die ROM- und RAM- Adressen festlegen. IAR lokalisiert das Textcodesegment im ROM- Bereich
Objektdatei und Segmente
Oben habe ich das Codesegment erwähnt. Mal sehen, was es ist.
Für jedes kompilierte Modul wird eine separate Objektdatei erstellt, die die folgenden Informationen enthält:
- Code- und Datensegmente
- DWARF-Debugging-Informationen
- Zeichentabelle
Wir interessieren uns für Code- und
Datensegmente .
Ein Segment ist ein solches Element, das einen Code oder Daten enthält, die an einer physischen Adresse im Speicher abgelegt werden müssen. Ein Segment kann mehrere Fragmente enthalten, normalerweise ein Fragment für jede Variable oder Funktion. Ein Segment kann sowohl im
ROM als auch im
RAM platziert werden .
Jedes Segment hat einen Namen und ein Attribut, das seinen Inhalt definiert. Das Attribut wird verwendet, um ein Segment in der Konfiguration für den Linker zu definieren. Zum Beispiel können Attribute sein:
- Code - ausführbarer Code
- schreibgeschützt - konstante Variablen
- readwrite - initialisierte Variablen
- zeroinit - nullinitialisierte Variablen
Natürlich gibt es andere Arten von Segmenten, zum Beispiel Segmente, die Debugging-Informationen enthalten, aber wir werden nur an solchen interessiert sein, die Code oder Daten aus unserer Anwendung enthalten.
Im Allgemeinen ist ein Segment der kleinste verknüpfbare Block. Bei Bedarf kann der Linker jedoch auch noch kleinere Blöcke (Fragmente) anzeigen. Wir werden diese Option nicht in Betracht ziehen, wir werden uns mit Segmenten befassen.
Während der Kompilierung werden Daten und Funktionen in verschiedenen Segmenten platziert. Während der Verknüpfung weist der Linker verschiedenen Segmenten echte physische Adressen zu. Der IAR-Compiler verfügt über vordefinierte Segmentnamen, von denen einige im Folgenden aufgeführt werden:
- .bss - Enthält statische und globale Variablen, die auf 0 initialisiert wurden
- .CSTACK - Enthält den vom Programm verwendeten Stapel
- .data - Enthält statische und global initialisierte Variablen
- .data_init - Enthält die Anfangswerte für die Daten im Abschnitt .data, wenn die Initialisierungsanweisung für den Linker verwendet wird
- HEAP - Enthält den Heap, der zum Hosten dynamischer Daten verwendet wird
- .intvec - Enthält eine Interrupt- Vektortabelle
- .rodata - Enthält konstante Daten
- .text - Enthält Programmcode
Um zu verstehen, wo sich die Konstanten befinden, werden wir uns nur für Segmente interessieren
.rodata - ein Segment, in dem Konstanten gespeichert sind,
.data - ein Segment, in dem alle initialisierten statischen und globalen Variablen gespeichert sind.
.bss - ein Segment, in dem alle statischen und globalen
.data- Variablen gespeichert sind, die mit Null (0) initialisiert wurden.
.text - ein Segment zum Speichern von Code.
In der Praxis bedeutet dies, dass, wenn Sie die Variable
int val = 3
, die Variable selbst vom Compiler im
.data- Segment
lokalisiert und mit dem
readwrite- Attribut markiert wird. Die Nummer 3 kann entweder im
.text- Segment oder im
.rodata- Segment oder, wenn, platziert werden Eine spezielle Direktive für den Linker in
.data_init wird angewendet und auch von dieser als
schreibgeschützt markiert.
Das
.rodata- Segment enthält konstante Daten und enthält konstante Variablen, Zeichenfolgen, aggregierte Literale usw. Und
dieses Segment kann überall im Speicher abgelegt werden.Jetzt wird klarer, was in den Linker-Einstellungen vorgeschrieben ist und warum:
place in ROM_region { readonly };
Das heißt, alle mit dem Attribut
readonly gekennzeichneten Daten sollten in ROM_region abgelegt werden. Somit können Daten aus verschiedenen Segmenten, die jedoch mit dem Attribut readonly gekennzeichnet sind, in das ROM gelangen.
Nun, das bedeutet, dass alle Konstanten im ROM sein müssen, aber warum liegt in unserem Code am Anfang des Artikels das konstante Objekt immer noch im RAM? class WantToBeInROM { private: int i; public: WantToBeInROM(int value): i(value) {} int Get() const { return i; } }; const WantToBeInROM myConstInROM(10); int main() { std::cout << &myConstInROM << std::endl ; }
Konstante Daten
Bevor wir die Situation klären, erinnern wir uns zunächst daran, dass globale Variablen im gemeinsamen Speicher erstellt werden, lokale Variablen, d. H. Variablen, die in "normalen" Funktionen deklariert sind, werden auf dem Stapel oder in Registern erstellt, und statische lokale Variablen werden auch im gemeinsam genutzten Speicher erstellt.
Was bedeutet das in C ++? Schauen wir uns ein Beispiel an:
void foo(const int& C1, const int& C2, const int& C3, const int& C4, const int& C5, const int& C6) { std::cout << C1 << C2 << C3 << C4 << C5 << C6 << std::endl; }
Dies sind alles konstante Daten. Für jeden von ihnen gilt jedoch die oben beschriebene Erstellungsregel. Lokale Variablen werden auf dem Stapel erstellt. Daher sollte es mit unseren Linker-Einstellungen folgendermaßen aussehen:
- Die globale Konstante von Fall1 muss sich im ROM befinden . Im Segment .rodata
- Die globale Konstante von Case2 muss sich im ROM befinden . Im Segment .rodata
- Die lokale Konstante von Fall 3 muss im RAM liegen (die Konstante wurde auf dem Stapel im STACK-Segment erstellt).
- Die statische Konstante von Case4 muss sich im ROM befinden . Im Segment .rodata
- Die lokale Konstante von Fall 5 muss im RAM liegen (ein interessanter Fall, der jedoch genau mit Fall 3 identisch ist).
- Die statische Konstante von Case6 muss sich im ROM befinden . Im Segment .rodata
Schauen wir uns nun die Debugging-Informationen und die generierte Map-Datei an. Der Debugger zeigt an, an welchen Adressen sich diese Konstanten befinden.

Wie ich bereits sagte, sind die Adressen 0x0800 ... das sind
ROM- Adressen und 0x200 ... das sind
RAM . Mal sehen, in welchen Segmenten der Compiler diese Konstanten verteilt hat:
.rodata const 0x800'4e2c 0x4 main.o //Case1 .rodata const 0x800'4e30 0x4 main.o //Case2 .rodata const 0x800'4e34 0x4 main.o //Case4 .rodata const 0x800'4e38 0x4 main.o //Case6
Vier globale und statische Konstanten fielen in das
.rodata- Segment, und zwei lokale Variablen fielen nicht in die Map-Datei, da sie auf dem Stapel erstellt wurden und ihre Adresse den Adressen des Stapels entspricht. Das CSTACK-Segment beginnt bei 0x2000'2488 und endet bei 0x2000'0488. Wie Sie auf dem Bild sehen können, werden die Konstanten erst am Anfang des Stapels erstellt.
Der Compiler platziert globale und statische Konstanten im .rodata- Segment, deren Position in den Linker-Einstellungen angegeben ist.
Ein weiterer wichtiger Punkt ist die
Initialisierung . Globale und statische Variablen, einschließlich Konstanten, müssen initialisiert werden. Dies kann auf verschiedene Arten erfolgen. Wenn es sich um eine Konstante handelt, die im
.rodata- Segment liegt, erfolgt die Initialisierung in der Kompilierungsphase, d. H. Der Wert wird sofort an die Adresse geschrieben, an der sich die Konstante befindet. Wenn dies eine reguläre Variable ist, kann die Initialisierung erfolgen, indem der Wert aus dem ROM-Speicher in die Adresse der globalen Variablen kopiert wird:
Wenn beispielsweise die globale Variable
int i = 3
definiert ist, hat der Compiler sie im Daten-Datensegment definiert. Der Linker hat sie auf 0x20000000 gesetzt:
.data inited 0x2000'0000
,
und sein Initialisierungswert (3) liegt im
.rodata- Segment unter der Adresse 0x8000190:
Initializer bytes const 0x800'0190
Wenn Sie diesen Code schreiben:
int i = 3; const int c = i;
Es ist offensichtlich, dass die globale Konstante
erst initialisiert wird, nachdem die globale Variable
i
initialisiert wurde, d. H. Zur Laufzeit. In diesem Fall befindet sich die Konstante im
RAMNun, wenn wir zu unserem zurückkehren
erstes Beispiel class WantToBeInROM { private: int i; public: WantToBeInROM(int value): i(value) {} int Get() const { return i; } }; const WantToBeInROM myConstInROM(10); int main() { std::cout << &myConstInROM << std::endl ; }
Und wir fragen uns: In welchem Segment hat der Compiler das konstante Objekt
myConstInROM
? Und wir bekommen die Antwort: Die Konstante liegt im
.bss- Segment
und enthält statische und globale Variablen, die auf Null (0) initialisiert sind.
.bss inited 0x2000'0004 0x4
myConstInROM 0x2000'0004 0x4
Warum? Denn in C ++ befindet sich ein Datenobjekt, das als Konstante deklariert ist und eine dynamische Initialisierung benötigt, im Lese- / Schreibspeicher und wird beim Erstellen initialisiert.
In diesem Fall erfolgt eine dynamische Initialisierung,
const WantToBeInROM myConstInROM(10)
, und der Compiler
fügt dieses Objekt in das
.bss- Segment ein, initialisiert zuerst alle Felder 0 und
ruft dann beim Erstellen eines konstanten Objekts den Konstruktor auf, um das Feld
i
Wert 10 zu initialisieren.
Wie können wir den Compiler dazu bringen, unser Objekt im
.rodata- Segment zu platzieren? Die Antwort auf diese Frage ist einfach. Sie sollten immer eine statische Initialisierung durchführen. Sie können es so machen:
1. In unserem Beispiel ist zu sehen, dass der Compiler die dynamische Initialisierung im Prinzip auf statisch optimieren kann, da der Konstruktor recht einfach ist. Für die IAR des Compilers können Sie die Konstante mit dem Attribut
__ro_placement markieren
__ro_placement const WantToBeInROM myConstInROM
Mit dieser Option platziert der Compiler die Variable an der Adresse im ROM:
myConstInROM 0x800'0144 0x4 Data
Offensichtlich ist dieser Ansatz nicht universell und im Allgemeinen sehr spezifisch. Deshalb gehen wir zur richtigen Methode über :)
2. Es ist ein
constexpr
Konstruktor zu
constexpr
. Wir weisen den Compiler sofort an, die statische Initialisierung zu verwenden, d. H. In der Kompilierungsphase wird das gesamte Objekt im Voraus vollständig "berechnet" und alle seine Felder sind bekannt. Alles was wir tun müssen, ist constexpr zum Konstruktor hinzuzufügen.
Objekt fliegt zum ROM class WantToBeInROM { private: int i; public: constexpr WantToBeInROM(int value): i(value) {} int Get() const { return i; } }; const WantToBeInROM myConstInROM(10); int main() { std::cout << &myConstInROM << std::endl ; }
Um sicherzugehen, dass sich Ihr konstantes Objekt im ROM befindet, müssen Sie einfache Regeln befolgen:
- Das .text- Segment, in dem der Code platziert ist, sollte sich im ROM befinden. Es wird in den Linker-Einstellungen konfiguriert.
- Das .rodata- Segment, in dem globale und statische Konstanten platziert sind, muss sich im ROM befinden. Es wird in den Linker-Einstellungen konfiguriert.
- Die Konstante muss global oder statisch sein.
- Attribute einer konstanten Variablenklasse dürfen nicht veränderbar sein
- Die Initialisierung des Objekts muss statisch sein, d. H. Der Konstruktor der Klasse, dessen Objekt eine Konstante sein wird, muss konstexpr sein oder überhaupt nicht definiert sein (es gibt keine dynamische Initialisierung).
- Wenn möglich, wenn Sie sicher sind, dass das Objekt im ROM anstelle von const gespeichert werden soll, verwenden Sie constexpr
Ein paar Worte zum constexpr und zum constexpr-Konstruktor. Der Hauptunterschied zwischen const und constexpr besteht darin, dass die Initialisierung der const-Variablen bis zur Laufzeit verzögert werden kann. Die Variable constexpr muss zur Kompilierungszeit initialisiert werden.
Alle constexpr-Variablen sind vom Typ const.Die Definition des constexpr-Konstruktors muss die folgenden Anforderungen erfüllen:
Der implizite Standardkonstruktor ist der constexpr-Konstruktor. Schauen wir uns nun einige Beispiele an:
Beispiel 1. Objekt im ROM class Test { private: int i; public: Test() {} ; int Get() const { return i + 1; } } ; const Test test;
Dies ist besser, nicht zu schreiben, da das Objekt in den RAM fliegt, sobald Sie sich entscheiden, das Attribut i zu initialisieren
Beispiel 2. Ein Objekt im RAM class Test { private: int i = 1;
Beispiel 3. Ein Objekt im RAM class Test { private: int i; public: Test(int value): i(value) {} ; int Get() const { return i + 1; } } ; const Test test(10);
Beispiel 4. Objekt im ROM class Test { private: int i; public: constexpr Test(int value): i(value) {} ; int Get() const { return i + 1; } } ; const Test test(10);
Beispiel 5. Ein Objekt im RAM class Test { private: int i; public: constexpr Test(int value): i(value) {} ; int Get() const { return i + 1; } } ; int main() { const Test test(10);
Beispiel 6. Objekt im ROM class Test { private: int i; public: constexpr Test(int value): i(value) {} ; int Get() const { return i + 1; } } ; int main() { static const Test test(10);
Beispiel 7. Kompilierungsfehler class Test { private: int i; public: constexpr Test(int value): i(value) {} ; int Get()
Beispiel 8. Ein Objekt im ROM, das von einer abstrakten Klasse erbt class ITest { private: int j; public: virtual int Get() const = 0; constexpr ITest(int value) : j(value) { } int Give() const { return j ; } }; class Test: public ITest { private: int i; public: constexpr Test(int value): i(value), ITest(value+1) {} ; int Get() const override { return i + 1; } } ; const Test test(10);
Beispiel 9. Ein Objekt im ROM aggregiert ein Objekt im RAM class ITest { protected: int j; public: virtual int Get() const = 0; constexpr ITest(int value) : j(value) { } int Give() const { return j ; } }; class TestImpl: public ITest { private: int k; public: TestImpl(int value): k(value), ITest(value) { } int Get() const override { return j + 10; } void Set(int value) { k = value; j = value + 10; } } ; TestImpl testImpl(1);
Beispiel 10. Das gleiche, aber statische Objekt im ROM class ITest { protected: int j; public: virtual int Get() const = 0; constexpr ITest(int value) : j(value) { } int Give() const { return j ; } }; class TestImpl: public ITest { private: int k; public: TestImpl(int value): k(value), ITest(value) { } int Get() const override { return j + 10; } void Set(int value) { k = value; j = value + 10; } } ; class Test: public ITest { private: int i; TestImpl & obj;
Beispiel 11. Und jetzt ist das konstante Objekt nicht statisch und daher im RAM class ITest { protected: int j; public: virtual int Get() const = 0; constexpr ITest(int value) : j(value) { } int Give() const { return j ; } }; class TestImpl: public ITest { private: int k; public: TestImpl(int value): k(value), ITest(value) { } int Get() const override { return j + 10; } void Set(int value) { k = value; j = value + 10; } } ; class Test: public ITest { private: int i; TestImpl & obj;
Beispiel 12. Kompilierungsfehler. class ITest { protected: int j; public: virtual int Get() const = 0; constexpr ITest(int value) : j(value) { } int Give() const { return j ; } }; class TestImpl: public ITest { private: int k; public: TestImpl(int value): k(value), ITest(value) { } int Get() const override { return j + 10; } void Set(int value) { k = value; j = value + 10; } } ; class Test: public ITest { private: int i; TestImpl obj;
Beispiel 13. Kompilierungsfehler class ITest { protected: int j; public: virtual int Get() const = 0; constexpr ITest(int value) : j(value) { } int Give() const { return j ; } }; class TestImpl: public ITest { private: int k; public: constexpr TestImpl(int value): k(value), ITest(value)
Beispiel 14. Ein Objekt im ROM class ITest { protected: int j; public: virtual int Get() const = 0; constexpr ITest(int value) : j(value) { } int Give() const { return j ; } }; class TestImpl: public ITest { private: int k; public: constexpr TestImpl(int value): k(value), ITest(value) { } int Get() const override { return j + 10; } void Set(int value) const
Und schließlich ein konstantes Objekt, das ein Array enthält, mit Array-Initialisierung über eine constexpr-Funktion. class Test { private: int k[100]; constexpr void InitArray() { int i = 0; for(auto& it: k) { it = i++ ; } } public: constexpr Test(): k() { InitArray();
Referenzen:
IAR C / C ++ - EntwicklungshandbuchConstexpr-Konstruktoren (C ++ 11)constexpr (C ++)PS.
Nach einer sehr nützlichen Diskussion mit Valdaros müssen Sie die folgenden Punkttangenskonstanten hinzufügen. Gemäß dem C ++ - Standard und diesem Dokument N1076.pdf1. Jede Änderung an einem konstanten Objekt (mit Ausnahme von veränderlichen Mitgliedern einer Klasse) während seiner Lebensdauer führt zu einem undefinierten Verhalten. Das heißt,
const int ci = 1 ; int* iptr = const_cast<int*>(&ci);
int i = 1; const int* ci = &i ; int* iptr = const_cast<int *> (ci);
2. Das Problem ist, dass dies nur während der gesamten Lebensdauer eines konstanten Objekts funktioniert, im Konstruktor und Destruktor jedoch nicht. Daher ist es durchaus legitim, dies zu tun: class Test { public: int i; constexpr Test(): i(0) { foo(this) ; } } ; Test *test1; constexpr void foo(Test* value) { value->i = 1;
Und es gilt als legal. Trotz der Tatsache, dass wir den Konstruktor constexpr und die darin enthaltene Funktion constexpr verwendet haben. Das Objekt geht direkt in den RAM.Um dies zu vermeiden, verwenden Sie const - constexpr anstelle von const. Dann tritt ein Kompilierungsfehler auf, der Sie darüber informiert, dass etwas nicht stimmt und das Objekt nicht konstant sein kann. class Test { public: int i; constexpr Test(): i(0) { foo(this) ; } } ; Test *test1; constexpr void foo(Test* value) { value->i = 1;