Masalah
Dalam konteks embedded-metal tingkat rendah , saya ingin membuat ruang kosong di memori, dalam struktur C ++ dan tanpa nama, untuk melarang pengguna mengakses lokasi memori tersebut.
Saat ini, saya telah mencapainya dengan meletakkan uint32_t :96;
bitfield jelek yang akan dengan mudah menggantikan tiga kata, tetapi itu akan memunculkan peringatan dari GCC (Bitfield terlalu besar untuk muat dalam uint32_t), yang cukup sah.
Meskipun berfungsi dengan baik, itu tidak terlalu bersih ketika Anda ingin mendistribusikan perpustakaan dengan beberapa ratus peringatan itu ...
Bagaimana cara melakukannya dengan benar?
Mengapa ada masalah di tempat pertama?
Proyek yang saya kerjakan terdiri dari mendefinisikan struktur memori dari periferal yang berbeda dari seluruh jalur mikrokontroler (STMicroelectronics STM32). Untuk melakukannya, hasilnya adalah kelas yang berisi gabungan beberapa struktur yang mendefinisikan semua register, bergantung pada mikrokontroler yang ditargetkan.
Salah satu contoh sederhana untuk perangkat yang cukup sederhana adalah sebagai berikut: Input / Output Tujuan Umum (GPIO)
union
{
struct
{
GPIO_MAP0_MODER;
GPIO_MAP0_OTYPER;
GPIO_MAP0_OSPEEDR;
GPIO_MAP0_PUPDR;
GPIO_MAP0_IDR;
GPIO_MAP0_ODR;
GPIO_MAP0_BSRR;
GPIO_MAP0_LCKR;
GPIO_MAP0_AFR;
GPIO_MAP0_BRR;
GPIO_MAP0_ASCR;
};
struct
{
GPIO_MAP1_CRL;
GPIO_MAP1_CRH;
GPIO_MAP1_IDR;
GPIO_MAP1_ODR;
GPIO_MAP1_BSRR;
GPIO_MAP1_BRR;
GPIO_MAP1_LCKR;
uint32_t :32;
GPIO_MAP1_AFRL;
GPIO_MAP1_AFRH;
uint32_t :64;
};
struct
{
uint32_t :192;
GPIO_MAP2_BSRRL;
GPIO_MAP2_BSRRH;
uint32_t :160;
};
};
Dimana semuanya GPIO_MAPx_YYY
adalah makro, didefinisikan sebagai uint32_t :32
atau tipe register (struktur khusus).
Di sini Anda melihat uint32_t :192;
mana yang berfungsi dengan baik, tetapi ini memicu peringatan.
Apa yang saya pertimbangkan sejauh ini:
Saya mungkin telah menggantinya dengan beberapa uint32_t :32;
(6 di sini), tetapi saya memiliki beberapa kasus ekstrim di mana saya memiliki uint32_t :1344;
(42) (antara lain). Jadi saya lebih suka tidak menambahkan sekitar seratus baris di atas 8k lainnya, meskipun pembuatan struktur sudah dituliskan.
Pesan peringatan yang tepat adalah seperti:
width of 'sool::ll::GPIO::<anonymous union>::<anonymous struct>::<anonymous>' exceeds its type
(Saya suka betapa teduhnya itu).
Saya lebih suka tidak menyelesaikan ini hanya dengan menghapus peringatan, tetapi menggunakan
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-WTheRightFlag"
/* My code */
#pragma GCC diagnostic pop
semoga bisa menjadi solusi ... jika saya temukan TheRightFlag
. Namun, seperti yang ditunjukkan di utas ini , gcc/cp/class.c
dengan bagian kode yang menyedihkan ini:
warning_at (DECL_SOURCE_LOCATION (field), 0,
"width of %qD exceeds its type", field);
Yang memberi tahu kita bahwa tidak ada -Wxxx
bendera untuk menghapus peringatan ini ...
sumber
char unused[12];
dan sebagainya?uint32_t :192;
.:42*32
alih-alih:1344
Jawaban:
Gunakan beberapa bitfield anonim yang berdekatan. Jadi, alih-alih:
misalnya, Anda akan memiliki:
Satu untuk setiap register Anda ingin menjadi anonim.
Jika Anda memiliki ruang yang besar untuk diisi, mungkin akan lebih jelas dan lebih sedikit kesalahan yang rawan menggunakan makro untuk mengulang satu ruang 32 bit. Misalnya, diberikan:
Kemudian ruang 1344 (42 * 32 bit) dapat ditambahkan sebagai berikut:
sumber
uint32_t :1344;
ada di tempat itu) jadi saya lebih suka tidak pergi ke cara ini ...Bagaimana dengan cara C ++ - ish?
Anda mendapatkan pelengkapan otomatis karena
GPIO
namespace, dan tidak perlu padding tiruan. Bahkan, lebih jelas apa yang terjadi, karena Anda dapat melihat alamat dari setiap register, Anda tidak perlu bergantung pada perilaku padding compiler sama sekali.sumber
ldr r0, [r4, #16]
, sementara kompiler lebih cenderung melewatkan pengoptimalan itu dengan setiap alamat dideklarasikan secara terpisah. GCC mungkin akan memuat setiap alamat GPIO ke register terpisah. (Dari kumpulan literal, meskipun beberapa di antaranya dapat direpresentasikan sebagai dirotasi dengan segera dalam pengkodean Thumb.)static volatile uint32_t &MAP0_MODER
, bukaninline
. Sebuahinline
variabel tidak mengkompilasi. (static
hindari memiliki penyimpanan statis untuk penunjuk, danvolatile
persis seperti yang Anda inginkan untuk MMIO untuk menghindari penghapusan penyimpanan mati atau pengoptimalan tulis / baca kembali.)static
melakukan sama baiknya untuk kasus ini. Terima kasih telah menyebutkanvolatile
, saya akan menambahkannya ke jawaban saya (dan mengubah sebaris menjadi statis, sehingga berfungsi untuk pra C ++ 17).GPIOA::togglePin()
.Dalam arena sistem tertanam, Anda dapat memodelkan perangkat keras baik dengan menggunakan struktur atau dengan menentukan pointer ke alamat register.
Pemodelan berdasarkan struktur tidak disarankan karena kompilator diperbolehkan untuk menambahkan bantalan antar anggota untuk tujuan penyelarasan (walaupun banyak kompiler untuk sistem tertanam memiliki pragma untuk mengemas struktur).
Contoh:
Anda juga bisa menggunakan notasi array:
Jika Anda harus menggunakan struktur, IMHO, metode terbaik untuk melewati alamat adalah dengan menentukan anggota dan tidak mengaksesnya:
Dalam salah satu proyek kami, kami memiliki konstanta dan struct dari vendor yang berbeda (vendor 1 menggunakan konstanta sedangkan vendor 2 menggunakan struktur).
sumber
static
anggota struktur yang mengasumsikan bahwa pelengkapan otomatis dapat menampilkan anggota statis. Jika tidak, bisa juga fungsi anggota sebaris.public:
danprivate:
sebanyak yang Anda inginkan, untuk mendapatkan urutan bidang yang benar.)public
danprivate
non-statis, maka itu bukan tipe tata letak standar , sehingga tidak memberikan jaminan pengurutan yang Anda pikirkan. (Dan saya cukup yakin kasus penggunaan OP memang memerlukan tipe tata letak standar.)volatile
pada deklarasi tersebut, BTW, untuk register I / O yang dipetakan memori.hak geza bahwa Anda benar-benar tidak ingin menggunakan kelas untuk ini.
Tetapi, jika Anda bersikeras, cara terbaik untuk menambahkan anggota yang tidak terpakai dengan lebar n byte, adalah dengan melakukannya:
Jika Anda menambahkan pragma khusus implementasi untuk mencegah penambahan padding arbitrer ke anggota kelas, ini bisa berfungsi.
Untuk GNU C / C ++ (gcc, clang, dan lainnya yang mendukung ekstensi yang sama), salah satu tempat yang valid untuk meletakkan atribut ini adalah:
(contoh pada penjelajah kompilator Godbolt menampilkan
offsetof(GPIO, b)
= 7 byte.)sumber
Untuk memperluas jawaban @ Clifford dan @Adam Kotwasinski:
sumber
Untuk memperluas jawaban Clifford, Anda selalu dapat membuat makro dari bitfield anonim.
Jadi, bukan
menggunakan
Dan kemudian gunakan seperti itu
Sayangnya, Anda akan membutuhkan sebanyak mungkin
EMPTY_32_X
varian byte yang Anda miliki :( Namun, ini memungkinkan Anda memiliki deklarasi tunggal di struct Anda.sumber
#define EMPTY_32_2 EMPTY_32_1; EMPTY_32_1
dan#define EMPTY_32_3 EMPTY_32_2; EMPTY_32_1
lainUntuk mendefinisikan spacer besar sebagai kelompok 32 bit.
sumber
Saya pikir akan bermanfaat untuk memperkenalkan beberapa struktur lagi; yang dapat, pada gilirannya, memecahkan masalah spacer.
Sebutkan variannya
Sementara namespace datar bagus, masalahnya adalah Anda berakhir dengan kumpulan bidang yang beraneka ragam dan tidak ada cara sederhana untuk meneruskan semua bidang terkait bersama-sama. Selain itu, dengan menggunakan struct anonim dalam penyatuan anonim Anda tidak bisa meneruskan referensi ke struct itu sendiri, atau menggunakannya sebagai parameter templat.
Oleh karena itu, sebagai langkah pertama, saya akan mempertimbangkan untuk memecah
struct
:Dan terakhir, tajuk global:
Sekarang, saya bisa menulis
void special_map0(Gpio:: Map0 volatile& map);
, serta mendapatkan gambaran umum singkat dari semua arsitektur yang tersedia dalam sekejap.Spacer Sederhana
Dengan pembagian definisi dalam beberapa tajuk, tajuk secara individual jauh lebih mudah dikelola.
Oleh karena itu, pendekatan awal saya untuk benar-benar memenuhi kebutuhan Anda akan tetap dengan berulang
std::uint32_t:32;
. Ya, ini menambahkan beberapa baris 100 ke baris 8k yang ada, tetapi karena setiap tajuk lebih kecil secara individual, mungkin tidak seburuk itu.Namun, jika Anda ingin mempertimbangkan solusi yang lebih eksotis ...
Memperkenalkan $.
Ini adalah fakta yang sedikit diketahui bahwa
$
karakter yang layak untuk pengenal C ++; itu bahkan karakter awal yang layak (tidak seperti angka).Sebuah
$
muncul dalam kode sumber kemungkinan akan mengangkat alis, dan$$$$
pasti akan menarik perhatian selama tinjauan kode. Ini adalah sesuatu yang dapat Anda manfaatkan dengan mudah:Anda bahkan dapat menyusun "lint" sederhana sebagai hook pra-commit atau di CI Anda yang mencari
$$$$
dalam kode C ++ yang dikomit dan menolak komit tersebut.sumber
GPIO_MAP0_MODER
mungkinvolatile
.) Mungkin referensi atau penggunaan parameter template dari anggota yang sebelumnya anonim dapat berguna. Dan untuk kasus umum dari struktur padding, tentu. Tetapi kasus penggunaan menjelaskan mengapa OP membiarkan mereka anonim.$$$padding##Index_[N_];
untuk membuat nama bidang lebih jelas jika muncul dalam pelengkapan otomatis atau saat debugging. (Atauzz$$$padding
untuk membuatnya mengurutkan setelahGPIO...
nama, karena inti dari latihan ini menurut OP adalah pelengkapan otomatis yang lebih baik untuk nama lokasi I / O yang dipetakan memori.)volatile
kualifikasi pada referensi, yang telah diperbaiki. Adapun penamaan; Biar saya serahkan ke OP. Ada banyak variasi (padding, reserved, ...), dan bahkan prefiks "terbaik" untuk pelengkapan otomatis mungkin bergantung pada IDE yang ada, meskipun saya sangat menghargai gagasan untuk mengubah urutan penyortiran.struct
) danunion
akhirnya disebarkan di mana-mana bahkan dalam bit khusus arsitektur yang tidak peduli tentang yang lain.Meskipun saya setuju struct tidak boleh digunakan untuk akses port I / O MCU, pertanyaan asli dapat dijawab dengan cara ini:
Anda mungkin perlu mengganti
__attribute__((packed))
dengan#pragma pack
atau yang serupa tergantung pada sintaks kompilator Anda.Menggabungkan anggota pribadi dan publik dalam sebuah struct biasanya mengakibatkan tata letak memori tersebut tidak lagi dijamin oleh standar C ++. Namun jika semua anggota non-statis dari sebuah struct bersifat pribadi, itu masih dianggap POD / tata letak standar, dan begitu juga struct yang menyematkannya.
Untuk beberapa alasan gcc menghasilkan peringatan jika anggota struct anonim bersifat pribadi jadi saya harus memberinya nama. Atau, membungkusnya ke dalam struct anonim lain juga menghilangkan peringatan (ini mungkin bug).
Perhatikan bahwa
spacer
anggota itu sendiri tidak bersifat pribadi, jadi data masih dapat diakses dengan cara ini:Namun ungkapan seperti itu terlihat seperti retasan yang jelas, dan semoga tidak akan digunakan tanpa alasan yang benar-benar bagus, apalagi sebagai kesalahan.
sumber
Anti-solusi.
JANGAN LAKUKAN INI: Campurkan bidang pribadi dan publik.
Mungkin makro dengan penghitung untuk menghasilkan nama variabel unik akan berguna?
sumber