Di mana konstanta Anda disimpan pada mikrokontroler CortexM (menggunakan kompiler C ++ IAR sebagai contoh)

Saya mengajari siswa saya cara bekerja dengan mikrokontroler STM32F411RE, yang papan tulisnya memiliki ROM sebanyak 512 kB dan RAM 128 kB
Biasanya, sebuah program ditulis ke ROM pada mikrokontroler ini, dan data variabel sangat sering dibutuhkan dalam RAM sehingga konstanta berada dalam ROM .
Dalam STM32F411RE, ROM mikrokontroler, memori terletak di alamat dengan 0x08000000 ... 0x0807FFFF , dan RAM dengan 0x20000000 ... 0x2001FFFF.

Dan jika semua pengaturan linker benar, siswa menghitung bahwa dalam kode langsung seperti itu konstanta terletak pada ROM :

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 ; } 

Anda juga dapat mencoba menjawab pertanyaan: di mana konstanta myConstInROM dalam ROM atau dalam RAM ?

Jika Anda menjawab pertanyaan ini bahwa dalam ROM , saya ucapkan selamat kepada Anda, bahkan kemungkinan besar Anda salah, konstanta umumnya terletak pada RAM, dan untuk mengetahui cara menempatkan konstanta Anda dengan benar dan benar di ROM - selamat datang di cat.

Pendahuluan


Pertama, penyimpangan kecil, mengapa repot-repot tentang ini.
Saat mengembangkan perangkat lunak penting keselamatan untuk alat ukur yang mematuhi IEC 61508-3: 2010 atau setara dengan domestik dari GOST IEC 61508-3-2018 , sejumlah poin harus diperhitungkan yang tidak penting untuk perangkat lunak konvensional.

Pesan utama dari standar ini adalah bahwa perangkat lunak harus mendeteksi setiap kegagalan yang mempengaruhi keandalan sistem dan menempatkan sistem dalam mode "crash".

Selain kegagalan mekanis yang jelas, misalnya, kegagalan atau degradasi sensor dan kegagalan komponen elektronik, kesalahan yang disebabkan oleh kegagalan lingkungan perangkat lunak, misalnya, mikrokontroler RAM atau ROM , harus dideteksi.

Dan jika dalam dua kasus pertama, dimungkinkan untuk mendeteksi kesalahan hanya dengan cara tidak langsung yang agak membingungkan (ada algoritma yang menentukan kegagalan sensor, misalnya, Metode menilai keadaan konverter termal resistansi ), maka jika terjadi kegagalan lingkungan perangkat lunak, ini dapat dilakukan lebih mudah, misalnya, kegagalan memori dapat verifikasi dengan pemeriksaan integritas data sederhana. Jika integritas data dilanggar, maka tafsirkan ini sebagai kegagalan memori.

Jika data untuk waktu yang lama terletak pada RAM tanpa memeriksa dan memperbarui, maka kemungkinan sesuatu akan terjadi pada mereka karena kegagalan RAM menjadi lebih tinggi dari waktu ke waktu. Contohnya adalah beberapa koefisien kalibrasi untuk menghitung suhu yang ditetapkan di pabrik dan ditulis ke EEPROM eksternal, saat startup mereka dibaca dan ditulis ke RAM dan ada di sana sampai daya dimatikan. Dan dalam kehidupan, sensor suhu dapat bekerja sepanjang periode interval antar-kalibrasi, hingga 3-5 tahun. Jelas, data RAM semacam itu harus dilindungi dan secara berkala diperiksa integritasnya.

Tetapi ada juga data, seperti konstanta yang dideklarasikan hanya untuk keterbacaan, objek driver LCD, SPI atau I2C, yang tidak boleh diubah, dibuat sekali dan tidak dihapus sampai daya dimatikan.

Data ini paling baik disimpan dalam ROM . Ini lebih dapat diandalkan dari sudut pandang teknologi dan lebih mudah untuk memeriksanya, cukup untuk secara berkala membaca checksum dari seluruh memori read-only dalam beberapa tugas prioritas rendah. Jika checksum tidak cocok, Anda dapat melaporkan kegagalan ROM dan sistem diagnostik akan menampilkan kecelakaan.

Jika data ini terletak pada RAM , itu akan menjadi masalah atau bahkan tidak mungkin untuk menentukan integritasnya karena fakta bahwa tidak jelas di mana data yang tidak dapat diubah dalam RAM dan di mana itu bisa berubah, linker menempatkannya seperti yang diinginkan, dan untuk melindungi setiap objek RAM dengan checksum terlihat seperti paranoia.

Oleh karena itu, cara termudah adalah menjadi 100% yakin bahwa data konstan dalam ROM . Cara melakukan ini saya ingin coba jelaskan. Tetapi pertama-tama Anda perlu berbicara tentang pengaturan memori di ARM.

Organisasi memori


Seperti yang Anda ketahui, inti ARM memiliki arsitektur Harvard - data dan kode bus dipisahkan. Biasanya ini berarti diasumsikan ada memori terpisah untuk program dan memori terpisah untuk data. Tetapi kenyataannya adalah bahwa ARM adalah arsitektur Harvard yang dimodifikasi, mis. akses ke memori dilakukan pada satu bus, dan perangkat manajemen memori sudah menyediakan pemisahan bus menggunakan sinyal kontrol: baca, tulis atau pilih area memori.

Dengan demikian, data dan kode dapat berada di area memori yang sama. Dalam ruang alamat tunggal ini dapat ditemukan dan memori ROM dan RAM dan periferal. Dan ini berarti bahwa sebenarnya kedua kode dan data bisa mendapatkan meskipun tergantung pada kompiler dan penghubung.

Oleh karena itu, untuk membedakan antara area memori untuk ROM (Flash) dan RAM, mereka biasanya ditunjukkan dalam pengaturan linker, misalnya, dalam IAR 8.40.1, terlihat seperti ini:

 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__]; 

RAM dalam mikrokontroler ini terletak di 0x20000000 ... 0x2001FFF, dan ROM di 0x008000000 ... 0x0807FFFF .
Anda dapat dengan mudah mengubah alamat awal ROM_start ke alamat RAM, katakanlah RAM_start dan alamat akhir ROM_end__ ke RAM_end__ dan program Anda akan sepenuhnya berada dalam RAM.
Anda bahkan dapat melakukan yang sebaliknya dan menentukan RAM di area memori ROM , dan program Anda akan berhasil merakit dan mem-flash, meskipun tidak akan berhasil :)
Beberapa mikrokontroler, seperti AVR, pada awalnya memiliki ruang alamat terpisah untuk memori program, memori data dan periferal, dan karenanya trik seperti itu tidak akan berfungsi di sana, dan program ini ditulis ke ROM secara default.

Semua ruang alamat di CortexM adalah tunggal, dan kode dan data dapat ditemukan di mana saja. Menggunakan pengaturan tautan, Anda dapat mengatur wilayah untuk alamat ROM dan RAM . IAR menempatkan segmen kode .text di wilayah ROM

File dan segmen objek


Di atas, saya menyebutkan segmen kode, mari kita lihat apa itu.

File objek terpisah dibuat untuk setiap modul yang dikompilasi, yang berisi informasi berikut:

  • Kode dan Segmen Data
  • Informasi Debugging DWARF
  • Tabel karakter

Kami tertarik pada segmen kode dan data. Segmen adalah elemen yang berisi potongan kode atau data yang harus ditempatkan pada alamat fisik dalam memori. Segmen dapat berisi beberapa fragmen, biasanya satu fragmen untuk setiap variabel atau fungsi. Segmen dapat ditempatkan di kedua ROM dan RAM .
Setiap segmen memiliki nama dan atribut yang mendefinisikan kontennya. Atribut digunakan untuk mendefinisikan segmen dalam konfigurasi untuk tautan. Misalnya, atribut mungkin:

  • kode - kode yang dapat dieksekusi
  • readonly - variabel konstan
  • readwrite - variabel yang diinisialisasi
  • zeroinit - variabel nol yang diinisialisasi

Tentu saja, ada jenis segmen lain, misalnya segmen yang berisi informasi debug, tetapi kami hanya akan tertarik pada segmen yang berisi kode atau data dari aplikasi kami.

Secara umum, segmen adalah blok yang dapat dinautkan terkecil. Namun, jika perlu, penghubung juga dapat menunjukkan blok yang lebih kecil (fragmen). Kami tidak akan mempertimbangkan opsi ini, kami akan lakukan dengan segmen.

Selama kompilasi, data dan fungsi ditempatkan di segmen yang berbeda. Dan selama menaut, tautan memberikan alamat fisik nyata ke segmen yang berbeda. Kompiler IAR memiliki nama segmen yang sudah ditentukan sebelumnya, beberapa di antaranya akan saya berikan di bawah ini:

  • .bss - Berisi variabel statis dan global yang diinisialisasi ke 0
  • .CSTACK - Berisi tumpukan yang digunakan oleh program
  • .data - Berisi variabel inisialisasi statis dan global
  • .data_init - Berisi nilai awal untuk data di bagian .data jika arahan inisialisasi untuk linker digunakan.
  • HEAP - Berisi heap yang digunakan untuk meng-host data dinamis
  • .intvec - Berisi tabel vektor interupsi
  • .rodata - Berisi data konstan
  • .text - Berisi kode program

Untuk memahami di mana konstanta berada, kami hanya akan tertarik pada segmen
.rodata - segmen di mana konstanta disimpan,
.data - segmen di mana semua variabel statis dan global yang diinisialisasi disimpan,
.bss - segmen di mana semua variabel .data statis dan global yang diinisialisasi dengan nol (0) disimpan,
.text - segmen untuk menyimpan kode.

Dalam praktiknya, ini berarti bahwa jika Anda mendefinisikan variabel int val = 3 , maka variabel itu sendiri akan ditemukan oleh kompiler di segmen data. Dan ditandai dengan atribut readwrite , dan angka 3 dapat ditempatkan baik di segmen .text atau di segmen .rodata atau, jika arahan khusus untuk tautan di. data_init diterapkan dan juga ditandai sebagai hanya dapat dibaca olehnya .

Segmen .rodata berisi data konstan dan mencakup variabel konstan, string, literal agregat, dan sebagainya. Dan segmen ini dapat ditempatkan di mana saja dalam memori.

Sekarang menjadi lebih jelas apa yang ditentukan dalam pengaturan tautan dan mengapa:

 place in ROM_region { readonly }; //   .rodata  .data_init (  )  ROM: place in RAM_region { readwrite, //   .data, .bss,  .noinit block STACK }; //  STACK  HEAP  RAM 

Artinya, semua data yang ditandai dengan atribut readonly harus ditempatkan di ROM_region. Dengan demikian, data dari segmen yang berbeda, tetapi ditandai dengan atribut readonly, dapat masuk ke ROM.

Nah itu berarti semua konstanta harus dalam ROM, tetapi mengapa dalam kode kita, di awal artikel, apakah objek konstan masih terletak pada 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 ; } 



Data konstan


Sebelum mengklarifikasi situasi, mari kita ingat kembali bahwa variabel global dibuat dalam memori bersama, variabel lokal, yaitu variabel yang dideklarasikan di dalam fungsi "normal" dibuat di stack atau di register, dan variabel lokal statis juga dibuat di memori bersama.

Apa artinya ini di C ++. Mari kita lihat sebuah contoh:

 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; } //     constexpr int Case1 = 1 ; //  (      ) const int Case2 = 2; int main() { //  . const int Case3 = 3 ; // . static const int Case4 = 4 ; //      ,     . constexpr int Case5 = Case1 + 5 ; //     . static constexpr int Case6 = 6 ; foo(Case1,Case2,Case3,Case4,Case5,Case6); return 1; } 

Ini semua data konstan. Tetapi untuk salah satu dari mereka aturan penciptaan yang dijelaskan di atas berlaku, variabel lokal dibuat pada stack. Karena itu, dengan pengaturan tautan kami, harus seperti ini:

  • Konstanta global Case1 harus dalam ROM . Di segmen .rodata
  • Konstanta global Case2 harus dalam ROM . Di segmen .rodata
  • Konstanta lokal Case3 harus terletak pada RAM (konstanta dibuat pada stack di segmen STACK)
  • Konstanta statis Case4 harus dalam ROM . Di segmen .rodata
  • Konstanta lokal Case5 harus terletak pada RAM (kasus yang menarik, tetapi persis identik dengan Kasus 3.)
  • Konstanta statis Case6 harus dalam ROM . Di segmen .rodata

Sekarang mari kita lihat informasi debug dan file peta yang dihasilkan. Debugger menunjukkan di mana alamat konstanta ini berada.

gambar

Seperti yang saya katakan sebelumnya, alamat 0x0800 ... ini adalah alamat ROM , dan 0x200 ... ini adalah RAM . Mari kita lihat di segmen mana kompiler mendistribusikan konstanta ini:

  .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 

Empat konstanta global dan statis jatuh ke segmen .rodata , dan dua variabel lokal tidak jatuh ke file peta karena mereka dibuat di stack dan alamatnya sesuai dengan alamat stack. Segmen CSTACK dimulai pada 0x2000'2488 dan berakhir pada 0x2000'0488. Seperti yang dapat Anda lihat dari gambar, konstanta baru saja dibuat di awal tumpukan.

Kompiler menempatkan konstanta global dan statis di segmen .rodata , lokasi yang ditentukan dalam pengaturan tautan.

Perlu dicatat poin penting lainnya, inisialisasi . Variabel global dan statis, termasuk konstanta, harus diinisialisasi. Dan ini bisa dilakukan dengan beberapa cara. Jika itu adalah bohong konstan di segmen .rodata , inisialisasi terjadi pada tahap kompilasi, mis. nilai segera ditulis ke alamat di mana konstanta berada. Jika ini adalah variabel biasa, maka inisialisasi dapat terjadi dengan menyalin nilai dari memori ROM ke alamat variabel global:

Misalnya, jika variabel global int i = 3 didefinisikan, maka kompiler mendefinisikannya dalam segmen data .d, penghubung meletakkannya di 0x20000000:
.data inited 0x2000'0000 ,
dan nilai inisialisasi (3) akan terletak di segmen .rodata di alamat 0x8000190:
Initializer bytes const 0x800'0190
Jika Anda menulis kode ini:

 int i = 3; const int c = i; 

Jelas bahwa konstanta global , diinisialisasi hanya setelah variabel global i diinisialisasi, yaitu, dalam runtime. Dalam hal ini, konstanta akan ditempatkan di RAM

Sekarang jika kita kembali ke
contoh awal
 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 ; } 

Dan kami bertanya pada diri sendiri: di segmen apa kompiler mendefinisikan objek konstan myConstInROM ? Dan kami mendapatkan jawabannya: konstanta akan terletak di segmen .bss, yang berisi variabel statis dan global yang diinisialisasi ke nol (0).
.bss inited 0x2000'0004 0x4
myConstInROM 0x2000'0004 0x4


Mengapa Karena dalam C ++, objek data yang dideklarasikan sebagai konstanta dan yang membutuhkan inisialisasi dinamis terletak di memori baca-tulis dan akan diinisialisasi pada waktu pembuatan ..

Dalam kasus ini, inisialisasi dinamis terjadi, const WantToBeInROM myConstInROM(10) , dan kompiler meletakkan objek ini di segmen .bss , menginisialisasi semua bidang 0 terlebih dahulu, dan kemudian, ketika membuat objek konstan, yang disebut konstruktor untuk menginisialisasi bidang i nilai 10.

Bagaimana kita dapat membuat kompiler menempatkan objek kita di segmen .rodata ? Jawaban untuk pertanyaan ini sederhana, Anda harus selalu melakukan inisialisasi statis. Anda dapat melakukannya dengan cara ini:

1. Dalam contoh kami, dapat dilihat bahwa, pada prinsipnya, kompiler dapat mengoptimalkan inisialisasi dinamis menjadi statis, karena konstruktornya cukup sederhana. Untuk IAR dari kompiler, Anda dapat menandai konstanta dengan atribut __ro_placement
__ro_placement const WantToBeInROM myConstInROM
Dengan opsi ini, kompiler akan menempatkan variabel di alamat dalam ROM:
myConstInROM 0x800'0144 0x4 Data
Jelas, pendekatan ini tidak universal dan umumnya sangat spesifik. Karena itu, kami beralih ke metode yang benar :)

2. Ini adalah untuk membuat konstruktor constexpr . Kami segera memberi tahu kompiler untuk menggunakan inisialisasi statis, mis. pada tahap kompilasi, ketika seluruh objek akan sepenuhnya "dihitung" sebelumnya dan semua bidangnya akan diketahui. Yang perlu kita lakukan adalah menambahkan constexpr ke konstruktor.

Objek terbang ke 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 ; } 


Jadi, untuk memastikan bahwa objek konstan Anda dalam ROM, Anda harus mengikuti aturan sederhana:
  1. Segmen .teks tempat kode ditempatkan harus dalam ROM. Ini dikonfigurasi dalam pengaturan tautan.
  2. Segmen .rodata di mana konstanta global dan statis ditempatkan harus dalam ROM. Ini dikonfigurasi dalam pengaturan tautan.
  3. Konstanta harus global atau statis.
  4. Atribut kelas variabel konstan tidak boleh berubah-ubah
  5. Inisialisasi objek harus statis, mis. Konstruktor kelas yang objeknya akan berupa konstanta harus constexpr atau tidak didefinisikan sama sekali (tidak ada inisialisasi dinamis)
  6. Jika memungkinkan, jika Anda yakin bahwa objek tersebut harus disimpan dalam ROM daripada menggunakan constexpr

Beberapa kata tentang constexpr dan konstruktor constexpr. Perbedaan utama antara const dan constexpr adalah bahwa inisialisasi variabel const dapat ditunda hingga runtime. Variabel constexpr harus diinisialisasi pada waktu kompilasi.
Semua variabel constexpr adalah tipe const.

Definisi konstruktor constexpr harus memenuhi persyaratan berikut:
  • Kelas tidak boleh memiliki kelas dasar virtual.
     struct D2 : virtual BASE { //error, D2 must not have virtual base class. constexpr D2() : BASE(), mem(55) { } private: int mem; }; 
  • Setiap tipe parameter kelas harus tipe literal.
  • Badan konstruktor harus = delete atau = default . Atau memenuhi persyaratan di bawah ini:
  • Tidak ada blok try catch di badan konstruktor.
  • Badan konstruktor dapat menggunakan nullptr
  • Badan konstruktor dapat menggunakan static_assert
  • Di dalam tubuh konstruktor, typedef dapat digunakan yang tidak mendefinisikan kelas atau enumerasi
  • Badan konstruktor dapat menggunakan arahan dan deklarasi using
  • Setiap anggota non-statis dari kelas atau kelas dasar harus diinisialisasi.
  • Konstruktor kelas atau kelas dasar, yang digunakan untuk menginisialisasi elemen non-statis dari anggota kelas dan sub-objek dari kelas dasar, harus berupa constexpr .
  • Inisialisasi untuk semua elemen data non-statis harus berupa constexpr
  • Saat menginisialisasi anggota kelas, semua jenis konversi harus valid dalam ekspresi konstan. Misalnya, menggunakan reinterpret_cast dan casting dari void* ke tipe pointer lain tidak diperbolehkan

Konstruktor default implisit adalah konstruktor constexpr. Sekarang mari kita lihat beberapa contoh:

Contoh 1. Objek dalam ROM
 class Test { private: int i; public: Test() {} ; int Get() const { return i + 1; } } ; const Test test; //  ROM.    . i  0  . int main() { std::cout << test.Get() << std::endl ; return 0; } 


Lebih baik tidak menulis seperti ini, karena begitu Anda memutuskan untuk menginisialisasi atribut i, objek akan terbang ke RAM

Contoh 2. Objek dalam RAM
 class Test { private: int i = 1; // i.       constexpr . public: Test() {} ; //       ,       ,  constexpr int Get() const { return i + 1; } } ; const Test test; //  RAM. i     int main() { std::cout << test.Get() << std::endl ; return 0; } 



Contoh 3. Objek dalam RAM
 class Test { private: int i; public: Test(int value): i(value) {} ; int Get() const { return i + 1; } } ; const Test test(10); //  RAM. i     int main() { std::cout << test.Get() << std::endl ; return 0; } 



Contoh 4. Objek dalam ROM
 class Test { private: int i; public: constexpr Test(int value): i(value) {} ; int Get() const { return i + 1; } } ; const Test test(10); //  ROM. i     constexpr  int main() { std::cout << test.Get() << std::endl ; return 0; } 



Contoh 5. Objek dalam 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); //  RAM.    std::cout << test.Get() << std::endl ; return 0; } 



Contoh 6. Objek dalam 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); //  ROM.    std::cout << test.Get() << std::endl ; return 0; } 



Contoh 7. Kesalahan kompilasi
 class Test { private: int i; public: constexpr Test(int value): i(value) {} ; int Get() // Get  ,  ,       (i),     .   ,       . { return i + 1; } } ; const Test test(10); int main() { std::cout << test.Get() << std::endl ; return 0; } 



Contoh 8. Objek dalam ROM, mewarisi dari kelas abstrak
 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); //  ROM. i     constexpr , j   constexpr  ITest int main() { std::cout << test.Give() << std::endl ; return 0; } 



Contoh 9. Objek dalam ROM mengagregasi objek yang terletak di 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); //    RAM. class Test: public ITest { private: int i; TestImpl & obj; //    public: constexpr Test(int value, TestImpl & ref): i(value), obj(ref), ITest(value+1) { } ; int Get() const override { return i + 1; } bool Set() const { obj.Set(100) ; //     return true; } } ; constexpr Test test(10, testImpl); //  ROM.     constexpr  int main() { std::cout << test.Set() << std::endl ; return 0; } 



Contoh 10. Objek yang sama tetapi statis di 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; //    public: constexpr Test(int value, TestImpl & ref): i(value), obj(ref),ITest(value+1) { } ; int Get() const override { return i + 1; } bool Set() const { obj.Set(100) ; //     return true; } } ; int main() { static TestImpl testImpl(1); //  static constexpr Test test(10, testImpl); //    ROM.     constexpr  std::cout << test.Set() << std::endl ; return 0; } 



Contoh 11. Dan sekarang objek konstan tidak statis dan karenanya dalam 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; //    public: constexpr Test(int value, TestImpl & ref): i(value), obj(ref),ITest(value+1) { } ; int Get() const override { return i + 1; } bool Set() const { obj.Set(100) ; //     return true; } } ; int main() { static TestImpl testImpl(1); //  const Test test(10, testImpl); //    RAM. std::cout << test.Set() << std::endl ; return 0; } 



Contoh 12. Kesalahan kompilasi.
 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; //   TestImpl public: constexpr Test(int value): i(value), obj(TestImpl(value)), //   constexpr   TestImpl ITest(value+1) { } ; int Get() const { return i + 1; } bool Set() const { obj.Set(100) ; //     return true; } } ; int main() { static TestImpl testImpl(1); //  static const Test test(10); //   std::cout << test.Set() << std::endl ; return 0; } 



Contoh 13. Kesalahan kompilasi
 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) //   constexpr { } int Get() const override { return j + 10; } void Set(int value) //   ,  k  j.       ROM,        RAM { k = value; j = value + 10; } } ; class Test: public ITest { private: int i; TestImpl obj; public: constexpr Test(int value): i(value), obj(TestImpl(value)), // constexpr     obj,    obj  .rodata . ITest(value+1) { } ; int Get() const { return i + 1; } bool Set() const { obj.Set(100) ; //        constexpr     return true; } } ; int main() { static TestImpl testImpl(1); //  static const Test test(10); //   std::cout << test.Set() << std::endl ; return 0; } 



Contoh 14. Objek dalam 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 //   const { //do something } } ; class Test: public ITest { private: int i; const TestImpl obj; // ,    constexpr  public: constexpr Test(int value): i(value), obj(TestImpl(value)), ITest(value+1) { } ; int Get() const { return i + 1; } bool Set() const { obj.Set(100) ; //   return true; } } ; int main() { //static TestImpl testImpl(1); //  static const Test test(10); //    ROM.     constexpr  std::cout << test.Set() << std::endl ; return 0; } 



Dan akhirnya, objek konstan yang berisi array, dengan inisialisasi array melalui fungsi constexpr.
 class Test { private: int k[100]; constexpr void InitArray() { int i = 0; for(auto& it: k) { it = i++ ; } } public: constexpr Test(): k() { InitArray(); // constexpr     } int Get(int index) const { return k[index]; } } ; int main() { static const Test test; //    ROM.     ,  constexpr . std::cout << test.Get(10) << std::endl ; return 0; } 


Referensi:
IAR C / C ++ Panduan Pengembangan
Constexpr constructors (C ++ 11)
constexpr (C ++)

PS.
Setelah diskusi yang sangat berguna dengan Valdaros, Anda perlu menambahkan konstanta tangen titik berikut. Menurut standar C ++ dan dokumen ini N1076.pdf

1. Setiap perubahan ke objek konstan (dengan pengecualian anggota kelas yang bisa berubah) selama masa hidupnya mengarah ke Perilaku Tidak Terdefinisi. Yaitu

  const int ci = 1 ; int* iptr = const_cast<int*>(&ci); //UB,       *iptr = 2 ; 

  int i = 1; const int* ci = &i ; int* iptr = const_cast<int *> (ci); //   *iptr = 2 ; // UB,   i 

2. Masalahnya adalah bahwa ini hanya berfungsi selama seumur hidup dari objek konstan, tetapi dalam konstruktor dan destruktor tidak berfungsi. Karena itu, cukup sah untuk melakukannya:

 class Test { public: int i; constexpr Test(): i(0) { foo(this) ; } } ; Test *test1; constexpr void foo(Test* value) { value->i = 1; //       0  1 test1 = value ; //           } const Test test; int main() { test1->i = 2; //          2. std::cout << &test << std::endl; } 

Dan itu dianggap legal. Terlepas dari kenyataan bahwa kami menggunakan konstruktor constexpr, dan fungsi constexpr di dalamnya. Objek langsung menuju ke RAM.

Untuk menghindarinya, gunakan const - constexpr alih-alih const, maka akan ada kesalahan kompilasi yang akan memberi tahu Anda bahwa ada sesuatu yang salah dan objek tidak dapat konstan.

 class Test { public: int i; constexpr Test(): i(0) { foo(this) ; } } ; Test *test1; constexpr void foo(Test* value) { value->i = 1; //       0  1 test1 = value ; //           } constexpr Test test; // / Error[Pe2400]: calling the default constructor for "Test" does not produce a constant value main.cpp 151 int main() { test1->i = 2; //          2. std::cout << &test << std::endl; } 

Source: https://habr.com/ru/post/id453262/


All Articles