Bagaimana cara membuat "spacer" dalam struktur memori kelas C ++?

94

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_YYYadalah makro, didefinisikan sebagai uint32_t :32atau 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.cdengan 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 -Wxxxbendera untuk menghapus peringatan ini ...

J Faucher
sumber
26
sudahkah kamu mempertimbangkan char unused[12];dan sebagainya?
MM
3
Saya hanya akan menekan peringatan itu. [class.bit] / 1 menjamin perilaku uint32_t :192;.
NathanOliver
3
@NathanOliver Saya akan dengan senang hati juga, tetapi tampaknya peringatan ini tidak dapat ditekan (Menggunakan GCC) atau saya tidak menemukan cara melakukannya. Selain itu, ini masih bukan cara yang bersih untuk dilakukan (tetapi akan sangat memuaskan). Saya berhasil menemukan tanda "-W" yang benar tetapi tidak berhasil menerapkannya hanya pada file saya sendiri (saya tidak ingin pengguna menghapus jenis peringatan ini untuk pekerjaannya)
J Faucher
3
BTW Anda dapat menulis :42*32alih-alih:1344
MM
1
Coba ini untuk menyembunyikan peringatan? gcc.gnu.org/onlinedocs/gcc/…
Hitobat

Jawaban:

36

Gunakan beberapa bitfield anonim yang berdekatan. Jadi, alih-alih:

    uint32_t :160;

misalnya, Anda akan memiliki:

    uint32_t :32;
    uint32_t :32;
    uint32_t :32;
    uint32_t :32;
    uint32_t :32;

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:

#define REPEAT_2(a) a a
#define REPEAT_4(a) REPEAT_2(a) REPEAT_2(a)
#define REPEAT_8(a) REPEAT_4(a) REPEAT_4(a)
#define REPEAT_16(a) REPEAT_8(a) REPEAT_8(a)
#define REPEAT_32(a) REPEAT_16(a) REPEAT_16(a)

Kemudian ruang 1344 (42 * 32 bit) dapat ditambahkan sebagai berikut:

struct
{
    ...
    REPEAT_32(uint32_t :32;) 
    REPEAT_8(uint32_t :32;) 
    REPEAT_2(uint32_t :32;)
    ...
};
Clifford
sumber
Terima kasih atas jawabannya. Saya sudah mempertimbangkannya, namun itu akan menambahkan lebih dari 200 baris pada beberapa file saya ( uint32_t :1344;ada di tempat itu) jadi saya lebih suka tidak pergi ke cara ini ...
J Faucher
1
@JFaucher Menambahkan solusi yang mungkin untuk persyaratan jumlah baris Anda. Jika Anda memiliki persyaratan seperti itu, Anda dapat menyebutkannya dalam pertanyaan untuk menghindari mendapatkan jawaban yang tidak memenuhi persyaratan tersebut.
Clifford
Terima kasih atas pengeditannya dan maaf karena tidak menyebutkan hitungan baris. Maksud saya adalah bahwa kode saya sudah sulit untuk dipelajari karena ada banyak baris dan saya lebih suka menghindari menambahkan terlalu banyak. Oleh karena itu, saya bertanya apakah seseorang tahu cara "bersih" atau "resmi" untuk menghindari penggunaan bitfield anonim yang berdekatan (meskipun itu berfungsi dengan baik). Pendekatan makro tampaknya baik-baik saja bagi saya. Omong-omong, dalam contoh Anda, bukankah Anda punya ruang 36 * 32 bit?
J Faucher
@JFaucher - dikoreksi. File pemetaan register I / O biasanya besar karena banyaknya register - biasanya Anda menulis sekali, dan pemeliharaan tidak menjadi masalah karena perangkat kerasnya konstan. Kecuali dengan register "menyembunyikan" Anda membuat pekerjaan pemeliharaan untuk diri Anda sendiri jika nanti Anda perlu mengaksesnya. Anda tentu tahu bahwa semua perangkat STM32 sudah memiliki header peta register yang disediakan oleh vendor? Akan jauh lebih sedikit rawan kesalahan untuk menggunakannya.
Clifford
2
Saya setuju dengan Anda dan, untuk bersikap adil, saya pikir saya akan mengikuti salah satu dari dua metode yang ditampilkan dalam jawaban Anda. Saya hanya ingin memastikan bahwa C ++ tidak memberikan solusi yang lebih baik sebelum melakukannya. Saya sangat menyadari bahwa ST menyediakan header tersebut, namun header tersebut dibangun di atas penggunaan makro dan operasi bitwise secara masif. Proyek saya adalah membangun C ++ yang setara dengan tajuk yang akan lebih sedikit rawan kesalahan (menggunakan kelas enum, bitfield, dan sebagainya). Itu sebabnya kami menggunakan skrip untuk "menerjemahkan" header CMSIS ke dalam struktur C ++ kami (dan menemukan beberapa kesalahan dalam file ST btw)
J Faucher
45

Bagaimana dengan cara C ++ - ish?

namespace GPIO {

static volatile uint32_t &MAP0_MODER = *reinterpret_cast<uint32_t*>(0x4000);
static volatile uint32_t &MAP0_OTYPER = *reinterpret_cast<uint32_t*>(0x4004);

}

int main() {
    GPIO::MAP0_MODER = 42;
}

Anda mendapatkan pelengkapan otomatis karena GPIOnamespace, 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.

geza
sumber
1
Ini mungkin mengoptimalkan kurang baik daripada struct untuk akses ke beberapa register MMIO dari fungsi yang sama. Dengan pointer ke alamat dasar dalam register, kompilator dapat menggunakan instruksi muat / penyimpanan dengan perpindahan langsung, seperti 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.)
Peter Cordes
4
Ternyata kekhawatiranku tidak berdasar; ARM GCC juga mengoptimalkan dengan cara ini. godbolt.org/z/ztB7hi . Tetapi perhatikan bahwa Anda menginginkan static volatile uint32_t &MAP0_MODER, bukan inline. Sebuah inlinevariabel tidak mengkompilasi. ( statichindari memiliki penyimpanan statis untuk penunjuk, dan volatilepersis seperti yang Anda inginkan untuk MMIO untuk menghindari penghapusan penyimpanan mati atau pengoptimalan tulis / baca kembali.)
Peter Cordes
1
@PeterCordes: variabel inline adalah fitur C ++ 17 baru. Tapi Anda benar, staticmelakukan sama baiknya untuk kasus ini. Terima kasih telah menyebutkan volatile, saya akan menambahkannya ke jawaban saya (dan mengubah sebaris menjadi statis, sehingga berfungsi untuk pra C ++ 17).
geza
2
Ini bukan perilaku yang didefinisikan dengan baik, lihat utas twitter ini dan mungkin yang ini berguna
Shafik Yaghmour
1
@JFaucher: buat namespace sebanyak struct yang Anda miliki, dan gunakan fungsi mandiri di namespace itu. Jadi, Anda akan melakukannya GPIOA::togglePin().
geza
20

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:

uint16_t * const UART1 = (uint16_t *)(0x40000);
const unsigned int UART_STATUS_OFFSET = 1U;
const unsigned int UART_TRANSMIT_REGISTER = 2U;
uint16_t * const UART1_STATUS_REGISTER = (UART1 + UART_STATUS_OFFSET);
uint16_t * const UART1_TRANSMIT_REGISTER = (UART1 + UART_TRANSMIT_REGISTER);

Anda juga bisa menggunakan notasi array:

uint16_t status = UART1[UART_STATUS_OFFSET];  

Jika Anda harus menggunakan struktur, IMHO, metode terbaik untuk melewati alamat adalah dengan menentukan anggota dan tidak mengaksesnya:

struct UART1
{
  uint16_t status;
  uint16_t reserved1; // Transmit register
  uint16_t receive_register;
};

Dalam salah satu proyek kami, kami memiliki konstanta dan struct dari vendor yang berbeda (vendor 1 menggunakan konstanta sedangkan vendor 2 menggunakan struktur).

Thomas Matthews
sumber
Terima kasih atas jawaban anda. Namun, saya memilih untuk menggunakan pendekatan struktur untuk memudahkan pekerjaan pengguna ketika dia mendapatkan fitur pelengkapan otomatis (Anda hanya akan menampilkan atribut yang tepat) dan saya tidak ingin "menampilkan" slot yang dipesan kepada pengguna sebagai tunjukkan dalam komentar posting pertama saya.
J Faucher
Anda masih bisa mendapatkannya dengan membuat alamat di atas menjadi staticanggota struktur yang mengasumsikan bahwa pelengkapan otomatis dapat menampilkan anggota statis. Jika tidak, bisa juga fungsi anggota sebaris.
Phil1970
@JFaucher Saya bukan orang sistem tertanam dan belum mengujinya, tetapi bukankah masalah pelengkapan otomatis diselesaikan dengan mendeklarasikan anggota yang dicadangkan sebagai pribadi? (Anda dapat mendeklarasikan anggota pribadi dalam sebuah struct, dan Anda dapat menggunakan public:dan private:sebanyak yang Anda inginkan, untuk mendapatkan urutan bidang yang benar.)
Nathaniel
1
@Nathaniel: Tidak begitu; jika kelas memiliki anggota data keduanya publicdan privatenon-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.)
ruakh
1
Jangan lupa volatilepada deklarasi tersebut, BTW, untuk register I / O yang dipetakan memori.
Peter Cordes
13

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:

char unused[n];

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:

#include <stddef.h>
#include <stdint.h>
#include <assert.h>  // for C11 static_assert, so this is valid C as well as C++

struct __attribute__((packed)) GPIO {
    volatile uint32_t a;
    char unused[3];
    volatile uint32_t b;
};

static_assert(offsetof(struct GPIO, b) == 7, "wrong GPIO struct layout");

(contoh pada penjelajah kompilator Godbolt menampilkan offsetof(GPIO, b)= 7 byte.)

Balapan Ringan dalam Orbit
sumber
9

Untuk memperluas jawaban @ Clifford dan @Adam Kotwasinski:

#define REP10(a)        a a a a a a a a a a
#define REP1034(a)      REP10(REP10(REP10(a))) REP10(a a a) a a a a

struct foo {
        int before;
        REP1034(unsigned int :32;)
        int after;
};
int main(void){
        struct foo bar;
        return 0;
}
mosvy
sumber
Saya telah memasukkan varian saran Anda dalam jawaban saya mengikuti persyaratan lebih lanjut dalam komentar. Kredit dimana kredit jatuh tempo.
Clifford
7

Untuk memperluas jawaban Clifford, Anda selalu dapat membuat makro dari bitfield anonim.

Jadi, bukan

uint32_t :160;

menggunakan

#define EMPTY_32_1 \
 uint32_t :32
#define EMPTY_32_2 \
 uint32_t :32;     \ // I guess this also can be replaced with uint64_t :64
 uint32_t :32
#define EMPTY_32_3 \
 uint32_t :32;     \
 uint32_t :32;     \
 uint32_t :32
#define EMPTY_UINT32(N) EMPTY_32_ ## N

Dan kemudian gunakan seperti itu

struct A {
  EMPTY_UINT32(3);
  /* which resolves to EMPTY_32_3, which then resolves to real declarations */
}

Sayangnya, Anda akan membutuhkan sebanyak mungkin EMPTY_32_Xvarian byte yang Anda miliki :( Namun, ini memungkinkan Anda memiliki deklarasi tunggal di struct Anda.

Adam Kotwasinski
sumber
5
Dengan menggunakan makro Boost CPP, saya pikir Anda dapat menggunakan rekursi untuk menghindari keharusan membuat semua makro yang diperlukan.
Peter Cordes
3
Anda dapat menurunkannya (hingga batas rekursi preprocessor, tetapi itu biasanya cukup). Jadi #define EMPTY_32_2 EMPTY_32_1; EMPTY_32_1dan #define EMPTY_32_3 EMPTY_32_2; EMPTY_32_1lain
Miral
@PeterCordes mungkin, tetapi tag menunjukkan bahwa mungkin kompatibilitas booth C dan C ++ diperlukan.
Clifford
2
C dan C ++ menggunakan praprosesor C yang sama; Saya tidak melihat masalah selain mungkin membuat header pendorong yang diperlukan tersedia untuk C. Mereka meletakkan hal-hal makro CPP di header terpisah.
Peter Cordes
1

Untuk mendefinisikan spacer besar sebagai kelompok 32 bit.

#define M_32(x)   M_2(M_16(x))
#define M_16(x)   M_2(M_8(x))
#define M_8(x)    M_2(M_4(x))
#define M_4(x)    M_2(M_2(x))
#define M_2(x)    x x

#define SPACER int : 32;

struct {
    M_32(SPACER) M_8(SPACER) M_4(SPACER)
};
jxh
sumber
1

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 memecahstruct :

// GpioMap0.h
#pragma once

// #includes

namespace Gpio {
struct Map0 {
    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;
};
} // namespace Gpio

// GpioMap1.h
#pragma once

// #includes

namespace Gpio {
struct Map1 {
    // fields
};
} // namespace Gpio

// ... others headers ...

Dan terakhir, tajuk global:

// Gpio.h
#pragma once

#include "GpioMap0.h"
#include "GpioMap1.h"
// ... other headers ...

namespace Gpio {
union Gpio {
    Map0 map0;
    Map1 map1;
    // ... others ...
};
} // namespace Gpio

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:

#define GPIO_RESERVED(Index_, N_) std::uint32_t $$$$##Index_[N_];

struct Map3 {
    GPIO_RESERVED(0, 6);
    GPIO_MAP2_BSRRL;
    GPIO_MAP2_BSRRH;
    GPIO_RESERVED(1, 5);
};

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.

Matthieu M.
sumber
1
Ingat bahwa kasus penggunaan khusus OP adalah untuk menjelaskan register I / O yang dipetakan memori ke kompilator. Tidak pernah masuk akal untuk menyalin seluruh struct berdasarkan nilai. (Dan setiap anggota suka GPIO_MAP0_MODERmungkin volatile.) 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.
Peter Cordes
Anda dapat menggunakan $$$padding##Index_[N_];untuk membuat nama bidang lebih jelas jika muncul dalam pelengkapan otomatis atau saat debugging. (Atau zz$$$paddinguntuk membuatnya mengurutkan setelah GPIO...nama, karena inti dari latihan ini menurut OP adalah pelengkapan otomatis yang lebih baik untuk nama lokasi I / O yang dipetakan memori.)
Peter Cordes
@PeterCordes: Saya memindai jawaban lagi untuk memeriksa, dan tidak pernah melihat penyalinan. Saya memang lupa volatilekualifikasi 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.
Matthieu M.
Saya mengacu pada " dan tidak ada cara sederhana untuk melewatkan semua bidang terkait bersama-sama ", yang terdengar seperti tugas struct, dan sisa kalimat tentang penamaan anggota struct dari serikat.
Peter Cordes
1
@PeterCordes: Saya sedang berpikir untuk melewatkan referensi, seperti yang diilustrasikan nanti. Saya merasa canggung bahwa struktur OP mencegah mereka membuat "modul" yang dapat dibuktikan secara statis hanya untuk mengakses arsitektur tertentu (dengan mengambil referensi ke yang spesifik struct) dan unionakhirnya disebarkan di mana-mana bahkan dalam bit khusus arsitektur yang tidak peduli tentang yang lain.
Matthieu M.
0

Meskipun saya setuju struct tidak boleh digunakan untuk akses port I / O MCU, pertanyaan asli dapat dijawab dengan cara ini:

struct __attribute__((packed)) test {
       char member1;
       char member2;
       volatile struct __attribute__((packed))
       {
       private:
              volatile char spacer_bytes[7];
       }  spacer;
       char member3;
       char member4;
};

Anda mungkin perlu mengganti __attribute__((packed))dengan #pragma packatau 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 spaceranggota itu sendiri tidak bersifat pribadi, jadi data masih dapat diakses dengan cara ini:

(char*)(void*)&testobj.spacer;

Namun ungkapan seperti itu terlihat seperti retasan yang jelas, dan semoga tidak akan digunakan tanpa alasan yang benar-benar bagus, apalagi sebagai kesalahan.

Jack White
sumber
1
Pengguna tidak dapat mendeklarasikan pengidentifikasi di namespace apa pun yang berisi garis bawah ganda di mana pun dalam nama (di C ++, atau hanya di awal di C); melakukan hal itu membuat kode tidak terbentuk dengan baik. Nama-nama ini dicadangkan untuk implementasi dan oleh karena itu secara teori dapat bertentangan dengan nama Anda dengan cara yang sangat halus dan berubah-ubah. Bagaimanapun, kompilator tidak berkewajiban untuk menyimpan kode Anda jika mengandungnya. Nama seperti itu bukanlah cara cepat untuk mendapatkan nama 'internal' untuk Anda gunakan sendiri.
underscore_d
Terima kasih, perbaiki.
Jack White
-1

Anti-solusi.

JANGAN LAKUKAN INI: Campurkan bidang pribadi dan publik.

Mungkin makro dengan penghitung untuk menghasilkan nama variabel unik akan berguna?

#define CONCAT_IMPL( x, y ) x##y
#define MACRO_CONCAT( x, y ) CONCAT_IMPL( x, y )
#define RESERVED MACRO_CONCAT(Reserved_var, __COUNTER__) 


struct {
    GPIO_MAP1_CRL;
    GPIO_MAP1_CRH;
    GPIO_MAP1_IDR;
    GPIO_MAP1_ODR;
    GPIO_MAP1_BSRR;
    GPIO_MAP1_BRR;
    GPIO_MAP1_LCKR;
private:
    char RESERVED[4];
public:
    GPIO_MAP1_AFRL;
    GPIO_MAP1_AFRH;
private:
    char RESERVED[8];
};
Robert Andrzejuk
sumber
4
Ide buruk; Anda baru saja kehilangan urutan anggota dari atas ke bawah .
Balapan Ringan di Orbit
3
Baik. Jika Tidak ada yang keberatan, saya akan meninggalkan jawabannya sebagai apa yang tidak boleh dilakukan.
Robert Andrzejuk
4
@NicHartley Mempertimbangkan jumlah jawaban, kita mendekati pertanyaan "penelitian". Dalam penelitian, pengetahuan tentang jalan buntu tetaplah pengetahuan, itu menghindari orang lain mengambil jalan yang salah. 1 untuk keberanian.
Oliv
1
@Oliv Dan saya -1 karena OP membutuhkan sesuatu, jawaban ini melanggar persyaratan, dan karena itu jawaban yang buruk. Saya secara eksplisit tidak membuat penilaian nilai apa pun, positif atau negatif, pada orang tersebut, di kedua komentar - hanya pada jawabannya. Saya pikir kami berdua setuju itu buruk. Apa yang dikatakan tentang orang tersebut di luar topik untuk situs ini. (Meskipun IMO siapa pun yang bersedia meluangkan waktu untuk menyumbangkan ide adalah melakukan sesuatu dengan benar, meskipun ide itu tidak berjalan dengan baik)
Gugatan Dana Monica
2
Ya itu jawaban yang salah. Tetapi saya khawatir beberapa orang mungkin memiliki gagasan yang sama. Karena komentar dan tautannya saya baru belajar sesuatu, itu bukan poin negatif bagi saya.
Robert Andrzejuk