Wie erstelle ich einen "Spacer" in einer C ++ - Klassenspeicherstruktur?

94

Das Thema

In einem Bare-Metal-Embedded- Kontext auf niedriger Ebene möchte ich einen leeren Bereich im Speicher innerhalb einer C ++ - Struktur und ohne Namen erstellen, um dem Benutzer den Zugriff auf diesen Speicherort zu verbieten.

Im Moment habe ich es erreicht, indem ich ein hässliches uint32_t :96;Bitfeld eingefügt habe, das bequemerweise drei Wörter ersetzt, aber eine Warnung von GCC auslöst (Bitfeld zu groß, um in uint32_t zu passen), was ziemlich legitim ist.

Es funktioniert zwar einwandfrei, ist aber nicht sehr sauber, wenn Sie eine Bibliothek mit mehreren Hundert dieser Warnungen verteilen möchten ...

Wie mache ich das richtig?

Warum gibt es überhaupt ein Problem?

Das Projekt, an dem ich arbeite, besteht darin, die Speicherstruktur verschiedener Peripheriegeräte einer gesamten Mikrocontrollerleitung (STMicroelectronics STM32) zu definieren. Das Ergebnis ist eine Klasse, die eine Vereinigung mehrerer Strukturen enthält, die abhängig vom Ziel-Mikrocontroller alle Register definieren.

Ein einfaches Beispiel für ein ziemlich einfaches Peripheriegerät ist das folgende: ein GPIO (General Purpose Input / Output)

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

Wobei alles GPIO_MAPx_YYYein Makro ist, das entweder als uint32_t :32oder als Registertyp definiert ist (eine dedizierte Struktur).

Hier sehen Sie, uint32_t :192;was gut funktioniert, aber es löst eine Warnung aus.

Was ich bisher bedacht habe:

Ich hätte es vielleicht durch mehrere ersetzen können uint32_t :32;(6 hier), aber ich habe einige extreme Fälle, in denen ich uint32_t :1344;(42) (unter anderem) habe. Daher möchte ich lieber nicht etwa hundert Zeilen über 8.000 Zeilen hinzufügen, obwohl die Strukturgenerierung in Skripten ausgeführt ist.

Die genaue Warnmeldung ist ungefähr so: width of 'sool::ll::GPIO::<anonymous union>::<anonymous struct>::<anonymous>' exceeds its type(Ich liebe es einfach, wie schattig es ist).

Ich würde dies lieber nicht lösen, indem ich einfach die Warnung entferne, sondern die Verwendung von

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

kann eine Lösung sein ... wenn ich finde TheRightFlag. Wie in diesem Thread erwähnt , gcc/cp/class.cmit diesem traurigen Codeteil:

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

Was uns sagt, dass es keine -WxxxFlagge gibt, um diese Warnung zu entfernen ...

J Faucher
quelle
26
Hast du darüber nachgedacht char unused[12];und so weiter?
MM
3
Ich würde nur die Warnung unterdrücken. [class.bit] / 1 garantiert das Verhalten von uint32_t :192;.
NathanOliver
3
@ NathanOliver Ich würde es auch gerne tun, aber es scheint, dass diese Warnung nicht unterdrückbar ist (mit GCC) oder ich habe nicht gefunden, wie das geht. Darüber hinaus ist es immer noch kein sauberer Weg (aber es wäre ziemlich befriedigend). Ich habe es geschafft, das richtige "-W" -Flag zu finden, aber es ist mir nicht gelungen, es nur auf meine eigenen Dateien anzuwenden (ich möchte nicht, dass der Benutzer diese Art von Warnungen für seine Arbeit entfernt)
J Faucher
3
Übrigens können Sie :42*32anstelle von:1344
MM
1
Versuchen Sie dies, um Warnungen zu unterdrücken? gcc.gnu.org/onlinedocs/gcc/…
Hitobat

Antworten:

36

Verwenden Sie mehrere benachbarte anonyme Bitfelder. Also statt:

    uint32_t :160;

Zum Beispiel hätten Sie:

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

Eine für jedes Register, für das Sie anonym bleiben möchten.

Wenn Sie große Leerzeichen ausfüllen müssen, ist es möglicherweise klarer und weniger fehleranfällig, Makros zum Wiederholen des einzelnen 32-Bit-Speicherplatzes zu verwenden. Zum Beispiel gegeben:

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

Dann kann ein 1344 (42 * 32 Bit) Speicherplatz wie folgt hinzugefügt werden:

struct
{
    ...
    REPEAT_32(uint32_t :32;) 
    REPEAT_8(uint32_t :32;) 
    REPEAT_2(uint32_t :32;)
    ...
};
Clifford
quelle
Danke für die Antwort. Ich habe bereits darüber nachgedacht, aber es würde mehr als 200 Zeilen in einige meiner Dateien einfügen ( uint32_t :1344;ist an Ort und Stelle), so dass ich lieber nicht diesen Weg gehen müsste ...
J Faucher
1
@JFaucher Eine mögliche Lösung für Ihre Zeilenanzahlanforderung wurde hinzugefügt. Wenn Sie solche Anforderungen haben, können Sie diese in der Frage erwähnen, um zu vermeiden, dass Sie Antworten erhalten, die diese nicht erfüllen.
Clifford
Vielen Dank für die Bearbeitung und entschuldigen Sie, dass Sie die Zeilenanzahl nicht angegeben haben. Mein Punkt ist, dass das Eintauchen in meinen Code bereits schmerzhaft ist, da es viele Zeilen gibt und ich lieber vermeiden möchte, zu viel mehr hinzuzufügen. Daher fragte ich, ob jemand einen "sauberen" oder "offiziellen" Weg kenne, um die Verwendung eines benachbarten anonymen Bitfelds zu vermeiden (auch wenn dies gut funktioniert). Der Makro-Ansatz scheint mir jedoch in Ordnung zu sein. Haben Sie in Ihrem Beispiel übrigens keinen 36 * 32-Bit-Speicherplatz?
J Faucher
@JFaucher - korrigiert. E / A-Registerzuordnungsdateien sind aufgrund der großen Anzahl von Registern notwendigerweise groß - normalerweise schreiben Sie einmal, und die Wartung ist kein Problem, da die Hardware eine Konstante ist. Außer durch "Ausblenden" von Registern erledigen Sie Wartungsarbeiten für sich selbst, wenn Sie später darauf zugreifen müssen. Sie wissen natürlich, dass alle STM32-Geräte bereits einen vom Anbieter bereitgestellten Registerkarten-Header haben? Es wäre weitaus weniger fehleranfällig, dies zu verwenden.
Clifford
2
Ich stimme Ihnen zu und um fair zu sein, denke ich, dass ich eine dieser beiden Methoden anwenden werde, die in Ihrer Antwort aufgeführt sind. Ich wollte nur sichergehen, dass C ++ vorher keine bessere Lösung bietet. Mir ist klar, dass ST diese Header bereitstellt, diese basieren jedoch auf der massiven Verwendung von Makros und bitweisen Operationen. Mein Projekt besteht darin, ein C ++ - Äquivalent zu den Headern zu erstellen, die weniger fehleranfällig sind (unter Verwendung von Aufzählungsklassen, Bitfeldern usw.). Aus diesem Grund verwenden wir ein Skript, um die CMSIS-Header in unsere C ++ - Strukturen zu "übersetzen" (und haben übrigens einige Fehler in ST-Dateien gefunden)
J Faucher
45

Wie wäre es mit einem C ++ - ish Weg?

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

Sie erhalten aufgrund des GPIONamespace eine automatische Vervollständigung , und es ist kein Dummy-Padding erforderlich. Es ist jedoch klarer, was los ist, da Sie die Adresse jedes Registers sehen können, müssen Sie sich überhaupt nicht auf das Auffüllverhalten des Compilers verlassen.

geza
quelle
1
Dies kann möglicherweise weniger gut als eine Struktur für den Zugriff auf mehrere MMIO-Register von derselben Funktion aus optimieren. Mit einem Zeiger auf die Basisadresse in einem Register kann der Compiler Lade- / Speicheranweisungen mit sofortigen Verschiebungen verwenden ldr r0, [r4, #16], während Compiler diese Optimierung eher verpassen, wenn jede Adresse separat deklariert wird. GCC wird wahrscheinlich jede GPIO-Adresse in ein separates Register laden. (Aus einem wörtlichen Pool, obwohl einige von ihnen in der Thumb-Codierung als sofort gedreht dargestellt werden können.)
Peter Cordes
4
Es stellte sich heraus, dass meine Sorgen unbegründet waren; ARM GCC optimiert auch auf diese Weise. godbolt.org/z/ztB7hi . Aber beachten Sie, dass Sie wollen static volatile uint32_t &MAP0_MODER, nicht inline. Eine inlineVariable wird nicht kompiliert. ( Vermeidet staticstatischen Speicher für den Zeiger und volatileist genau das, was Sie für MMIO wünschen, um die Beseitigung von Dead-Stores oder die Optimierung von Write / Readback zu vermeiden.)
Peter Cordes
1
@PeterCordes: Inline-Variablen sind eine neue C ++ 17-Funktion. Aber Sie haben Recht, statictut genauso gut für diesen Fall. Vielen Dank für die Erwähnung volatile, ich werde es meiner Antwort hinzufügen (und Inline in statisch ändern, damit es für Pre C ++ 17 funktioniert).
Geza
2
Dies ist kein genau definiertes Verhalten, siehe diesen Twitter-Thread und vielleicht ist dieser nützlich
Shafik Yaghmour
1
@JFaucher: Erstellen Sie so viele Namespaces wie Sie haben, und verwenden Sie eigenständige Funktionen in diesem Namespace. Also wirst du haben GPIOA::togglePin().
Geza
20

Im Bereich eingebetteter Systeme können Sie Hardware entweder mithilfe einer Struktur oder durch Definieren von Zeigern auf die Registeradressen modellieren.

Die Modellierung nach Struktur wird nicht empfohlen, da der Compiler zu Ausrichtungszwecken Auffüllungen zwischen Elementen hinzufügen darf (obwohl viele Compiler für eingebettete Systeme ein Pragma zum Packen der Struktur haben).

Beispiel:

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

Sie können auch die Array-Notation verwenden:

uint16_t status = UART1[UART_STATUS_OFFSET];  

Wenn Sie die Struktur IMHO verwenden müssen, besteht die beste Methode zum Überspringen von Adressen darin, ein Mitglied zu definieren und nicht darauf zuzugreifen:

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

In einem unserer Projekte haben wir sowohl Konstanten als auch Strukturen von verschiedenen Anbietern (Anbieter 1 verwendet Konstanten, während Anbieter 2 Strukturen verwendet).

Thomas Matthews
quelle
Danke für deine Antwort. Ich entscheide mich jedoch für einen Strukturansatz, um dem Benutzer die Arbeit zu erleichtern, wenn er eine automatische Vervollständigungsfunktion erhalten hat (nur die richtigen Attribute werden angezeigt), und ich möchte dem Benutzer die reservierten Slots nicht als "anzeigen" wies in einem Kommentar meines ersten Beitrags darauf hin.
J Faucher
Sie können dies weiterhin tun, indem Sie die oben genannten Adressmitglieder staticzu einer Struktur machen, vorausgesetzt, dass die automatische Vervollständigung statische Elemente anzeigen kann. Wenn nicht, können es auch Inline-Member-Funktionen sein.
Phil1970
@JFaucher Ich bin keine Person für eingebettete Systeme und habe dies nicht getestet. Würde das Problem der automatischen Vervollständigung jedoch nicht gelöst, indem das reservierte Mitglied als privat deklariert wird? (Sie können private Mitglieder in einer Struktur deklarieren und Sie können public:und private:so oft Sie möchten verwenden, um die richtige Reihenfolge der Felder zu erhalten.)
Nathaniel
1
@ Nathaniel: Nicht so; Wenn eine Klasse sowohl nicht statische Datenelemente publicals auch privatenicht statische Datenelemente enthält, handelt es sich nicht um einen Standardlayouttyp. Daher bietet sie nicht die Bestellgarantien, an die Sie denken. (Und ich bin mir ziemlich sicher, dass der Anwendungsfall des OP einen Standardlayouttyp erfordert.)
Ruakh
1
Vergessen Sie volatilediese Deklarationen übrigens nicht für speicherabgebildete E / A-Register.
Peter Cordes
13

geza hat recht, dass du dafür wirklich keine klassen verwenden willst.

Wenn Sie jedoch darauf bestehen möchten, können Sie ein unbenutztes Element mit einer Breite von n Bytes am besten einfach folgendermaßen hinzufügen :

char unused[n];

Wenn Sie ein implementierungsspezifisches Pragma hinzufügen, um zu verhindern, dass den Mitgliedern der Klasse beliebige Auffüllungen hinzugefügt werden, kann dies funktionieren.


Für GNU C / C ++ (gcc, clang und andere, die dieselben Erweiterungen unterstützen) ist eine der gültigen Stellen für das Attribut:

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

(Beispiel im Godbolt-Compiler-Explorer mit offsetof(GPIO, b)= 7 Bytes.)

Leichtigkeitsrennen im Orbit
quelle
9

Um die Antworten von @ Clifford und @Adam Kotwasinski zu erweitern:

#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
quelle
Ich habe eine Variante Ihres Vorschlags in meine Antwort aufgenommen, wobei weitere Anforderungen in einem Kommentar enthalten sind. Kredit, wo Kredit fällig ist.
Clifford
7

Um die Antwort von Clifford zu erweitern, können Sie die anonymen Bitfelder jederzeit makroauslesen.

Also statt

uint32_t :160;

verwenden

#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

Und dann benutze es gerne

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

Leider benötigen Sie so viele EMPTY_32_XVarianten wie Sie Bytes haben :( Trotzdem können Sie einzelne Deklarationen in Ihrer Struktur haben.

Adam Kotwasinski
quelle
5
Ich denke, Sie können mit Boost-CPP-Makros die Rekursion verwenden, um zu vermeiden, dass Sie alle erforderlichen Makros von Hand erstellen müssen.
Peter Cordes
3
Sie können sie kaskadieren (bis zur Rekursionsgrenze des Präprozessors, aber das ist normalerweise ausreichend). So #define EMPTY_32_2 EMPTY_32_1; EMPTY_32_1und #define EMPTY_32_3 EMPTY_32_2; EMPTY_32_1so
Miral
@PeterCordes vielleicht, aber die Tags zeigen an, dass möglicherweise Kompatibilität mit Stand C und C ++ erforderlich ist.
Clifford
2
C und C ++ verwenden denselben C-Präprozessor. Ich sehe kein anderes Problem, als vielleicht den erforderlichen Boost-Header für C verfügbar zu machen. Sie setzen das CPP-Makro-Zeug in einen separaten Header.
Peter Cordes
1

Definieren eines großen Abstandshalters als Gruppen von 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
quelle
1

Ich denke, es wäre vorteilhaft, etwas mehr Struktur einzuführen. Dies kann wiederum das Problem der Abstandshalter lösen.

Nennen Sie die Varianten

Flache Namespaces sind zwar nett, aber das Problem ist, dass Sie am Ende eine bunte Sammlung von Feldern haben und keine einfache Möglichkeit, alle verwandten Felder zusammen zu übergeben. Wenn Sie anonyme Strukturen in einer anonymen Vereinigung verwenden, können Sie keine Verweise auf die Strukturen selbst übergeben oder sie als Vorlagenparameter verwenden.

Als ersten Schritt würde ich daher in Betracht ziehen, Folgendes auszubrechenstruct :

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

Und schließlich der globale Header:

// Gpio.h
#pragma once

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

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

Jetzt kann ich eine schreiben void special_map0(Gpio:: Map0 volatile& map); auf einen Blick einen schnellen Überblick über alle verfügbaren Architekturen verschaffen.

Einfache Abstandshalter

Da die Definition in mehrere Header aufgeteilt ist, sind die Header einzeln viel einfacher zu verwalten.

Daher wäre mein erster Ansatz, um Ihre Anforderungen genau zu erfüllen, wiederholt zu bleiben std::uint32_t:32;. Ja, es werden einige 100s-Zeilen zu den vorhandenen 8k-Zeilen hinzugefügt, aber da jeder Header einzeln kleiner ist, ist er möglicherweise nicht so schlecht.

Wenn Sie jedoch bereit sind, exotischere Lösungen in Betracht zu ziehen ...

Wir stellen $ vor.

Es ist eine wenig bekannte Tatsache, dass dies $ein brauchbares Zeichen für C ++ - Bezeichner ist. Es ist sogar ein brauchbarer Startcharakter (im Gegensatz zu Ziffern).

Ein $Erscheinen im Quellcode würde wahrscheinlich die Augenbrauen hochziehen und $$$$wird bei Codeüberprüfungen definitiv Aufmerksamkeit erregen. Dies ist etwas, das Sie leicht nutzen können:

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

Sie können sogar einen einfachen "Flusen" als Pre-Commit-Hook oder in Ihrem CI zusammenstellen, der $$$$im festgeschriebenen C ++ - Code sucht, und solche Commits ablehnen.

Matthieu M.
quelle
1
Denken Sie daran, dass der spezielle Anwendungsfall des OP darin besteht, dem Compiler speicherabgebildete E / A-Register zu beschreiben. Es ist niemals sinnvoll, die gesamte Struktur nach Wert zu kopieren. (Und jedes Mitglied mag GPIO_MAP0_MODERes vermutlich volatile.) Möglicherweise könnte jedoch die Verwendung von Referenz- oder Vorlagenparametern der zuvor anonymen Mitglieder nützlich sein. Und für den allgemeinen Fall von Polsterstrukturen sicher. Der Anwendungsfall erklärt jedoch, warum das OP sie anonym gelassen hat.
Peter Cordes
Sie können $$$padding##Index_[N_];den Feldnamen verwenden , um ihn selbsterklärender zu gestalten, falls er jemals bei der automatischen Vervollständigung oder beim Debuggen angezeigt wird. (Oder zz$$$paddingum es nach GPIO...Namen sortieren zu lassen , weil der Sinn dieser Übung laut OP darin besteht, die automatische Vervollständigung für speicherabgebildete E / A-Standortnamen zu verbessern.)
Peter Cordes
@PeterCordes: Ich habe die Antwort erneut gescannt, um sie zu überprüfen, und nie eine Erwähnung des Kopierens gesehen. Ich habe jedoch das volatileQualifikationsmerkmal auf der Referenz vergessen , das korrigiert wurde. Wie für die Benennung; Ich werde es bis zum OP lassen. Es gibt viele Variationen (Auffüllen, reserviert, ...), und selbst das "beste" Präfix für die automatische Vervollständigung kann von der vorliegenden IDE abhängen, obwohl ich die Idee, die Sortierung zu optimieren, sehr schätze.
Matthieu M.
Ich bezog mich auf " und keine einfache Möglichkeit, alle verwandten Felder zusammen zu übergeben ", was nach Strukturzuweisung klingt, und auf den Rest des Satzes über die Benennung der Strukturmitglieder der Gewerkschaft.
Peter Cordes
1
@PeterCordes: Ich habe darüber nachgedacht, als Referenz zu übergeben, wie später unten dargestellt. Ich finde es umständlich, dass die Struktur des OP sie daran hindert, "Module" zu erstellen, von denen statisch nachgewiesen werden kann, dass sie nur auf eine bestimmte Architektur zugreifen (indem sie sich auf die spezifische beziehen struct), und dass unionsich diese letztendlich überall ausbreiten, selbst in architekturspezifischen Bits, die könnte sich weniger um die anderen kümmern.
Matthieu M.
0

Obwohl ich damit einverstanden bin, dass Strukturen nicht für den Zugriff auf MCU-E / A-Ports verwendet werden sollten, kann die ursprüngliche Frage folgendermaßen beantwortet werden:

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

Sie können ersetzen müssen __attribute__((packed))mit #pragma packoder ähnlich je nach Compiler - Syntax.

Das Mischen von privaten und öffentlichen Mitgliedern in einer Struktur führt normalerweise dazu, dass das Speicherlayout vom C ++ - Standard nicht mehr garantiert wird. Wenn jedoch alle nicht statischen Elemente einer Struktur privat sind, wird sie weiterhin als POD / Standard-Layout betrachtet, ebenso wie Strukturen, die sie einbetten.

Aus irgendeinem Grund gibt gcc eine Warnung aus, wenn ein Mitglied einer anonymen Struktur privat ist, sodass ich ihm einen Namen geben musste. Wenn Sie es alternativ in eine weitere anonyme Struktur einbinden, wird die Warnung ebenfalls entfernt (dies kann ein Fehler sein).

Beachten Sie, dass das spacerMitglied selbst nicht privat ist, sodass auf folgende Weise auf Daten zugegriffen werden kann:

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

Ein solcher Ausdruck sieht jedoch wie ein offensichtlicher Hack aus und würde hoffentlich nicht ohne einen wirklich guten Grund verwendet werden, geschweige denn als Fehler.

Jack White
quelle
1
Benutzer können keine Bezeichner in einem Namespace deklarieren, der an einer beliebigen Stelle im Namen doppelte Unterstriche enthält (in C ++ oder nur am Anfang in C). Dadurch wird der Code schlecht geformt. Diese Namen sind der Implementierung vorbehalten und können daher theoretisch auf schrecklich subtile und launische Weise mit Ihren in Konflikt geraten. Der Compiler ist jedoch nicht verpflichtet, Ihren Code aufzubewahren, wenn er diese enthält. Solche Namen sind kein schneller Weg, um 'interne' Namen für Ihren eigenen Gebrauch zu erhalten.
underscore_d
Danke, habe es behoben.
Jack White
-1

Anti-Lösung.

TUN SIE DAS NICHT: Mischen Sie private und öffentliche Felder.

Vielleicht ist ein Makro mit einem Zähler zum Generieren eindeutiger Variablennamen hilfreich?

#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
quelle
4
Schlechte Idee; Sie haben gerade die Bestellung der Mitglieder von oben nach unten verloren .
Leichtigkeitsrennen im Orbit
3
OK. Wenn es niemandem etwas ausmacht, lasse ich die Antwort als was nicht zu tun ist.
Robert Andrzejuk
4
@NicHartley Angesichts der Anzahl der Antworten stehen wir einer "Forschungs" -Frage nahe. In der Forschung ist das Wissen über Sackgassen immer noch Wissen, es vermeidet, dass andere den falschen Weg einschlagen. +1 für den Mut.
Oliv
1
@Oliv Und ich -1, weil das OP etwas erforderte, verstieß diese Antwort gegen die Anforderung, und daher ist es eine schlechte Antwort. Ich habe in beiden Kommentaren ausdrücklich kein positives oder negatives Werturteil über die Person abgegeben - nur über die Antwort. Ich denke, wir können uns beide einig sein, dass es schlecht ist. Was dies über die Person aussagt, ist für diese Site nicht thematisch. (Obwohl IMO jeder, der bereit ist, sich etwas Zeit zu nehmen, um eine Idee beizutragen, etwas richtig macht, auch wenn die Idee nicht aufgeht)
Fund Monica's Lawsuit
2
Ja, das ist die falsche Antwort. Aber ich befürchte, einige Leute könnten auf die gleiche Idee kommen. Aufgrund des Kommentars und des Links habe ich gerade etwas gelernt, das sind keine negativen Punkte für mich.
Robert Andrzejuk