Jak utworzyć „odstępnik” w strukturze pamięci klasy C ++?

94

Problem

W kontekście osadzonym na niskim poziomie typu bare-metal chciałbym utworzyć w pamięci puste miejsce w ramach struktury C ++ i bez nazwy, aby uniemożliwić użytkownikowi dostęp do takiej lokalizacji w pamięci.

W tej chwili osiągnąłem to, umieszczając brzydkie pole uint32_t :96;bitowe, które wygodnie zajmie miejsce trzech słów, ale podniesie ostrzeżenie z GCC (Bitfield zbyt duży, aby zmieścić się w uint32_t), co jest całkiem uzasadnione.

Chociaż działa dobrze, nie jest zbyt czysty, gdy chcesz rozpowszechniać bibliotekę z kilkuset takimi ostrzeżeniami ...

Jak mam to zrobić poprawnie?

Dlaczego pojawia się problem w pierwszej kolejności?

Projekt, nad którym pracuję, polega na zdefiniowaniu struktury pamięci różnych peryferiów całej linii mikrokontrolerów (STMicroelectronics STM32). W tym celu powstaje klasa, która zawiera sumę kilku struktur definiujących wszystkie rejestry w zależności od docelowego mikrokontrolera.

Prosty przykład całkiem prostego urządzenia peryferyjnego jest następujący: wejście / wyjście ogólnego przeznaczenia (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;
    };
};

Gdzie wszystko GPIO_MAPx_YYYjest makrem, zdefiniowane jako uint32_t :32lub typ rejestru (dedykowana struktura).

Tutaj widzisz, uint32_t :192;który działa dobrze, ale wyzwala ostrzeżenie.

Co do tej pory rozważałem:

Mogłem go zastąpić kilkoma uint32_t :32;(6 tutaj), ale mam kilka skrajnych przypadków, w których mam uint32_t :1344;(42) (między innymi). Więc wolałbym nie dodawać około stu wierszy do 8k innych, mimo że generowanie struktury jest skryptowane.

Dokładny komunikat ostrzegawczy jest taki: width of 'sool::ll::GPIO::<anonymous union>::<anonymous struct>::<anonymous>' exceeds its type(po prostu uwielbiam to, jak jest podejrzane).

Wolałbym nie rozwiązywać tego przez proste usunięcie ostrzeżenia, ale użycie

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-WTheRightFlag"
/* My code */
#pragma GCC diagnostic pop

może być rozwiązaniem ... jeśli znajdę TheRightFlag. Jednak, jak wskazano w tym wątku , gcc/cp/class.cz tą smutną częścią kodu:

warning_at (DECL_SOURCE_LOCATION (field), 0,
        "width of %qD exceeds its type", field);

Co mówi nam, że nie ma -Wxxxflagi, która usuwałaby to ostrzeżenie ...

J Faucher
źródło
26
czy zastanawiałeś się char unused[12];i tak dalej?
MM
3
Po prostu stłumiłbym ostrzeżenie. [class.bit] / 1 gwarantuje zachowanie uint32_t :192;.
NathanOliver
3
@NathanOliver też z przyjemnością, ale wygląda na to, że tego ostrzeżenia nie można ukryć (przy użyciu GCC) lub nie znalazłem, jak to zrobić. Co więcej, nadal nie jest to czysty sposób (ale byłby całkiem satysfakcjonujący). Udało mi się znaleźć właściwą flagę „-W”, ale nie udało się zastosować go tylko na własnych plików (nie chcę użytkownika, aby usunąć tego rodzaju ostrzeżeń za pracę)
J Faucher
3
BTW :42*32zamiast tego możesz napisać:1344
MM
1
Spróbuj tego, aby stłumić ostrzeżenia? gcc.gnu.org/onlinedocs/gcc/…
Hitobat

Odpowiedzi:

36

Użyj wielu sąsiednich anonimowych pól bitowych. Więc zamiast:

    uint32_t :160;

na przykład miałbyś:

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

Po jednym dla każdego rejestru, w którym chcesz zachować anonimowość.

Jeśli masz duże przestrzenie do wypełnienia, może być wyraźniejsze i mniej podatne na błędy, aby użyć makr do powtórzenia pojedynczej 32-bitowej przestrzeni. Na przykład, biorąc pod uwagę:

#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)

Następnie można dodać przestrzeń 1344 (42 * 32 bity) w ten sposób:

struct
{
    ...
    REPEAT_32(uint32_t :32;) 
    REPEAT_8(uint32_t :32;) 
    REPEAT_2(uint32_t :32;)
    ...
};
Clifford
źródło
Dziękuję za odpowiedź. Rozważyłem już, że jednak dodałoby to ponad 200 wierszy do niektórych moich plików ( uint32_t :1344;jest na miejscu), więc wolałbym nie iść tą drogą ...
J Faucher
1
@JFaucher Dodano możliwe rozwiązanie do wymagań dotyczących liczby linii. Jeśli masz takie wymagania, możesz wspomnieć o nich w pytaniu, aby uniknąć odpowiedzi, które ich nie spełniają.
Clifford
Dzięki za edycję i przepraszam, że nie podałem liczby linii. Chodzi mi o to, że zagłębianie się w mój kod jest już bolesne, ponieważ jest dużo linii i wolałbym uniknąć dodawania zbyt wielu innych. Dlatego pytałem, czy ktoś zna „czysty” lub „oficjalny” sposób na uniknięcie używania sąsiedniego anonimowego pola bitowego (nawet jeśli to działa). Jednak podejście makro wydaje mi się dobre. Nawiasem mówiąc, w twoim przykładzie nie masz przestrzeni 36 * 32 bitów?
J Faucher
@JFaucher - poprawione. Pliki mapowania rejestrów we / wy są z konieczności duże ze względu na dużą liczbę rejestrów - zwykle piszesz raz, a konserwacja nie jest problemem, ponieważ sprzęt jest stały. Z wyjątkiem „ukrywania” rejestrów, wykonujesz prace konserwacyjne dla siebie, jeśli będziesz potrzebować później uzyskać do nich dostęp. Czy zdajesz sobie oczywiście sprawę, że wszystkie urządzenia STM32 mają już nagłówek mapy rejestru dostarczony przez sprzedawcę? Używanie tego byłoby znacznie mniej podatne na błędy.
Clifford
2
Zgadzam się z tobą i, szczerze mówiąc, myślę, że skorzystam z jednej z tych dwóch metod przedstawionych w twojej odpowiedzi. Chciałem się tylko upewnić, że C ++ nie zapewnia lepszego rozwiązania, zanim to zrobię. Wiem dobrze, że ST dostarcza te nagłówki, jednak są one zbudowane na masowym użyciu makr i operacji bitowych. Mój projekt polega na zbudowaniu odpowiednika w C ++ tych nagłówków, które będą mniej podatne na błędy (przy użyciu klas wyliczeniowych, pól bitowych i tak dalej). Dlatego używamy skryptu do „tłumaczenia” nagłówków CMSIS na nasze struktury C ++ (i znaleźliśmy błędy w plikach ST, btw)
J Faucher
45

A co powiesz na sposób w C ++?

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

Otrzymujesz autouzupełnianie ze względu na GPIOprzestrzeń nazw i nie ma potrzeby uzupełniania fikcyjnego. Nawet, jest bardziej jasne, co się dzieje, ponieważ możesz zobaczyć adres każdego rejestru, nie musisz w ogóle polegać na zachowaniu wypełniania kompilatora.

geza
źródło
1
Może to optymalizować gorzej niż struktura dostępu do wielu rejestrów MMIO z tej samej funkcji. Za pomocą wskaźnika do adresu podstawowego w rejestrze kompilator może używać instrukcji ładowania / przechowywania z natychmiastowymi przesunięciami, na przykład ldr r0, [r4, #16], podczas gdy kompilatory są bardziej skłonne do pominięcia tej optymalizacji z każdym adresem deklarowanym osobno. GCC prawdopodobnie załaduje każdy adres GPIO do oddzielnego rejestru. (Z literalnej puli, chociaż niektóre z nich można przedstawić jako obrócone natychmiast w kodowaniu Thumb.)
Peter Cordes
4
Okazuje się, że moje obawy były bezpodstawne; ARM GCC również optymalizuje w ten sposób. godbolt.org/z/ztB7hi . Ale pamiętaj, że chcesz static volatile uint32_t &MAP0_MODER, nie inline. inlineZmienna nie kompiluje. ( staticunika posiadania statycznej pamięci dla wskaźnika i volatilejest dokładnie tym, czego chcesz dla MMIO, aby uniknąć eliminacji martwego magazynu lub optymalizacji zapisu / odczytu zwrotnego).
Peter Cordes
1
@PeterCordes: zmienne inline to nowa funkcja C ++ 17. Ale masz rację, staticrównie dobrze radzi sobie w tym przypadku. Dziękuję za wzmiankę volatile, dodam to do mojej odpowiedzi (i zmienię inline na static, więc działa dla wersji C ++ 17).
geza
2
To nie jest ściśle określone zachowanie, zobacz ten wątek na Twitterze i może ten jest przydatny
Shafik Yaghmour
1
@JFaucher: utwórz tyle przestrzeni nazw, ile masz struktur, i użyj samodzielnych funkcji w tej przestrzeni nazw. Więc będziesz mieć GPIOA::togglePin().
geza
20

Na arenie systemów wbudowanych można modelować sprzęt za pomocą struktury lub definiując wskaźniki do adresów rejestrów.

Modelowanie według struktury nie jest zalecane, ponieważ kompilator może dodawać wypełnienie między elementami w celu wyrównywania (chociaż wiele kompilatorów dla systemów osadzonych ma pragmę pakowania struktury).

Przykład:

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

Możesz również użyć notacji tablicowej:

uint16_t status = UART1[UART_STATUS_OFFSET];  

Jeśli musisz użyć struktury, IMHO, najlepszą metodą pomijania adresów byłoby zdefiniowanie członka i brak dostępu do niego:

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

W jednym z naszych projektów mamy zarówno stałe, jak i struktury od różnych dostawców (sprzedawca 1 używa stałych, podczas gdy sprzedawca 2 używa struktur).

Thomas Matthews
źródło
Dzięki za odpowiedź. Jednak wybieram podejście strukturalne, aby ułatwić pracę użytkownikowi, gdy ma on funkcję autouzupełniania (będą wyświetlane tylko odpowiednie atrybuty) i nie chcę „pokazywać” użytkownikowi zarezerwowanych gniazd jako wskazał w komentarzu do mojego pierwszego postu.
J Faucher
Nadal możesz to mieć, tworząc powyższe staticelementy adresu w strukturze, przy założeniu, że autouzupełnianie może wyświetlać statyczne elementy członkowskie. Jeśli nie, mogą to być również wbudowane funkcje składowe.
Phil 1970,
@JFaucher Nie jestem osobą zajmującą się systemami wbudowanymi i nie testowałem tego, ale czy problem z autouzupełnianiem nie zostałby rozwiązany przez zadeklarowanie zarezerwowanego członka jako prywatnego? (Możesz zadeklarować prywatnych członków w strukturze i możesz używać public:i private:tyle razy, ile chcesz, aby uzyskać prawidłową kolejność pól.)
Nathaniel
1
@Nathaniel: Nie tak; jeśli klasa ma zarówno statyczne, jak publici privateniestatyczne elementy składowe danych, to nie jest to standardowy typ układu , więc nie zapewnia kolejności, o której myślisz. (I jestem prawie pewien, że przypadek użycia OP wymaga standardowego typu układu.)
ruakh
1
Nie zapomnij volatileo tych deklaracjach, BTW, dla rejestrów I / O mapowanych w pamięci.
Peter Cordes
13

geza ma rację, że naprawdę nie chcesz do tego używać klas.

Ale gdybyś nalegał, najlepszym sposobem na dodanie nieużywanego elementu o szerokości n bajtów jest po prostu zrobienie tego:

char unused[n];

Może to zadziałać, jeśli dodasz pragmę specyficzną dla implementacji, aby zapobiec dodawaniu arbitralnego dopełnienia do elementów członkowskich klasy.


W przypadku GNU C / C ++ (gcc, clang i innych, które obsługują te same rozszerzenia), jednym z prawidłowych miejsc na umieszczenie atrybutu jest:

#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");

(przykład w eksploratorze kompilatora Godbolt pokazujący offsetof(GPIO, b)= 7 bajtów.)

Wyścigi lekkości na orbicie
źródło
9

Aby rozwinąć odpowiedzi @ Clifforda i @Adama Kotwasinskiego:

#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
źródło
W mojej odpowiedzi zawarłem wariant Pańskiej sugestii po dalszych wymaganiach w komentarzu. Kredyt tam, gdzie jest on należny.
Clifford
7

Aby rozwinąć odpowiedź Clifforda, zawsze możesz makro anonimowe pola bitowe.

Więc zamiast

uint32_t :160;

posługiwać się

#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

A potem użyj tego jak

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

Niestety, będziesz potrzebować tylu EMPTY_32_Xwariantów, ile masz bajtów :( Mimo to pozwala ci mieć pojedyncze deklaracje w twojej strukturze.

Adam Kotwasiński
źródło
5
Korzystając z makr Boost CPP, myślę, że możesz użyć rekurencji, aby uniknąć konieczności ręcznego tworzenia wszystkich niezbędnych makr.
Peter Cordes
3
Możesz je kaskadowo (do limitu rekursji preprocesora, ale to zwykle wystarczające). Więc #define EMPTY_32_2 EMPTY_32_1; EMPTY_32_1i #define EMPTY_32_3 EMPTY_32_2; EMPTY_32_1itd.
Miral
Być może @PeterCordes, ale tagi wskazują, że być może wymagana jest zgodność z Booth C i C ++.
Clifford
2
C i C ++ używają tego samego preprocesora C; Nie widzę problemu innego niż udostępnienie niezbędnego nagłówka boost dla C. Umieszczają rzeczy CPP-makro w osobnym nagłówku.
Peter Cordes
1

Aby zdefiniować duży odstępnik jako grupy 32-bitowe.

#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
źródło
1

Myślę, że korzystne byłoby wprowadzenie większej struktury; co z kolei może rozwiązać problem elementów dystansowych.

Nazwij warianty

Chociaż płaskie przestrzenie nazw są fajne, problem polega na tym, że otrzymujesz pstrokaty zbiór pól i nie ma prostego sposobu na przekazanie wszystkich powiązanych pól razem. Ponadto, używając anonimowych struktur w anonimowej unii, nie można przekazywać odwołań do samych struktur ani używać ich jako parametrów szablonu.

Dlatego w pierwszej kolejności rozważyłbym wyłamaniestruct :

// 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 ...

I na koniec globalny nagłówek:

// Gpio.h
#pragma once

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

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

Teraz mogę napisać void special_map0(Gpio:: Map0 volatile& map);, a także uzyskać szybki przegląd wszystkich dostępnych architektur.

Proste elementy dystansowe

Dzięki rozdzieleniu definicji na wiele nagłówków, poszczególne nagłówki są znacznie łatwiejsze do zarządzania.

Dlatego moim początkowym podejściem, aby dokładnie spełnić Twoje wymagania, byłoby trzymanie się powtarzania std::uint32_t:32; . Tak, dodaje kilka 100s linii do istniejących 8k linii, ale ponieważ każdy nagłówek jest indywidualnie mniejszy, może nie być tak zły.

Jeśli jednak chcesz rozważyć bardziej egzotyczne rozwiązania ...

Przedstawiamy $.

Jest to mało znany fakt, że $jest to odpowiedni znak dla identyfikatorów C ++; jest to nawet odpowiedni znak początkowy (w przeciwieństwie do cyfr).

$Pojawiające się w kodzie źródłowym najprawdopodobniej podniesie brwi, a $$$$na pewno będzie przyciągać uwagę podczas przeglądów kodu. To jest coś, z czego możesz łatwo skorzystać:

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

Możesz nawet złożyć prosty "lint" jako punkt zaczepienia przed zatwierdzeniem lub w swoim CI, który szuka $$$$w zatwierdzonym kodzie C ++ i odrzuca takie zatwierdzenia.

Matthieu M.
źródło
1
Należy pamiętać, że szczególny przypadek użycia OP służy do opisywania mapowanych w pamięci rejestrów I / O do kompilatora. To nigdy sens skopiować cały struct przez wartość. (I GPIO_MAP0_MODERprzypuszczalnie każdy członek jest podobny volatile). Możliwe, że odniesienie lub użycie parametru szablonu przez wcześniej anonimowych członków może być jednak przydatne. I oczywiście w ogólnym przypadku struktur wypełniających. Ale przypadek użycia wyjaśnia, dlaczego PO pozostawił ich anonimowych.
Peter Cordes
Możesz użyć, $$$padding##Index_[N_];aby nazwa pola była bardziej zrozumiała na wypadek, gdyby pojawiła się podczas autouzupełniania lub podczas debugowania. (Lub zz$$$paddingżeby sortować po GPIO...nazwach, ponieważ celem tego ćwiczenia zgodnie z OP jest ładniejsze autouzupełnianie dla mapowanych w pamięci nazw lokalizacji we / wy.)
Peter Cordes
@PeterCordes: Ponownie zeskanowałem odpowiedź, aby sprawdzić, i nigdy nie widziałem żadnej wzmianki o kopiowaniu. Zapomniałem jednak volatilekwalifikatora w referencji, który został poprawiony. Jeśli chodzi o nazewnictwo; Zwolnię to do OP. Istnieje wiele odmian (dopełnienie, zarezerwowane, ...), a nawet „najlepszy” prefiks do automatycznego uzupełniania może zależeć od dostępnego IDE, chociaż doceniam pomysł poprawiania sortowania.
Matthieu M.
Miałem na myśli „ i nie ma prostego sposobu na przekazanie wszystkich pokrewnych pól razem ”, co brzmi jak przypisanie struktury, a reszta zdania dotyczy nazywania struktur członków związku.
Peter Cordes
1
@PeterCordes: Myślałem o przekazaniu przez odniesienie, jak zilustrowano później. Uważam za niezręczne, że struktura OP uniemożliwia im tworzenie „modułów”, co do których statycznie można udowodnić, że mają dostęp tylko do określonej architektury (poprzez odniesienie do konkretnej struct) i że unionostatecznie są one propagowane wszędzie, nawet w bitach specyficznych dla architektury, które mniej przejmować się innymi.
Matthieu M.
0

Chociaż zgadzam się, że struktury nie powinny być używane do dostępu do portu we / wy MCU, na oryginalne pytanie można odpowiedzieć w ten sposób:

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

Być może trzeba wymienić __attribute__((packed))z #pragma packlub podobny w zależności od składni kompilatora.

Mieszanie prywatnych i publicznych elementów członkowskich w strukturze zwykle powoduje, że układ pamięci nie jest już gwarantowany przez standard C ++. Jednak jeśli wszystkie niestatyczne elementy członkowskie struktury są prywatne, to nadal jest uważane za układ POD / standardowy, podobnie jak struktury, które je osadzają.

Z jakiegoś powodu gcc wyświetla ostrzeżenie, jeśli członek anonimowej struktury jest prywatny, więc musiałem nadać mu nazwę. Alternatywnie, umieszczenie go w kolejnej anonimowej strukturze również usuwa ostrzeżenie (może to być błąd).

Pamiętaj, że spacerczłonek nie jest sam w sobie prywatny, więc nadal można uzyskać dostęp do danych w ten sposób:

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

Jednak takie wyrażenie wygląda jak oczywisty hack i miejmy nadzieję, że nie zostanie użyte bez naprawdę dobrego powodu, nie mówiąc już o błędzie.

Jack White
źródło
1
Użytkownicy nie mogą deklarować identyfikatorów w żadnej przestrzeni nazw zawierającej podwójne podkreślenia w dowolnym miejscu nazwy (w C ++ lub tylko na początku w C); powoduje to zniekształcenie kodu. Nazwy te są zastrzeżone dla implementacji i dlatego teoretycznie mogą kolidować z twoimi w strasznie subtelny i kapryśny sposób. W każdym razie kompilator nie ma obowiązku zatrzymywania kodu, jeśli je zawiera. Takie nazwy nie są szybkim sposobem na uzyskanie nazw „wewnętrznych” do własnego użytku.
underscore_d
Dzięki, naprawiłem to.
Jack White
-1

Anty-rozwiązanie.

NIE ROBIĆ TEGO: Mieszaj pola prywatne i publiczne.

Może przyda się makro z licznikiem do generowania unikalnych nazw zmiennych?

#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
źródło
4
Kiepski pomysł; właśnie straciłeś porządkowanie elementów od góry do dołu .
Wyścigi lekkości na orbicie
3
Ok. Jeśli nikt nie będzie miał nic przeciwko, zostawię odpowiedź, czego nie robić.
Robert Andrzejuk
4
@NicHartley Biorąc pod uwagę liczbę odpowiedzi, jesteśmy blisko pytania „badawczego”. W badaniach wiedza o impasach jest nadal wiedzą, pozwala uniknąć pójścia w złą stronę. +1 za odwagę.
Oliv
1
@Oliv A ja -1'd, ponieważ OP czegoś wymagał, ta odpowiedź naruszyła wymaganie, a zatem jest to zła odpowiedź. Wyraźnie nie dokonałem żadnej oceny tej osoby, pozytywnej ani negatywnej, w żadnym z komentarzy - tylko na temat odpowiedzi. Myślę, że oboje możemy się zgodzić, że jest źle. To, co mówi o tej osobie, jest nie na temat tej witryny. (Chociaż IMO każdy, kto chce poświęcić trochę czasu na przesłanie pomysłu, robi coś dobrze, nawet jeśli pomysł się nie sprawdzi)
Pozew Moniki z funduszem Monica
2
Tak, to zła odpowiedź. Ale obawiam się, że niektórzy ludzie wpadną na ten sam pomysł. Ze względu na komentarz i link, którego właśnie się nauczyłem, nie jest to dla mnie negatywne punkty.
Robert Andrzejuk