web-dev-qa-db-ja.com

C++クラスのメモリ構造内に "spacer"を作成する方法

問題

低レベルのベアメタル埋め込み コンテキストでは、ユーザがそのようなメモリ位置にアクセスするのを禁止するために、C++構造内に名前なしでメモリ内に空白スペースを作成したいと思います。

現時点で、私はこれを達成するために3つの単語の代わりになる醜いuint32_t :96;ビットフィールドを配置しましたが、GCC(Bitfieldは大きすぎてuint32_tに収まりません)から警告を出します。

それはうまく動作しますが、あなたがそれらの警告の数百でライブラリを配布したいとき、それはあまりきれいではありません...

どうすれば正しくできますか?

そもそもなぜ問題があるのでしょうか。

私が取り組んでいるプロジェクトは、マイクロコントローラライン全体のさまざまなペリフェラルのメモリ構造を定義することです(STMicroelectronics STM32)。そうするために、結果は目的とするマイクロ コントローラに依存して全てのレジスタを定義するいくつかの構造体の和集合を含むクラスです。

非常に単純なペリフェラルの簡単な例は次のとおりです。汎用入出力(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;
    };
};

すべてのGPIO_MAPx_YYYがマクロで、uint32_t :32またはレジスタ型(専用の構造体)として定義されている場合。

ここではうまく動作するuint32_t :192;を見ていますが、それは警告を引き起こします。

私が今まで考えてきたこと

私はそれをいくつかのuint32_t :32;6)に取り替えたかもしれませんが、私はuint32_t :1344;(42)を含む極端な場合があります(とりわけ)。そのため、構造生成がスクリプト化されていても、8Kの他のものの上に約100行を追加したくはありません。

正確な警告メッセージは、次のようなものです。width of 'sool::ll::GPIO::<anonymous union>::<anonymous struct>::<anonymous>' exceeds its type(私はそれがどれほど曖昧かが好きです)。

私はむしろ_(notではなく、単に警告を取り除くことでこれを解決したいのですが、

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

私はTheRightFlagを見つけたら解決策かもしれません…。しかし、 このスレッド で指摘されているように、この悲しいコード部分を含むgcc/cp/class.c

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

これは、この警告を削除するための-Wxxxフラグがないことを示しています...

93
J Faucher

複数の隣接する匿名ビットフィールドを使用してください。だから代わりに:

    uint32_t :160;

たとえば、次のようになります。

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

あなたが匿名になりたいそれぞれの登録簿に1つ。

あなたがそれを埋めるために大きなスペースを持っているならば、それはより明確であり、そして単一の32ビットスペースを繰り返すためにマクロを使う傾向が少ないエラーであるかもしれない。例えば、

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

その場合、1344(42 * 32ビット)スペースを追加できます。

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

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

あなたはGPIO名前空間のために自動補完を得ます、そしてダミーパディングの必要はありません。たとえ、各レジスタのアドレスを見ることができるので、何が起こっているのか、より明確です。コンパイラのパディング動作にまったく頼る必要はありません。

44
geza

組み込みシステムの分野では、構造体を使用するか、レジスタアドレスへのポインタを定義することによってハードウェアをモデル化できます。

(組み込みシステム用の多くのコンパイラーには構造体をパックするためのプラグマがありますが)コンパイラーは位置合わせの目的でメンバー間にパディングを追加することを許可されているので、構造によるモデリングはお勧めできません。

例:

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

配列表記を使うこともできます。

uint16_t status = UART1[UART_STATUS_OFFSET];  

構造体のIMHOを使用する必要がある場合は、アドレスをスキップする最善の方法は、メンバーを定義してそれにアクセスしないことです。

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

私たちのプロジェクトの1つでは、さまざまなベンダーの定数と構造体の両方を使用しています(ベンダー1は定数を使用し、ベンダー2は構造体を使用します)。

20
Thomas Matthews

gezaは、あなたが本当にこのためにクラスを使用したくないという権利を持っています。

しかし、もしあなたが主張するならば、 n bytesの幅の未使用のメンバを追加するための最良の方法は、単純にそうすることです:

char unused[n];

クラスのメンバに任意のパディングが追加されないように実装固有のプラグマを追加した場合、これは機能します。


GNU C/C++(gcc、clang、およびその他の同じ拡張子をサポートするもの)の場合、属性を置くための有効な場所の1つは次のとおりです。

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

(例 Godboltコンパイラエクスプローラーでoffsetof(GPIO, b) = 7バイトを表示)

@ Cliffordと@Adam Kotwasinskiの答えを詳しく説明します。

#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

クリフォードの答えを拡大するために、あなたはいつでも匿名ビットフィールドをマクロアウトすることができます。

だから代わりに

uint32_t :160;

つかいます

#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

そしてそれを次のように使う

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

残念ながら、あなたが持っているバイト数と同じだけのEMPTY_32_Xバリアントが必要になるでしょう。

7

もう少し構造を導入することが有益であると思います。これにより、スペーサーの問題を解決できます。

バリアントに名前を付ける

フラットな名前空間はいいのですが、問題はあなたが複雑なフィールドの集まりになってしまい、すべての関連フィールドを一緒に渡す単純な方法ではなくなることです。さらに、匿名共用体で匿名構造体を使用することで、構造体自体への参照を渡したり、テンプレートパラメータとして使用したりすることはできません。

したがって、最初のステップとして、structの分解を検討します。

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

そして最後に、グローバルヘッダ:

// Gpio.h
#pragma once

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

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

今、私はvoid special_map0(Gpio:: Map0 volatile& map);を書くことができるだけでなく、一目ですべての利用可能なアーキテクチャの概要を得ることができます。

単純なスペーサー

定義が複数のヘッダーに分割されているため、ヘッダーは個々にはるかに扱いやすくなっています。

したがって、あなたの要件を正確に満たすための私の最初のアプローチは、繰り返しstd::uint32_t:32;を使用することです。はい、既存の8k行に数100行を追加しますが、各ヘッダーは個々に小さいので、それほど悪くないかもしれません。

あなたがもっとエキゾチックな解決策を検討しても構わないと思っているなら、ただし...

$を導入しています。

$がC++識別子に有効な文字であることはあまり知られていません。それは(数字とは異なり)実行可能な開始文字でさえあります。

ソースコードに現れる$はおそらく眉を上げるでしょう、そして$$$$は間違いなくコードレビューの間に注目を集めるでしょう。これはあなたが簡単に利用できるものです:

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

コミットされたC++コードで$$$$を探し、そのようなコミットを拒否する単純な "lint"をpre-commitフックとして、またはあなたのCIにまとめることさえできます。

1
Matthieu M.

大きなスペーサーを32ビットのグループとして定義します。

#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

構造体をMCUのI/Oポートアクセスに使用してはいけないことに同意しますが、元の質問には次のように回答できます。

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

コンパイラの構文によっては、__attribute__((packed))#pragma packなどに置き換える必要があるかもしれません。

構造体にプライベートメンバとパブリックメンバを混在させると、通常、メモリレイアウトがC++標準で保証されなくなります。ただし、構造体の all non-staticメンバがprivateである場合、それでもPOD /標準レイアウトと見なされ、それらを埋め込む構造体も同様です。

何らかの理由で、匿名の構造体のメンバーが非公開の場合、gccは警告を出しますので、名前を付ける必要がありました。あるいは、それをさらに別の無名の構造体にラップすることでも警告を取り除けます(これはバグかもしれません)。

spacerメンバーはそれ自体がプライベートではないので、データには引き続きこの方法でアクセスできます。

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

しかしながら、そのような表現は明らかなハックのように見えます、そして間違いなく、間違いなく、本当に正当な理由がなければ使用されないことを願います。

0
Jack White