it-swarm.dev

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

Die Angelegenheit

In einem low-Level-Bare-Metal-Embedded-Kontext möchte ich einen leeren Speicherplatz innerhalb einer C++ - Struktur und ohne Namen erstellen, um dem Benutzer den Zugriff auf diesen Speicherplatz zu verbieten.

Im Moment habe ich es erreicht, indem ich ein hässliches uint32_t :96;-Bitfeld gesetzt habe, das bequem drei Wörter ersetzen kann, aber es wird eine Warnung von GCC (Bitfield zu groß, um in uint32_t zu passen) ausgegeben, 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 aus der Definition der Speicherstruktur verschiedener Peripheriegeräte einer gesamten Mikrocontroller-Linie (STMicroelectronics STM32). Das Ergebnis ist eine Klasse, die eine Vereinigung mehrerer Strukturen enthält, die je nach angesteuertem Mikrocontroller alle Register definieren.

Ein einfaches Beispiel für ein recht einfaches Peripheriegerät ist das folgende: ein allgemeiner Eingang/Ausgang (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;
    };
};

Dabei ist GPIO_MAPx_YYY ein Makro, das entweder als uint32_t :32 oder als Registertyp (dedizierte Struktur) definiert ist.

Hier sehen Sie den uint32_t :192;, der gut funktioniert, aber eine Warnung auslöst.

Was ich bisher gedacht habe:

Ich habe es vielleicht durch mehrere uint32_t :32; ersetzt (6 hier), aber ich habe einige extreme Fälle, in denen ich uint32_t :1344; (42) (unter anderem) habe. Ich würde also lieber nicht einhundert Zeilen über 8.000 Zeilen hinzufügen, obwohl die Strukturgenerierung über Skripte verfügt.

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

Ich würde eher nicht lösen, 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 TheRightFlag finde. Wie jedoch in diesem Thread , gcc/cp/class.c mit diesem traurigen Codeteil hervorgehoben:

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

Was sagt uns, dass es kein -Wxxx Flag gibt, um diese Warnung zu entfernen ...

93
J Faucher

Verwenden Sie mehrere nebeneinander liegende 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 jede Registrierung, die Sie anonym sein möchten.

Wenn Sie viel Platz zum Füllen haben, ist es möglicherweise klarer und weniger fehleranfällig, Makros zur Wiederholung des einzelnen 32-Bit-Bereichs 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 Leerzeichen von 1344 (42 * 32 Bit) hinzugefügt werden:

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

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

44
geza

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

Die Modellierung nach Struktur wird nicht empfohlen, da der Compiler zu Ausrichtungszwecken Abstand 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).

20
Thomas Matthews

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

Wenn Sie darauf bestehen, besteht die beste Möglichkeit, ein nicht verwendetes Mitglied mit der Breite von n Bytes hinzuzufügen, einfach:

char unused[n];

Wenn Sie ein implementierungsspezifisches Pragma hinzufügen, um zu verhindern, dass den Klassenmitgliedern willkürliche Auffüllung hinzugefügt wird, kann dies funktionieren.


Für GNU C/C++ (gcc, clang und andere, die die gleichen Erweiterungen unterstützen) lautet einer der gültigen Orte 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 zeigt offsetof(GPIO, b) = 7 Bytes.)

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

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

Also statt 

uint32_t :160;

benutzen

#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 benutzen Sie 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_X-Varianten, wie viele Bytes Sie haben :( Trotzdem können Sie einzelne Deklarationen in Ihrer Struktur haben.

7

Ich denke, es wäre nützlich, etwas mehr Struktur einzuführen. was wiederum das Problem der Abstandhalter lösen kann.

Benennen Sie die Varianten

Während flache Namespaces Nice sind, besteht das Problem darin, dass Sie mit einer bunten Sammlung von Feldern enden und keine einfache Möglichkeit, alle verwandten Felder gemeinsam zu übergeben. Durch die Verwendung anonymer Strukturen in einer anonymen Vereinigung können Sie außerdem keine Verweise auf die Strukturen selbst übergeben oder als Vorlagenparameter verwenden.

Als ersten Schritt würde ich erwägen, das struct auszubrechen:

// 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 void special_map0(Gpio:: Map0 volatile& map); schreiben und auf einen Blick einen Überblick über alle verfügbaren Architekturen erhalten.

Einfache Abstandhalter

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

Mein erster Ansatz, Ihre Anforderungen genau zu erfüllen, wäre daher, bei wiederholtem std::uint32_t:32; zu bleiben. Ja, es werden einige 100s-Zeilen zu den vorhandenen 8k-Zeilen hinzugefügt. Da jedoch jeder Header einzeln kleiner ist, ist er möglicherweise nicht so schlecht.

Wenn Sie bereit sind, weitere exotische Lösungen in Betracht zu ziehen ...

$ Einführen.

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

Ein $, der im Quellcode erscheint, würde wahrscheinlich die Augenbrauen hochziehen, und $$$$ wird definitiv bei Code-Reviews auf sich aufmerksam machen. 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 ein einfaches "Fussel" als Pre-Commit-Hook oder in Ihrem CI zusammenstellen, das im festgeschriebenen C++ - Code nach $$$$ sucht und solche Commits ablehnt.

1
Matthieu M.

Definieren eines großen Abstandshalters als Gruppen von 32 Bits.

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

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

Abhängig von Ihrer Compilersyntax müssen Sie möglicherweise __attribute__((packed)) durch #pragma pack oder ähnliches ersetzen.

Das Mischen von privaten und öffentlichen Mitgliedern in einer Struktur führt normalerweise dazu, dass das Speicherlayout durch den C++ - Standard nicht mehr garantiert wird. Wenn jedoch all nicht-statische Member einer Struktur privat sind, wird dies immer noch als POD/Standardlayout betrachtet, und auch Strukturen, in die sie eingebettet werden.

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

Beachten Sie, dass das Mitglied spacer nicht selbst privat ist, so dass auf Daten immer noch auf diese Weise zugegriffen werden kann: 

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

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

0
Jack White