C ++クラスのメモリ構造に「スペーサー」を作成するにはどうすればよいですか?


94

問題

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

現在、私はuint32_t :96;3ワードの代わりになる醜いビットフィールドを配置することでそれを達成しましたが、GCC(ビットフィールドが大きすぎて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;
    };
};

all 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(私はそれがどれほど怪しいかが好きです)

警告を削除するだけで解決できませんが、

#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、この警告を削除するフラグがないことを示しています...


26
あなたは考えchar unused[12];ましたか?
MM

3
警告を抑制します。 [class.bit] / 1はの動作を保証しますuint32_t :192;
NathanOliver 2018年

3
@NathanOliver私も喜んでいますが、この警告は抑制できないようです(GCCを使用)、またはその方法を見つけられませんでした。さらに、それはまだクリーンな方法ではありません(しかし、それはかなり満足できるでしょう)。正しい "-W"フラグを見つけることができましたが、自分のファイルだけに適用することはできませんでした(ユーザーが自分の作業に対してこの種の警告を削除したくない)
J Faucher

3
ところで、:42*32代わりに書くことができます:1344
MM

1
これを試して警告を抑制しますか?gcc.gnu.org/onlinedocs/gcc/...
Hitobat

回答:


36

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

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

答えてくれてありがとう。私はすでにそれを考えました、しかしそれは私のファイルのいくつかに200行以上を追加するでしょう(uint32_t :1344;その場所にあります)ので、私はこのように行く必要はありません...
J Faucher

1
@JFaucherは、行数の要件に可能な解決策を追加しました。そのような要件がある場合は、質問にそれらを記載して、要件を満たさない回答を取得しないようにすることができます。
クリフォード

編集に感謝し、行数のことを述べていないのは残念。私の要点は、行がたくさんあるので、コードはすでに飛び込むのが苦痛であり、それ以上追加することは避けたいということです。したがって、隣接する匿名ビットフィールドの使用を回避する「クリーン」または「公式」の方法を誰かが知っているかどうかを尋ねていました(たとえそれがうまくいくとしても)。でも、マクロなアプローチは私にはいいようです。ところで、あなたの例では、36 * 32ビットのスペースがありませんか?
J Faucher

@JFaucher-修正されました。レジスタの数が多いため、I / Oレジスタマッピングファイルは必然的に大きくなります。ハードウェアは定数であるため、通常は一度書き込むだけで、メンテナンスは問題になりません。レジスターを「非表示」にすることを除いて、後でそれらにアクセスする必要がある場合に、自分で保守作業を行うことになります。もちろん、すべてのSTM32デバイスにすでにベンダーから提供されたレジスタマップヘッダーがあることを知っていますか?これを使用することで、エラーが発生しにくくなります。
クリフォード、

2
私はあなたに同意します、そして、公平を期すために、私はあなたの答えに表示されたこれら二つの方法のうちの一つで行くと思います。そうする前に、C ++がより良いソリューションを提供しないことを確認したかっただけです。STがこれらのヘッダーを提供することはよく知っていますが、これらはマクロとビット単位の演算の大量の使用に基づいて構築されています。私のプロジェクトでは、C ++これらのヘッダと等価になります構築することで以下のエラーを起こしやすい(列挙型クラス、ビットフィールドを使用してなどを)。そのため、スクリプトを使用してCMSISヘッダーをC ++構造に「変換」します(そしてSTファイルにエラーがいくつか見つかりました)
J Faucher

45

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名前空間のためにオートコンプリートが得られ、ダミーのパディングは必要ありません。さらに、各レジスタのアドレスを確認できるため、コンパイラのパディング動作にまったく依存する必要がないので、状況がより明確になります。


1
これは、同じ関数から複数のMMIOレジスターにアクセスするための構造体ほど最適化しない可能性があります。レジスターのベースアドレスへのポインターを使用すると、コンパイラーは、のようldr r0, [r4, #16]に即時変位のあるロード/ストア命令を使用できますが、コンパイラーは、各アドレスが個別に宣言されていると、その最適化を見逃す可能性が高くなります。GCCはおそらく、各GPIOアドレスを個別のレジスタにロードします。(リテラルプールから、ただしそれらのいくつかはThumbエンコーディングでローテーションされたイミディエートとして表すことができます。)
Peter Cordes

4
私の心配は根拠のないものであることがわかりました。ARM GCCもこの方法で最適化します。 godbolt.org/z/ztB7hi。ただしstatic volatile uint32_t &MAP0_MODER、必要ではないことに注意してくださいinlineinline変数はコンパイルされません。(staticポインターの静的ストレージvolatileを回避し、MMIOがデッドストアの削除または書き込み/読み戻しの最適化を回避するためにまさに必要なものです。)
Peter Cordes

1
@PeterCordes:インライン変数はC ++ 17の新しい機能です。しかし、あなたは正しいですstatic。この場合も同様にうまくいきます。を言及してくれてありがとうvolatile、私はそれを私の答えに追加します(そしてインラインを静的に変更しますので、C ++ 17以前で動作します)。
geza 2018年

2
これは厳密に定義された動作ではありません。このTwitterスレッドを参照してください。おそらくこれは役に立ちます
Shafik Yaghmour

1
@JFaucher:構造体と同じ数の名前空間を作成し、その名前空間でスタンドアロン関数を使用します。だから、あなたは持っているでしょうGPIOA::togglePin()
geza

20

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

コンパイラーはメンバーを整列させる目的でパディングを追加できるため、構造によるモデリングはお勧めできません(組み込みシステムの多くのコンパイラーには、構造をパックするためのプラグマがあります)。

例:

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は構造を使用しています)。


ご回答有難うございます。ただし、私はユーザーがオートコンプリート機能を取得したときにユーザーの作業を簡単にするために構造アプローチを使用することを選択し(正しい属性のみが表示されます)、ユーザーに予約済みスロットを「表示」したくありません。私の最初の投稿のコメントで指摘した。
Jフォシェ

staticオートコンプリートが静的メンバーを表示できると想定して、上記のアドレスメンバーを構造体のメンバーにすることで、それを維持できます。そうでない場合は、インラインメンバー関数にすることもできます。
Phil1970

@JFaucher私は組み込みシステムの専門家ではなく、これをテストしていませんが、予約済みメンバーを非公開と宣言することでオートコンプリートの問題を解決できませんか?(あなたは、構造体の民間メンバーを宣言することができ、あなたが使用することができますpublic:し、private:あなたが望むようにフィールドの正しい順序を取得するには、何回ものように。)
ナサニエル

1
@Nathaniel:そうではありません。クラスは両方持っている場合publicprivate非静的データメンバを、それはありません標準レイアウトのタイプ、それはあなたのしている思考の順序保証を提供していませんので、。(そして、OPのユースケースには標準のレイアウトタイプが必要だと確信しています。)
ruakh

1
volatileメモリマップI / Oレジスタについては、これらの宣言を忘れないでください。
Peter Cordes

13

このためにクラスを使用したくないというのは、gezaの権利です。

ただし、主張したい場合は、nバイト幅の未使用のメンバーを追加する最善の方法は、単にそうすることです。

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

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


9

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

コメントの追加要件に従って、あなたの提案の変形を私の回答に組み込んだ。クレジットが支払われるべきクレジット。
クリフォード

7

クリフォードの答えを拡張するために、いつでも匿名のビットフィールドをマクロ化できます。

だから代わりに

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バイト数と同じ数のバリアントが必要になります:(それでも、構造体で単一の宣言を持つことができます。


5
Boost CPPマクロを使用すると、再帰を使用して、必要なすべてのマクロを手動で作成する必要がなくなると思います。
Peter Cordes

3
それらをカスケードすることができます(プリプロセッサーの再帰制限までですが、通常はそれで十分です)。だから、#define EMPTY_32_2 EMPTY_32_1; EMPTY_32_1および#define EMPTY_32_3 EMPTY_32_2; EMPTY_32_1など
Miral

@PeterCordesはおそらくですが、タグはおそらくブースCとC ++の互換性が必要であることを示しています。
クリフォード

2
CとC ++は同じCプリプロセッサを使用します。必要なブーストヘッダーをCで利用できるようにする以外に問題はありません。CPPマクロは別のヘッダーに配置されます。
Peter Cordes

1

大きなスペーサーを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

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

バリアントに名前を付けます

フラットな名前空間は便利ですが、問題はフィールドの雑多なコレクションになり、関連するすべてのフィールドを一緒に渡す簡単な方法がないことです。さらに、匿名ユニオンで匿名構造体を使用すると、構造体自体への参照を渡したり、テンプレートパラメータとして使用したりすることはできません。

したがって、最初のステップとして、次のことを検討することを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行に数百行を追加しますが、各ヘッダーは個別に小さいので、それほど悪くはないかもしれません。

ただし、よりエキゾチックなソリューションを検討する場合は...

$のご紹介。

これはあまり知られていない事実で$あり、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);
};

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


1
OPの特定の使用例は、メモリマップされたI / Oレジスタをコンパイラに記述するためのものであることに注意してください。値全体で構造体全体をコピーすることは決して意味がありません。(そして、各メンバーGPIO_MAP0_MODERはおそらくvolatileです。)以前の匿名メンバーの参照またはテンプレートパラメータの使用は、おそらく有用かもしれません。そして、パディング構造体の一般的なケースについては、確かに。しかし、ユースケースは、OPが匿名のままにした理由を説明しています。
Peter Cordes

$$$padding##Index_[N_];オートコンプリートやデバッグ時に表示される場合に備えて、フィールド名をわかりやすくするために使用できます。(または、OPに従ってこの演習の全体のポイントがメモリマップされたI / Oロケーション名のオートコンプリートが優れているため、名前のzz$$$padding後に並べ替えることができますGPIO...。)
Peter Cordes

@PeterCordes:確認のためにもう一度回答をスキャンしましたが、コピーについての言及はありませんでした。volatile参照の修飾子を忘れていましたが、修正されました。命名については; OPまでお任せします。多くのバリエーション(パディング、予約済みなど)があり、オートコンプリートの「最適な」接頭辞でさえ手元のIDEに依存している可能性がありますが、ソートを微調整するという考えには感謝します。
Matthieu M.

私はに言及た「と一緒に、すべての関連分野を渡す簡単な方法」、その構造体の割り当て、および労働組合の構造体のメンバに名前を付ける程度の文章の残りの部分のような音。
Peter Cordes

1
@PeterCordes:後で説明するように、参照渡しを考えていました。OPの構造が、特定のアーキテクチャにのみアクセスすることが静的に証明できる(特定のへの参照を取得することにより)「モジュール」を作成することをOPの構造が防ぎ、アーキテクチャ固有のビットでさえどこにでも伝搬されることは不便だstructと思いunionます。他の人のことはあまり気にしないでしょう。
Matthieu M.

0

構造体を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 ++標準では保証されなくなります。ただし、構造体の静的でないすべてのメンバーがプライベートの場合でも、POD /標準レイアウトと見なされ、それらを埋め込む構造体も同様です。

何らかの理由で、匿名の構造体のメンバーがプライベートである場合、gccは警告を生成するため、名前を付ける必要がありました。あるいは、それをさらに別の匿名構造体にラップすると、警告が表示されなくなります(これはバグである可能性があります)。

注意spacerメンバーがプライベートそのものではないので、データはまだこのようにアクセスすることができます。

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

しかし、そのような表現は明らかなハックのように見え、うまくいけば、間違いとしては言うまでもなく、本当に正当な理由がない限り使用されないでしょう。


1
ユーザーは、名前のどこかに二重下線を含む名前空間で識別子を宣言することはできません(C ++の場合、またはCの先頭のみ)。これを行うと、コードの形式が不適切になります。これらの名前は実装に予約されているため、理論的には恐ろしく微妙で気まぐれな方法であなたの名前と競合する可能性があります。いずれにせよ、コードにコードが含まれている場合、コンパイラはコードを保持する義務を負いません。このような名前は、自分で使用するために「内部」名を取得するための迅速な方法ではありません。
underscore_d

ありがとう、修正しました。
ジャックホワイト

-1

アンチソリューション。

これを行わないでください:プライベートフィールドとパブリックフィールドを混在させます。

たぶん、uniqie変数名を生成するためのカウンターを備えたマクロが役立つでしょうか?

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


3
OK。誰も気にしないのなら、私は何をしないかという答えを残しておきます。
Robert Andrzejuk

4
@NicHartley答えの数を考えると、「研究」の質問に近いです。研究では、行き詰まりの知識はまだ知識であり、他の人が間違った方法をとるのを防ぎます。勇気の+1。
Oliv

1
@Olivそして、OPが何かを必要としたため、私は-1を返しました。この回答は要件に違反しているため、不正解です。私は、どちらのコメントでも、その人について、肯定的または否定的な価値判断を明示的にはしませんでした。私たちは、それが悪いことであることに同意できると思います。その人についてそれが言っていることは、このサイトでは話題外です。(ただし、IMOのアイデアを貢献するためにいくつかの時間を取って喜んで、誰もがやっている何かを発想が出てパンしない場合でも、右)
基金モニカの訴訟

2
はい、それは間違った答えです。しかし、私は一部の人々が同じ考えに来るかもしれないと恐れています。コメントとリンクのおかげで私は何かを学びましたが、それは私にとって否定的な点ではありません。
Robert Andrzejuk
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.