4

C での実験を続けて、ビット フィールドがメモリにどのように配置されるかを確認したいと思いました。私はIntel 64ビットマシンで作業しています。これが私のコードです:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
int main(int argc, char**argv){
       struct box_props
       {
         unsigned int opaque       : 1;
         unsigned int fill_color   : 3;
         unsigned int              : 4; 
         unsigned int show_border  : 1;
         unsigned int border_color : 3;
         unsigned int border_style : 2;
         unsigned int              : 2; 
       };

       struct box_props s;
       memset(&s, 0, 32);
       s.opaque = 1;
       s.fill_color = 7;
       s.show_border = 1;
       s.border_color = 7;
       s.border_style = 3;

       int i;
       printf("sizeof box_porps: %d sizeof unsigned int: %d\n", sizeof(struct box_props), sizeof(unsigned int));
       char *ptr = (char *)&s;
       for (i=0; i < sizeof(struct box_props); i++){
          printf("%x = %x\n", ptr + i, *(ptr + i));
       }

       return 0;

出力は次のとおりです。

sizeof box_porps: 4 sizeof unsigned int: 4
5be6e2f0 = f
5be6e2f1 = 3f
5be6e2f2 = 0
5be6e2f3 = 0

そしてここに質問があります: なぜstruct box_propssize4があるの2ですか? その場合、パディングはどのように行われますか? 私は少し(前兆なし)混乱しています。

すべての回答について事前にThx

4

4 に答える 4

9

この場合、合計要件は 2 バイト (1+3+4+1+3+2+2) ですが、使用されるデータ型のサイズ ( unsigned int) は 4 バイトです。したがって、割り当てられたメモリも 4 バイトです。2 バイトだけを割り当てたい場合は unsigned short、データ型として使用し、プログラムを再度実行してください。

于 2013-05-22T13:16:55.470 に答える
6

ISO C 標準から:

実装では、ビットフィールドを保持するのに十分な大きさのアドレス指定可能なストレージユニットを割り当てることができます。(およびそれ以降) 構造体または共用体の末尾に名前のないパディングがある場合があります。

そのため、常に構造体に可能な限り最小のメモリ チャンクを選択する必要はありません。32 ビット ワードはおそらくコンパイラのネイティブ サイズであるため、それが選択されます。

于 2013-05-22T13:22:00.997 に答える
3

メモリ内のビット フィールドの配置は、コンパイラが構造内のさまざまなフィールドの割り当てを決定する方法だけでなく、実行しているマシンのエンディアンにも依存します。これらを1つずつ見ていきましょう。コンパイラ内のフィールドの割り当ては、フィールドのサイズ (@DDD として) を指定することによって制御できますが、別のメカニズムを介して制御することもできます。コンパイラーに構造体を指示するpackか、コンパイラーがコンパイル対象のマシン・アーキテクチャーに最適化する方法に適したままにしておくことができます。パッキングはpacked type 属性を使用して指定されます。したがって、構造体を次のように指定した場合:

struct __attribute__ ((__packed__)) box_props {
    ...
} 

メモリ内に別のレイアウトが表示される場合があります。構造コンポーネントを調べても、レイアウトが異なることはわかりません。メモリ内のレイアウトが変更される可能性があります。構造体のパッキングは、特定の場所で特定のビットを期待する IO デバイスなど、何か他のものと通信する場合に非常に重要です。

ビット フィールド構造の 2 つ目の問題は、レイアウトがエンディアンに依存することです。メモリ内の構造体 (またはそのためのデータ) のレイアウトは、ビッグ エンディアン (POWER) マシンで実行しているか、リトル エンディアン (x86 など)マシンで実行しているかによって異なります。一部のシステム (たとえば、組み込み PowerPC システムはバイエンディアンです)。

一般に、ビット フィールドを使用すると、コードの移植が非常に難しくなります。これは、メモリ内のデータのレイアウトが混乱するためです。

お役に立てれば!

于 2013-05-22T13:20:12.677 に答える
2

何らかの理由で、C 標準の実装者は、前のフィールドが同じ型から割り当てられたビットフィールドでない限り、ビットフィールドと共に数値型を指定することで、その数値型を保持するのに十分なスペースを割り当てる必要があると判断しました。次のフィールドを処理するのに十分なスペースが残っています。

特定の例では、16 ビットの unsigned short を持つマシンでは、ビットフィールドの宣言を unsigned short に変更する必要があります。たまたま unsigned char も機能し、同じ結果が得られますが、常にそうとは限りません。最適にパックされたビットフィールドが char 境界にまたがるが短い境界にはまたがらない場合、ビットフィールドを宣言unsigned charすると、そのようなまたがりを避けるためにパディングが必要になります。

一部のプロセッサでは、ストレージ ユニットの境界にまたがるビットフィールドのコードを問題なく生成できますが、現在の C 標準では、それらをそのようにパッキングすることは禁止されています (理由はわかりませんが)。たとえば、典型的な 8/16/32/64 ビット データ型のマシンでは、フィールドがバイト境界にまたがる必要があるため、コンパイラはプログラマが 8 つの 3 バイト フィールドを含む 3 バイト構造を指定することを許可できませんでした。 . コンパイラが境界にまたがるフィールドを処理する必要がない、またはビットフィールドを特定の方法でレイアウトする必要がないという仕様を理解できました (特定のビットフィールドがたとえばビット 4 を使用する必要があることを指定できれば、それらは無限に便利であると見なすことができます)。 7 ある場所の)、しかし現在の標準は両方の世界の最悪を与えるようです。

いずれにせよ、ビットフィールドを効率的に使用する唯一の方法は、ストレージユニットの境界がどこにあるかを把握し、ビットフィールドのタイプを適切に選択することです。

PS -- ビットフィールドを含む構造体の宣言を禁止していたコンパイラを思い出すのは興味深いことですがvolatile(ビットフィールドを書き込むときの一連の操作が明確に定義されていない可能性があるため)、新しいルールの下ではセマンティクスを明確に定義できます (私はそうではありません)。仕様が実際にそれらを必要とするかどうかはわかりません)。たとえば、次のようになります。

typedef struct {
  uint64_t b0:8,b1:8,b2:8,b3:8, b4:8,b5:8,b6:8,b7:8;
  uint64_t b8:8,b9:8,bA:8,bB:8, bC:8,bD:8,bE:8,bF:8;
} FOO;
extern volatile FOO bar;

このステートメントbar.b3 = 123;は から最初の 64 ビットを読み取り、更新された値barで の最初の 64 ビットを書き込みます。bar揮発性でない場合bar、コンパイラはそのシーケンスを単純な 8 ビット書き込みに置き換える可能性がありbarますが、32 ビットまたは 64 ビット チャンクでのみ書き込み可能なハードウェア レジスタのようなものになる可能性があります。

ドラザーがあれば、次のようなものを使用してビットフィールドを定義できます。

typedef struct {
  uint32_t {
    baudRate:13=0, dataFormat:3,
    enableRxStartInt: 1=28, enableRxDoneInt: 1, enableTxReadyInt: 1, enableTxEmptyInt: 1;};
  };
} UART_CONTROL;

これは、baudRate がビット 0 (LSB) から始まる 13 ビットであること、dataFormat が baudRate の後に始まる 3 ビットであること、enableRxStartInt がビット 28 であることなどを示します。このような構文を使用すると、多くのタイプのデータ パッキングとアンパッキングを移植可能な方法で書き込むことができます。多くの I/O レジスタ操作をコンパイラに依存しない方法で行うことができます (ただし、そのようなコードは明らかにハードウェア固有のものです)。

于 2013-05-22T13:25:12.653 に答える