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_YYY
ein Makro ist, das entweder als uint32_t :32
oder 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.c
mit diesem traurigen Codeteil:
warning_at (DECL_SOURCE_LOCATION (field), 0,
"width of %qD exceeds its type", field);
Was uns sagt, dass es keine -Wxxx
Flagge gibt, um diese Warnung zu entfernen ...
quelle
char unused[12];
und so weiter?uint32_t :192;
.:42*32
anstelle von:1344
Antworten:
Verwenden Sie mehrere benachbarte anonyme Bitfelder. Also statt:
Zum Beispiel hätten Sie:
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:
Dann kann ein 1344 (42 * 32 Bit) Speicherplatz wie folgt hinzugefügt werden:
quelle
uint32_t :1344;
ist an Ort und Stelle), so dass ich lieber nicht diesen Weg gehen müsste ...Wie wäre es mit einem C ++ - ish Weg?
Sie erhalten aufgrund des
GPIO
Namespace 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.quelle
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.)static volatile uint32_t &MAP0_MODER
, nichtinline
. Eineinline
Variable wird nicht kompiliert. ( Vermeidetstatic
statischen Speicher für den Zeiger undvolatile
ist genau das, was Sie für MMIO wünschen, um die Beseitigung von Dead-Stores oder die Optimierung von Write / Readback zu vermeiden.)static
tut genauso gut für diesen Fall. Vielen Dank für die Erwähnungvolatile
, ich werde es meiner Antwort hinzufügen (und Inline in statisch ändern, damit es für Pre C ++ 17 funktioniert).GPIOA::togglePin()
.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:
Sie können auch die Array-Notation verwenden:
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:
In einem unserer Projekte haben wir sowohl Konstanten als auch Strukturen von verschiedenen Anbietern (Anbieter 1 verwendet Konstanten, während Anbieter 2 Strukturen verwendet).
quelle
static
zu einer Struktur machen, vorausgesetzt, dass die automatische Vervollständigung statische Elemente anzeigen kann. Wenn nicht, können es auch Inline-Member-Funktionen sein.public:
undprivate:
so oft Sie möchten verwenden, um die richtige Reihenfolge der Felder zu erhalten.)public
als auchprivate
nicht 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.)volatile
diese Deklarationen übrigens nicht für speicherabgebildete E / A-Register.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 :
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:
(Beispiel im Godbolt-Compiler-Explorer mit
offsetof(GPIO, b)
= 7 Bytes.)quelle
Um die Antworten von @ Clifford und @Adam Kotwasinski zu erweitern:
quelle
Um die Antwort von Clifford zu erweitern, können Sie die anonymen Bitfelder jederzeit makroauslesen.
Also statt
verwenden
Und dann benutze es gerne
Leider benötigen Sie so viele
EMPTY_32_X
Varianten wie Sie Bytes haben :( Trotzdem können Sie einzelne Deklarationen in Ihrer Struktur haben.quelle
#define EMPTY_32_2 EMPTY_32_1; EMPTY_32_1
und#define EMPTY_32_3 EMPTY_32_2; EMPTY_32_1
soDefinieren eines großen Abstandshalters als Gruppen von 32 Bit.
quelle
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 auszubrechen
struct
:Und schließlich der globale Header:
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: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.quelle
GPIO_MAP0_MODER
es vermutlichvolatile
.) 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.$$$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. (Oderzz$$$padding
um es nachGPIO...
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.)volatile
Qualifikationsmerkmal 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.struct
), und dassunion
sich diese letztendlich überall ausbreiten, selbst in architekturspezifischen Bits, die könnte sich weniger um die anderen kümmern.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:
Sie können ersetzen müssen
__attribute__((packed))
mit#pragma pack
oder ä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
spacer
Mitglied selbst nicht privat ist, sodass auf folgende Weise auf Daten zugegriffen werden kann: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.
quelle
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?
quelle