Wie erstelle ich einen “Abstandshalter” in einer C++-Klassenspeicherstruktur?

Lesezeit: 11 Minuten

Benutzeravatar von J Faucher
J Faucher

Die Angelegenheit

In einem Low-Level-Bare-Metal eingebettet Kontext möchte ich innerhalb einer C++-Struktur und ohne Namen ein Leerzeichen im Speicher erstellen, um dem Benutzer den Zugriff auf diesen Speicherort zu verbieten.

Im Moment habe ich es erreicht, indem ich ein hässliches gesetzt habe uint32_t :96; bitfield, das praktischerweise den Platz von drei Wörtern einnehmen wird, aber es wird eine Warnung von GCC auslösen (Bitfield too large to fit in uint32_t), was ziemlich legitim ist.

Obwohl es gut funktioniert, ist es 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 ganzen Mikrocontroller-Linie (STMicroelectronics STM32) zu definieren. Dazu ist das Ergebnis eine Klasse, die eine Vereinigung mehrerer Strukturen enthält, die alle Register definieren, abhängig vom anvisierten Mikrocontroller.

Ein einfaches Beispiel für ein ziemlich einfaches Peripheriegerät ist das Folgende: ein General Purpose Input/Output (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;
    };
};

Wo alle GPIO_MAPx_YYY ist ein Makro, definiert entweder als uint32_t :32 oder der Registertyp (eine dedizierte Struktur).

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

Was ich mir bisher überlegt habe:

Ich hätte es vielleicht durch mehrere ersetzt uint32_t :32; (6 hier), aber ich habe einige extreme Fälle, in denen ich habe uint32_t :1344; (42) (unter anderem). Daher würde ich lieber nicht etwa hundert Zeilen zu 8k anderen hinzufügen, obwohl die Strukturgenerierung per Skript erfolgt.

Die genaue Warnmeldung lautet etwa 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 lieber nicht lösen Sie dies, indem Sie einfach die Warnung entfernen, aber 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 jedoch 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 gibt -Wxxx markieren, um diese Warnung zu entfernen…

  • hast du bedacht char unused[12]; usw?

    – MM

    1. November 2018 um 21:56 Uhr

  • Ich würde die Warnung einfach unterdrücken. [class.bit]/1 garantiert das Verhalten von uint32_t :192;.

    – NathanOliver

    1. November 2018 um 22:07 Uhr

  • @NathanOliver würde ich auch gerne, aber es scheint, dass diese Warnung nicht unterdrückbar ist (mit GCC) oder ich habe nicht gefunden, wie das geht. Außerdem ist es immer noch kein sauberer Weg (aber es wäre ziemlich befriedigend). Ich habe es geschafft, das richtige “-W” -Flag zu finden, aber ich habe es nicht geschafft, 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

    1. November 2018 um 22:13 Uhr


  • Übrigens kannst du schreiben :42*32 Anstatt von :1344

    – MM

    1. November 2018 um 22:15 Uhr

  • Versuchen Sie dies, um Warnungen zu unterdrücken? gcc.gnu.org/onlinedocs/gcc/…

    – Hitobat

    1. November 2018 um 22:26 Uhr

Benutzeravatar von geza
geza

Wie wäre es mit einem C++-artigen 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 die automatische Vervollständigung aufgrund der GPIO Namespace, und es ist kein Dummy-Padding erforderlich. Es ist sogar klarer, was vor sich geht, da Sie die Adresse jedes Registers sehen können, Sie müssen sich überhaupt nicht auf das Füllverhalten des Compilers verlassen.

  • Dies könnte möglicherweise weniger gut optimieren als eine Struktur für den Zugriff auf mehrere MMIO-Register von derselben Funktion. Mit einem Zeiger auf die Basisadresse in einem Register kann der Compiler Lade-/Speicherbefehle mit sofortigen Verschiebungen verwenden, wie z 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 Literal-Pool, obwohl einige von ihnen als rotierte Direktwerte in der Thumb-Codierung dargestellt werden können.)

    – Peter Cordes

    2. November 2018 um 2:12 Uhr

  • 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_MODERnicht inline. Ein inline Variable kompiliert nicht. (static vermeidet jeglichen statischen Speicher für den Zeiger, und volatile ist genau das, was Sie für MMIO wollen, um die Eliminierung von Dead-Stores oder die Optimierung von Write / Read-Back zu vermeiden.)

    – Peter Cordes

    2. November 2018 um 2:59 Uhr

  • @PeterCordes: Inline-Variablen sind eine neue C++17-Funktion. Aber du hast recht, static funktioniert in diesem Fall genauso gut. Danke für die Erwähnung volatilefüge ich es meiner Antwort hinzu (und ändere Inline in statisch, damit es für Pre-C++ 17 funktioniert).

    – geza

    2. November 2018 um 3:07 Uhr

  • Dies ist kein genau definiertes Verhalten, siehe diesen Twitter-Thread und vielleicht ist dieser nützlich

    – Shafik Yaghmour

    2. November 2018 um 6:39 Uhr

  • @JFaucher: Erstellen Sie so viele Namespaces, wie Sie Strukturen haben, und verwenden Sie eigenständige Funktionen in diesem Namespace. Also wirst du haben GPIOA::togglePin().

    – geza

    2. November 2018 um 13:44 Uhr

Benutzeravatar von Clifford
Clifford

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, das Sie anonym halten möchten.

Wenn Sie große Leerzeichen zu füllen haben, kann es klarer und weniger fehleranfällig sein, Makros zu verwenden, um das einzelne 32-Bit-Leerzeichen zu wiederholen. 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) Leerzeichen wie folgt hinzugefügt werden:

struct
{
    ...
    REPEAT_32(uint32_t :32;) 
    REPEAT_8(uint32_t :32;) 
    REPEAT_2(uint32_t :32;)
    ...
};

  • Danke für die Antwort. Ich habe das bereits in Betracht gezogen, aber es würde in einigen meiner Dateien über 200 Zeilen hinzufügen (uint32_t :1344; ist im Ort) also würde ich diesen Weg lieber nicht gehen müssen…

    – J Faucher

    1. November 2018 um 23:11 Uhr

  • @JFaucher Eine mögliche Lösung für Ihre Zeilenanzahlanforderung hinzugefügt. Wenn Sie solche Anforderungen haben, können Sie sie in der Frage erwähnen, um zu vermeiden, Antworten zu erhalten, die diese nicht erfüllen.

    – Clifford

    2. November 2018 um 12:30 Uhr

  • Danke für die Bearbeitung und Entschuldigung, dass ich die Sache mit der Zeilenzahl nicht angegeben habe. Mein Punkt ist, dass es schon schmerzhaft ist, in meinen Code einzutauchen, da es viele Zeilen gibt und ich es lieber vermeiden würde, zu viel mehr hinzuzufügen. Daher habe ich gefragt, ob jemand einen “sauberen” oder “offiziellen” Weg kennt, um die Verwendung benachbarter anonymer Bitfelder zu vermeiden (auch wenn das gut funktioniert). Der Makro-Ansatz scheint mir jedoch in Ordnung zu sein. Übrigens, haben Sie in Ihrem Beispiel keinen 36 * 32-Bit-Speicherplatz?

    – J Faucher

    2. November 2018 um 13:15 Uhr

  • @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. Abgesehen davon, dass Sie Register “verstecken”, machen Sie sich selbst Wartungsarbeiten, wenn Sie später darauf zugreifen müssen. Sie wissen natürlich, dass alle STM32-Geräte bereits einen vom Hersteller bereitgestellten Register-Map-Header haben? Es wäre weitaus weniger fehleranfällig, das zu verwenden.

    – Clifford

    2. November 2018 um 14:13 Uhr


  • Ich stimme Ihnen zu, und um fair zu sein, denke ich, dass ich eine der beiden Methoden anwenden werde, die in Ihrer Antwort angegeben sind. Ich wollte nur sicher sein, dass C++ keine bessere Lösung bietet, bevor ich dies tue. Ich bin mir bewusst, dass ST diese Header bereitstellt, aber diese basieren auf der massiven Verwendung von Makros und bitweisen Operationen. Mein Projekt besteht darin, ein C++-Äquivalent zu diesen Headern zu erstellen, die es sein werden weniger fehleranfällig (unter Verwendung von Enum-Klassen, 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

    2. November 2018 um 14:33 Uhr


Im Bereich eingebetteter Systeme können Sie Hardware modellieren, indem Sie entweder eine Struktur verwenden oder Zeiger auf die Registeradressen definieren.

Die Modellierung nach Struktur wird nicht empfohlen, da der Compiler zu Ausrichtungszwecken Padding zwischen Membern 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 verwenden müssen, wäre IMHO die beste Methode zum Überspringen von Adressen, 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).

  • Danke für deine Antwort. Ich entscheide mich jedoch für einen strukturierten Ansatz, um die Arbeit des Benutzers zu erleichtern, wenn er eine automatische Vervollständigungsfunktion erhält (es werden nur die richtigen Attribute angezeigt) und ich möchte dem Benutzer nicht die reservierten Slots als “zeigen”. wies in einem Kommentar zu meinem ersten Beitrag darauf hin.

    – J Faucher

    1. November 2018 um 23:01 Uhr

  • Sie können das immer noch haben, indem Sie die oben genannte Adresse machen static Mitglieder einer Struktur, vorausgesetzt, dass die automatische Vervollständigung statische Mitglieder anzeigen kann. Wenn nicht, können es auch Inline-Member-Funktionen sein.

    – Phil1970

    2. November 2018 um 1:09 Uhr

  • @JFaucher Ich bin keine Person für eingebettete Systeme und habe dies nicht getestet, aber würde das Problem der automatischen Vervollständigung nicht gelöst, indem das reservierte Mitglied als privat deklariert würde? (Sie können private Member in einer Struktur deklarieren und verwenden public: und private: beliebig oft, um die richtige Reihenfolge der Felder zu erhalten.)

    – N. Jungfrau

    2. November 2018 um 6:13 Uhr


  • @Nathaniel: Nicht so; wenn eine Klasse beides hat public und private nicht statische Datenmember, dann ist es kein a Standard-Layouttyp, also bietet es nicht die Bestellgarantien, an die Sie denken. (Und ich bin mir ziemlich sicher, dass der Anwendungsfall des OP einen Standardlayouttyp erfordert.)

    – ruach

    2. November 2018 um 6:42 Uhr


  • Nicht vergessen volatile auf diesen Deklarationen, BTW, für speicherabgebildete E / A-Register.

    – Peter Cordes

    2. November 2018 um 9:11 Uhr

Leichtigkeitsrennen im Benutzeravatar von Orbit
Leichtigkeitsrennen im Orbit

geza hat Recht, dass Sie dafür wirklich keine Klassen verwenden wollen.

Aber, wenn Sie darauf bestehen würden, fügen Sie am besten ein unbenutztes Mitglied hinzu n Bytes Breite, ist einfach zu tun:

char unused[n];

Wenn Sie ein implementierungsspezifisches Pragma hinzufügen, um das Hinzufügen von willkürlichem Padding zu den Membern der Klasse zu verhindern, kann dies funktionieren.


Für GNU C/C++ (gcc, clang und andere, die dieselben Erweiterungen unterstützen) ist einer der gültigen Orte, um das Attribut zu platzieren:

#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 zeigt offsetof(GPIO, b) = 7 Bytes.)

Benutzeravatar von mosvy
mosvy

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

  • Ich habe eine Variante Ihres Vorschlags in meine Antwort im Folgenden aufgenommen Bedarf in einem Kommentar. Kredit wem Kredit gebührt.

    – Clifford

    2. November 2018 um 12:34 Uhr

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

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 werden Sie so viele brauchen EMPTY_32_X Varianten so viele Bytes Sie haben 🙁 Trotzdem können Sie einzelne Deklarationen in Ihrer Struktur haben.

  • Ich habe eine Variante Ihres Vorschlags in meine Antwort im Folgenden aufgenommen Bedarf in einem Kommentar. Kredit wem Kredit gebührt.

    – Clifford

    2. November 2018 um 12:34 Uhr

Benutzeravatar von jxh
jxh

Um einen großen Abstandshalter als Gruppen von 32 Bits zu definieren.

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

1420370cookie-checkWie erstelle ich einen “Abstandshalter” in einer C++-Klassenspeicherstruktur?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy