以下の内容はhttps://tnishinaga.hatenablog.com/entry/2025/02/10/133133より取得しました。


C言語のbitfieldの仕様と実装を調査した話

C言語にはbitfieldという機能がありますが、この機能にはさまざまな落とし穴があり、注意深く利用しても予期せぬバグや移植性の問題を引き起こす可能性があるとよく言われています。 実際、jpcertを確認すると複数の勧告が出ています。

私は先輩技術者から「とくに組み込みの分野においてC言語のbitfieldは使うべきではない」と教わって生きてきました。 本記事ではbitfieldの仕様や問題点、なぜ仕様を避けるべきか、緩和策や代替案について検討します。

目次

TL;DR

  • bitfieldの仕様は難しい
    • gccの場合は最終的にはpsABIがほとんどの振る舞いを決めている
    • clangはわからない
  • bitfield関係の実装依存問題を解決するのも難しい
    • 生成されるコードを推測しづらい
    • テストで確認できることとできないことがある
  • 安牌を取るなら伝統的なシフトとマスクを用いるほうが良い

bitfieldにはどのような問題があるのか

bitfieldについては、以下の問題が語られる事が多いです。

  • bitfieldの仕様の多くが実装依存で移植性に課題がある
  • バイスレジスタ制御にbitfieldを適用すると、意図しないアクセスが行われる可能性がある
  • 伝統的なシフトとマスクによる方法と比べると、最適化が難しくパフォーマンス影響がある

ひとつめの問題は移植性の問題です。 bitfieldはC言語の仕様書で決まっている部分が非常に少なく、決められていない部分は、コンパイラの仕様書、ABI仕様書、またはコンパイラ実装依存となっています。たとえば、bitfieldの順番やレイアウト等はC言語の仕様で決められておらず、その他の部分はコンパイラの実装やCPUアーキテクチャのABIごとに異なる可能性があります。 そのためbitfield利用の際は、想定利用環境での仕様の確認および生成されるコードの確認が必須です。また、他環境でコードを利用するためには同じ確認が必要となります。そして環境間で仕様に差がある場合は移植ができません。そのため、移植性(portability)に難があると言われます。

ふたつめはデバイスレジスタ制御にbitfieldを適用するのは危険という話です。 MMIOなどのデバイスレジスタ制御では、そのレジスタにアクセスすること自体が意味を持つ(副作用がある)場合があるため、読み書きのタイミングやビット幅は適切になるよう制御したいです。 一方、bitfieldを利用した場合に生成されるアセンブリコードはコンパイルオプションや実装依存のため、読み書きのタイミングやビット幅の制御が行えません。また、レイアウト等の問題を引く可能性もあります。 よって、bitfieldをMMIOのメモリに割り当てて読み書きするような用途で利用するのは危険であり、使うべきではありません。

3つめはコンパイラのバージョン等にもよりますが、伝統的なシフトとマスクを用いてビット制御を行う場合に比べると数命令程度命令数が増える場合があります。

以降、各問題について詳しく見ていきましょう。

bitfieldの仕様

bitfieldに関する仕様をすべて把握するためには、以下の3つの資料に当たる必要があります。 加えて、このすべての資料で言及されていない部分については実装依存となるため、生成されたコードを確認する必要があります。

C言語の仕様

最初にC言語の仕様書を確認します。

Bitfieldのビット配置等について書かれていそうな部分を一部抜粋します。 通常文が和訳内容で、() 内は筆者の補足、{}内は筆者の感想を記します。

13 An implementation may allocate any addressable storage unit large enough to hold a bit-field. If enough space remains, a bit-field that immediately follows another bit-field in a structure shall be packed into adjacent bits of the same unit. If insufficient space remains, whether a bit-field that does not fit is put into the next unit or overlaps adjacent units is implementation-defined. The order of allocation of bit-fields within a unit (high-order to low-order or low-order to high-order) is implementation-defined. The alignment of the addressable storage unit is unspecified.

§ 6.7.3.2, pp.106, ISO/IEC 9899:2024 (en) — N3220 working draft

§ 6.7.3.2は構造体や共用体の仕様を決めている部分です。ここでは以下のbitfield仕様を決めています。

  • (コンパイラの?)実装はビットフィールドを格納するのに十分なサイズのアドレス可能な記憶領域を割り当てられる(may)
  • (記憶単位内に)十分な空きスペースがあり、構造体内で1つのビットフィールドの直後に別のビットフィールドが続く場合、同じ記憶単位内の隣接するビットに詰め込まれる必要があります(shall)
  • (記憶単位内に)十分な空きスペースがない場合、そのビットフィールドが次の記憶単位に格納されるか、隣接するビットと重なるかは実装依存
  • (記憶)単位内でビットがビットフィールドが割り当てられる順序(高位ビットから低位ビット、または低位ビットから高位ビット)は実装依存
  • アドレス可能な記憶単位のアライメントは不定(unspecified)

また、Annex J以降にはビットフィールドのPortability issues(移植性の問題)について書かれています。

  • 不定(unspecified)
    • ビットフィールドのアライメント(19, J.1 Unspecified behavior)
  • 未定義動作(Undefined behavior)
    • (offsetofマクロの話なので省略)
  • 実装依存(Implementation-defined behavior)
    • int(signedでもunsignedでもない)のbit-fieldをsigned intとして扱うか、unsigned intとして扱うか
    • bool, signed int, unsigned int, bit-precise integer 型以外のbit-field型を許可するか
    • bit-fieldにアトミック型を許可するか
    • bit-fieldがストレージユニットの境界をまたげるか
    • ユニット内のbit-field割当順序
    • 構造体の非bit-fieldメンバーのアライメント
      • J.3.10 Structures, unions, enumerations, and bit-fieldsより引用

まとめると、bitfieldの仕様で明確に決まっているのは「記憶単位内に十分な空きスペースがあり、構造体内で1つのビットフィールドの直後に別のビットフィールドが続く場合、同じ記憶単位内の隣接するビットに詰め込まれる必要がある」というルールだけです。それ以外の部分は実装依存となっています。

bitfieldに関するコンパイラのドキュメント

実装依存部の仕様を見るために、clangとgccのドキュメントを当たります。

gcc

GCCは4.9 Structures, Unions, Enumerations, and Bit-Fields節で、C言語仕様で実装依存と書かれていた部分の仕様を決めています。

Structures unions enumerations and bit-fields implementation (Using the GNU Compiler Collection (GCC))

ここからbitfieldに関係する部分を一部抜粋して引用します。

Whether a “plain” int bit-field is treated as a signed int bit-field or as an unsigned int bit-field (C90 6.5.2, C90 6.5.2.1, C99 and C11 6.7.2, C99 and C11 6.7.2.1). By default it is treated as signed int but this may be changed by the -funsigned-bitfields option.

Allowable bit-field types other than _Bool, signed int, and unsigned int (C99 and C11 6.7.2.1). Other integer types, such as long int, and enumerated types are permitted even in strictly conforming mode.

Whether atomic types are permitted for bit-fields (C11 6.7.2.1). Atomic types are not permitted for bit-fields.

Whether a bit-field can straddle a storage-unit boundary (C90 6.5.2.1, C99 and C11 6.7.2.1). Determined by ABI.

The order of allocation of bit-fields within a unit (C90 6.5.2.1, C99 and C11 6.7.2.1). Determined by ABI.

The alignment of non-bit-field members of structures (C90 6.5.2.1, C99 and C11 6.7.2.1). Determined by ABI.

https://gcc.gnu.org/onlinedocs/gcc-14.2.0/gcc/Structures-unions-enumerations-and-bit-fields-implementation.html より引用

和訳は以下。

  • plainなintのbitfieldがsigned int bitfieldにとして扱うかunsigned bitfieldとして扱うか
    • デフォルトはsigned int bitfieldとして扱う。ただし -funsigned-bitfields オプションで変更できる
  • bitfieldに(Cの仕様で決められた)_Bool, signed int, unsigned int以外を使えるか
    • long intのような他の整数型や列挙型は、厳密に適合するモードであっても許可される
  • ビットフィールド型にAtomic typeは許可されるか
    • 許可されない
  • 以下、ABI依存
    • ビットフィールドがストレージユニットの境界を跨ぐことができるか
    • ユニット内のビットフィールドの割当順序(並び順)
    • 構造体のビットフィールド以外のメンバのアラインメント

Cの仕様では実装依存とされている部分の一部はgccの仕様としてカバーしており、その他の部分は各CPUアーキテクチャのABI依存となっているようです。

gcc-3.x頃のbitfield仕様

過去に遡って仕様を確認したところ、gccのbitfieldの仕様が定まったのはgcc-4.0.4からのようです。 それまでの仕様は非常に簡素で、実装依存部のほとんどはgcc側で決めているようです。実装依存の挙動を確認したい際はgcc 3.x系を使うとよいのかもしれません。

clang

私が確認した限りではclangのbitfieldの仕様が書かれたドキュメントを見つけられませんでした。 本当にドキュメントが存在しない場合、clangではbitfieldのすべての挙動が実装依存となってしまいます。 流石にそんなことはないはずなので、これらの仕様がどこにあるかご存じの方がいれば教えていただけると嬉しいです。

CPUアーキテクチャのABIで定められたbitfieldの仕様

bitfieldの仕様はCPUアーキテクチャのABI仕様書にも記されています。 以下、bitfieldに関する記述を見つけられた主要なCPUアーキテクチャABIの資料一覧です。

順番に中身をみていきます。

x86 SystemV ABI

Linux等で使われているABIです。

x32

順番前後しますが、bitfieldの記述に関してはほぼx64と同様なのでそちらを参照してください。 bitfieldのレイアウトが図示されている点はx64のABIよりわかりやすくて嬉しいです。

x64(AMD64)

x86 psABIs / x86-64 psABI · GitLab

この資料のpp.19-20より、bitfieldに関する記述を引用します。

C struct and union definitions may include bit-fields that define integral values of a specified size.

The ABI does not permit bit-fields having the type m64, m128, m256 or m512. Programs using bit-fields of these types are not portable. Bit-fields that are neither signed nor unsigned always have non-negative values. Although they may have type char, short, int, or long (which can have negative values), these bit-fields have the same range as a bit-field of the same size with the corresponding unsigned type. Bit-fields obey the same size and alignment rules as other structure and union members. Also:

•bit-fields are allocated from right to left

•bit-fields must be contained in a storage unit appropriate for its declared type

•bit-fields may share a storage unit with other struct / union members

Unnamed bit-fields’ types do not affect the alignment of a structure or union.

System V Application Binary Interface AMD64 Architecture Processor Supplement(With LP64 and ILP32 Programming Models) Version 1.0, November 18, 2024, pp.19-20 より引用

ざっと和訳するとこんな感じです。

  • __m64型等をbitfieldに使うのはABIとしては不許可
  • 符号付きでも符号なしでもないbitfield(char型、short型、int型、long型)は、常に負でない値を持つ
    • {つまり、素のint型のbitfieldはunsigned intとして解釈されるということ?}
  • ビットフィールドは、他の構造体や共用体のメンバーと同じサイズとアライメントの規則に従う
  • bitfieldは右から左に確保される
    • (他のABIでいうところのLittle-Endian順)
  • bitfieldは宣言された型に適したストレージ・ユニットに含まれなければならない
  • bitfieldは他の構造体/union メンバーとストレージ・ユニットを共有することができる

Microsoft ABI

x32

すぐに見つけられなかったので省略します。

x64

microsoft ABIのbitfieldに関する記述は非常に簡素です。

Structure bit fields are limited to 64 bits and can be of type signed int, unsigned int, int64, or unsigned int64. Bit fields that cross the type boundary will skip bits to align the bitfield to the next type alignment. For example, integer bitfields may not cross a 32-bit boundary.

和訳するとこんな感じ

  • bitfieldのサイズは最大64bit
  • 許容される型は以下
    • signed int
    • unsigned int
    • (signed) int64
    • unsigned int64
  • 型境界を超えるbitfieldは次の型のアライメントに合わせるためにskip
    • 例: int(32bit) bitfieldは32bitの境界を超えてはならない(may)

困ったことにbitの並び順についてはルールがないため、bitfieldの中身がどのような順序となるか実装依存となります。 そのためか、gccには互換を取るための -mms-bitfields というオプションがあるようです。

ARM(aapcs)

ARMのpsABIはbitfieldの仕様が今回調べた中ではもっとも詳細に書かれていました。 そのため、他のABIでも書かれている基本的な部分のみに絞って紹介します。 また、ARM32とARM64のbitfieldに関する記述はほぼ同様なので今回はARM64について見ていきます。

https://github.com/ARM-software/abi-aa/blob/05abf4f78dd7837774c4880fc0e6c01ce9e41ba8/aapcs64/aapcs64.rst#bit-fields

  • コンテナー型の決定
    • ビットフィールドが宣言された整数型に収まる場合
      • 整数型がコンテナー型になる
    • 収まらない場合
      • ビットフィールドのサイズを超えない程度に最大の整数型がコンテナー型になる
      • オーバーサイズビットフィールドの項を参照
  • ビットフィールドがコンテナサイズより大きくない場合(10.1.8.1 Bit-fields no larger than their container)
    • コンテナーのアドレスはコンテナーの型のサイズでアラインされる
    • bit offsetマナーはエンディアン依存
    • bitfieldは以下に依存してシフトおよびマスクを行い、必要なら符号拡張をしてコンテナーから取り出される

bitの並び順がエンディアン依存であることや、取り出し方法や抽出式まで書かれている点が特徴的です。

RISC-V

今回見た中でもっとも新しいアーキテクチャでして、bitfieldに関する仕様が導入されたのが2018年頃、今の形になったのが2024年となっています。

決まっている内容もARMに比べるとシンプルです。

https://github.com/riscv-non-isa/riscv-elf-psabi-doc/blob/3a79e936eec5491078b1133ac943f91ef5fd75fd/riscv-cc.adoc#bit-fields

以下、ドキュメントにかかれている内容のまとめです。

  • ビットフィールドはリトルエンディアンでpackされる
  • ビットフィールドがその整数型のアライメント境界をまたぐ場合、そのビットフィールドは次のアライメント境界から始まるようにパディングされる
  • ビットフィールドのサイズがその整数型のサイズを超える場合、その超える分はパディングとして扱われ、次のビットフィールドは次のアライメント境界から配置される
    • (整数型のサイズを超えるbitfieldを作ると、超えた分のビットはundefinedとなる)

bitfieldの仕様についてのまとめ

psABI依存の部分はアーキテクチャごとに細かい差分はありますが、おおむね以下の部分は共通していそうです。

  • ビットの並び順はlittle-endian(右から左)
  • 許容される型はC言語仕様と同様

つまり、CPUアーキテクチャのint型のビット幅以下のbitfieldを宣言して、そこに隙間のない(paddingがない)ピッタリのビットを定義すれば、コンパイラアーキテクチャが変わっても仕様上は想定外のレイアウトにならないはずです。 しかしそれ以外の場合については各アーキテクチャの仕様とコンパイラの実装を確認する必要があるでしょう。 また、この挙動は(めったにないことですが)ABI仕様が変更された場合*1に変わる可能性も考慮が必要かもしれません。

実装依存部の確認

bitfieldの問題としてあげた以下の2点はコンパイラの実装や最適化オプションによって挙動が変わる部分となります。

  • バイスレジスタ制御にbitfieldを適用すると、意図しないアクセスが発生する可能性がある
  • 伝統的なシフトとマスクによる方法と比べると、最適化が難しくパフォーマンス影響がある

よって、実際にCのコードと命令列を見比べながら、それぞれの問題について見ていきます。

バイスレジスタ制御にbitfieldを適用すると、意図しないアクセスが発生する可能性がある

これはbitfieldへのアクセス手順が実装依存という点が問題となります。

bitfieldはあるメモリの1bitだけ書き換えるなどができるので、以下のようにしてMMIOレジスタ書き換え等にも使いたくなります。 しかし、このようなコードは書くべきではありません。

// https://godbolt.org/z/oeqba9q83
#include <stdint.h>

typedef struct {
    uint32_t b0_7:8;
    uint32_t b8_23: 16;
    uint32_t b24_31:8;
} MMIO_REG;

void hoge(volatile uint32_t *addr, uint8_t data) {
    volatile MMIO_REG *reg = (volatile MMIO_REG *)addr;
    reg->b0_7 = data & 0xff;
}

こちらのコードをgcc-4.5.4でコンパイルすると、以下のようにデータを1byteでstore(strb)するコードが生成されます。

# gcc 4.5.4で生成されたコード
hoge:
        strb    r1, [r0, #0]
        bx      lr

bitfieldにはどのような問題があるのか でも述べたように、MMIOなどのアクセスには読み込みに副作用がある場合や、アクセスするbit幅によって読み書き結果に差が出る場合があります。 そのため、このようなコードが生成されると、意図しない挙動が発生する可能性があります

なお、このコードを新しめのgcc(14.2.0)1でビルドすると32bit load, 32bit storeのコードが生成されるので、同様の問題は起こりません。

# gcc 13.1.0で生成されたコード
hoge:
        ldr     r3, [r0]
        bic     r3, r3, #255
        orr     r3, r1, r3
        str     r3, [r0]
        bx      lr

ただし、この挙動については仕様で決まっていないため、今後変更される場合があります。

どうしてもbitfieldを使いたい場合は、以下のようにMMIOを直接書き換えないようにすれば実装依存の影響を受けにくくなるはずですが、絶対大丈夫という保証はありません。

#include <stdint.h>

void hoge(volatile uint32_t *addr, uint8_t data) {
    volatile MMIO_REG reg = *(volatile MMIO_REG *)addr;
    reg.b0_7 = data & 0xff;
    *(volatile MMIO_REG *)addr = reg;
}
# gcc 4.5.4の生成したコード
hoge:
        ldr     r3, [r0, #0]
        sub     sp, sp, #8
        str     r3, [sp, #4]
        strb    r1, [sp, #4]
        ldr     r3, [sp, #4]
        str     r3, [r0, #0] <- r0(data)にストア
        add     sp, sp, #8
        bx      lr

安牌をとるのであれば、伝統的なシフトとマスクを用いた方法を使うべきでしょう。

伝統的なシフトとマスクによる方法と比べるとパフォーマンスが低下する可能性がある

構造体のビットメンバは実用上の欠点がある。

...(中略)...

多くの一般的なコンパイラは、ビットメンバの読み書きに対して、非効率なコードを生成する(プロセッサのワード単位でロード/ストアするほうが効率的)。

https://ja.wikipedia.org/wiki/ビットフィールド より引用

wikipediaにこう書かれているように、bitfieldを用いると非効率なコードを生成する場合があります。 以下に非効率なコードを生成する例を紹介します。

// 非効率なコードを生成する例
// https://godbolt.org/z/b3E7n3jfz
#include <stdint.h>

typedef struct {
    uint32_t b0_7:8;
    uint32_t b8_23: 16;
    uint32_t b24_31:8;
} MMIO_REG;

void hoge(
    uint32_t *addr, 
    uint8_t b0_7, 
    uint16_t b8_23,
    uint8_t b24_31
    ) {
    MMIO_REG *reg 
        = (MMIO_REG *)addr;
    reg->b0_7 = b0_7;
    reg->b8_23 = b8_23;
    reg->b24_31 = b24_31;
}

こちらのCコードをgcc 14.2.0でコンパイルした結果を以下に示します。

hoge:
        strb    r1, [r0]
        ldr     ip, .L2
        ldr     r1, [r0]
        and     r2, r2, ip
        bic     r1, r1, ip, lsl #8
        orr     r1, r1, r2, lsl #8
        str     r1, [r0]
        strb    r3, [r0, #3]
        bx      lr
.L2:
        .word   65535

wikipediaの話通り、32bitストアと8bitストアを行う効率の悪いコードが生成されました。

このコードはさまざまな点で意図通りではありません。 Cのコードの意図が32bitのbitfieldすべてを一度に書き換えることであれば、ストアが2度に分かれているのは意図に反しますし、パフォーマンスも低下します。 それぞれのフィールドごとにメモリストアを行ってほしいという意図だとしても、その意図は守れていません。

以下のようにCのコードを書き換えれば1つ目の意図通りのコードを出力もできますが、このコードもコンパイラのバージョンや最適化オプションなどで変わる可能性があります。2つ目もvolatileをつければ意図通りになるはずですが、こちらもコンパイラやオプション次第となります。

// https://godbolt.org/z/nsdosjnhf
#include <stdint.h>

typedef struct {
    uint32_t b0_7:8;
    uint32_t b8_23: 16;
    uint32_t b24_31:8;
} MMIO_REG;

void hoge(
    uint32_t *addr, 
    uint8_t b0_7, 
    uint16_t b8_23,
    uint8_t b24_31
    ) {
    MMIO_REG reg = *(MMIO_REG*)addr;
    reg.b0_7 = b0_7;
    reg.b8_23 = b8_23;
    reg.b24_31 = b24_31;
    *addr = *(uint32_t *)&reg;
}
# 生成されたコード
hoge:
        orr     r1, r1, r2, lsl #8
        orr     r1, r1, r3, lsl #24
        str     r1, [r0]
        bx      lr

やはり安牌をとるのであれば、挙動を読みやすい伝統的なシフトとマスクを用いた方法を使うべきでしょう。

その他色々

bitfieldの定義を1bitでも誤ってはいけない

bitfield内のビット定義を1bitでも多くするか、少なくすると困ったことが起こります。

1bit多く定義した場合

以下のようにbitfieldの定義を誤ってコンテナサイズより1bitでも大きくしてしまうと、bitfieldのサイズはコンテナサイズに収まらなくなります。以下の例の場合、bitfieldのサイズは8となります。

// https://godbolt.org/z/7x4EqraqW
#include <stdint.h>
#include <assert.h>

typedef struct {
    uint32_t b0_7:8;
    uint32_t b8_23: 16;
    uint32_t ext:1;
    uint32_t b24_31:8;
} MMIO_REG;

void hoge(void) {
    static_assert(
        sizeof(MMIO_REG) == 4, 
    "invalid size");
}

このミスはstatic_assertなどを用いてコンパイル時に検知するコードを入れると防げます。 逆に言えば、static_assertさえいれておけば定義のミスを検知する仕組みが入れられるメリットがあるとも言えるかもしれません。

こちらの問題は伝統的なシフトとマスクを用いた方法に切り替えても発生しますし、むしろ気づくのが難しくなる予想です。

1bit少なく定義した場合

以下のコードは16bit幅のb0_15を定義したつもりが、誤って1bit少ない15bitで定義してしまった例になります。

typedef struct {
    uint32_t b0_15:15;
    uint32_t b16_31:16;
} MMIO_REG;

bitfield内のどこかの定義を誤って1bit短くしてしまった場合はちょっと面倒なことになります。 具体的な例は以下です。

  • sizeof(MMIO_REG) では誤りに気がつけない
    • bitfieldがコンテナサイズ(uint32_t)に収まっているため
  • b0_15 に215より大きな値を入れないとコンパイラの警告が出ない
  • b0_15以降のビットすべてのビットオフセットがずれる
    • 今回の場合は b16_31 にセットした値が意図したビットオフセットに挿入されない
  • padding bitがどこに挿入されるかはコンパイラやABIの仕様・実装依存

このバグをコンパイル時やテストで見つけるのは結構難しいです。見つけられるとすればコードレビューのときだけで、そこで見つけられなかった場合はruntimeのバグとして対処するしかなくなります。

テストで見つける方法があるとすれば、伝統的なシフトとマスクを用いた方法で作った値とbitfieldで作った値を比較する仕組みを作るとかでしょうか。(そこまでしてbitfieldを使う意味とは……?)

なお、伝統的なシフトとマスクを用いた方法を使った場合でもこの問題自体を防ぐことはできませんが、伝統的な方法はビットオフセットが独立して定義されているため、1つミスをしても他に波及しないメリットがあります。一方、そのせいでバグの発覚が遅れる可能性もあり、一長一短です。

テストが書きづらい

bitfieldの振る舞いはプロセッサのABIに依存するため、コードの移植性に難があります。 仮にコードを他のアーキテクチャに移植する予定がなかったとしても、テストを作る際に困る場合があります。

組み込み開発でもユニットテストを書いて実行したいと考えたとき、第一候補は実デバイスqemu等の上でテストを行うことと思います。 しかし、これらのうえでテストを組むコストや実行時間を考えると、HALを用意するなどして可能な限りx86_64のPC上でテストを実行したいとなる場合があります。 しかし、コードの移植性に難がある場合、HALがその差を吸収しきれずテストが作れなくなる可能性が出てきてしまいます。

これを避けるのであれば、bitfieldより伝統的な方法を用いた方が、アーキテクチャに左右される部分が少なく適していると思われます。

まとめ

  • bitfieldの仕様は難しい
    • gccの場合は最終的にはpsABIがほとんどの振る舞いを決めている
    • clangはわからない
  • bitfield関係の実装依存問題を解決するのも難しい
    • 生成されるコードを推測しづらい
    • テストで確認できることとできないことがある
  • 安牌を取るなら伝統的なシフトとマスクを用いるほうが良い

bitfieldはそのほとんどがCが言語の仕様で定まっておらず、正確な挙動を把握するにはコンパイラの仕様、プロセッサのABI仕様、コンパイラの実装、生成されるコードすべての確認が必要となります。 また、コンパイラのバージョンを更新したり、オプションを変更するたびにバグが起こらないか確かめる必要もあるかもしれません。 その他にも、コンパイラの仕様やバグによってはプロセッサのABI仕様と異なる挙動をする可能性もありますし、(めったにないことですが)ABI自体が変わることもありえます。 これらすべての可能性を検討し、適切に運用するのはなかなか難しいと思います。 そのため、とくに複数人での開発の場や、実務のコードに持ち込む際には議論が必要でしょう。

それRustだとどうなの?

趣味ではRustで組み込み開発をしているので、Rustでのbitfieldの扱いを書いておきます。

RustにはBitfieldはありません。 どうしてもRustでbitfieldを使いたい場合は、まずbitfieldに期待する機能を考えたあと、その期待する機能に応じて第三者の作ったライブラリ(crate)を適切に利用して機能を実現します。 具体的には以下のライブラリを用います。

Rustではbitfieldの機能を外部ライブラリが提供しているので、bitfieldの挙動はライブラリの仕様や実装のみで決められます。 これにより、実装依存や未定義動作の問題を減らせたり、移植性の向上や、テストが書きやすくなるなどのメリットを得られるでしょう。

EOF

おしまい


  1. gcc 5.4から14.2.0と同じコードが得られたので、4.5.4から5.4の間に何らかの変更があったようです

*1:大昔の話になりますが、ARMは初期のABI(OABI)から今使われているEABIに変わったことがあります。RISC-Vなど新しいアーキテクチャが今後発展の過程でABIを変更する可能性はあるかもしれません。




以上の内容はhttps://tnishinaga.hatenablog.com/entry/2025/02/10/133133より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14