C / C ++の構造のサイズと、それが起こった理由についてのいくつかの言葉

以下のテキストでは、「プラットフォーム」という用語は、コンパイルされたコードが実行されるプロセッサ、コンパイラ、およびオペレーティングシステムの任意のセットと呼ばれます。



歴史的に、C言語は、その基礎にある主な目標の中に次のようなものがあるように作成されました。

さまざまなプラットフォームについて少し。 膨大な数(プラットフォーム)があります-プロセッサには16ビット、32ビット、64ビットがあります。 ハードウェアレベルで浮動小数点演算を実行できる人もいれば、倍精度演算をサポートしている人もいれば、FPUがまったくないプロセッサもあります。 プロセッサは、ワード内の内部バイト順序(ビッグ/リトルエンディアン)、プロセッサが外部メモリとどのように動作するかなども異なります。 など



そして、この動物園全体には、C言語の1つの標準があります。 ここからが楽しみです。





C言語では、その基本型(char、short、int、long、float、double)の厳密な制限はほとんどありません。 ビットの最小数は一部のタイプに設定されます(たとえば、タイプcharは少なくとも8ビット、int / short-16、long-32)、基本タイプのサイズはcharのサイズの倍数であることが指定されます。 charサイズ自体は1と同じです。



また、例として、MS-DOSのプログラムでは、intとshortのサイズは同じであり、これらのタイプ自体は16ビットでした。 Win32プラットフォームの場合、intサイズは既に異なります-32ビットです。



ちょっと変わった:プラットフォームがあり(たとえば、Texas Instrumentsから)、基本型のサイズが「通常の」ものとは大きく異なります。 たとえば、 すべての基本型のサイズは同じで、1に等しくなります。 つまり sizeof(int)== sizeof(char)== sizeof(double)。 はい、そうです-このプラットフォームのcharとshortはどちらも32ビットです。 規格に違反していません。 または、そのようなプラットフォーム:「long」タイプはメモリで8バイトを占有しますが、実際に使用されるのは5バイトだけです。 sizeof(long)== 8、ULONG_MAX = 1099511627775または2 40 -1 また、これは規格と矛盾しませんが、最初は驚くべきことです。



標準を開発する際に考慮すべき「鉄の世界」からのもう1つの重要な点は、プロセッサがメモリでどのように機能するかです。 アドレスバス、データバス、メモリチップがどのように機能するかを詳しく調べない場合、圧倒的な数のアーキテクチャに対して、次のルールが存在します:1つのコマンドでNバイト値を読み書きする場合、そのアドレスはNの倍数でなければなりません。メモリは4バイトのintに書き込まれ、アドレスは完全に4で割る必要があります。2バイトのショートなども同様です。



このルールが失敗するとどうなりますか? 異なるプラットフォームでは異なります:一部(ARMなど)では、プロセッサが中断されてOSのカーネルに制御が移りますが、その他(TIのDSP)では、プロセッサは最も近い複数のアドレスに静かに書き込みます(つまり、そこにない) 、彼らは言ったが、 ))、しかしx86プラットフォームでは、プロセッサは( いくつかのタイプのデータのために )プログラマが意図した通りに、パフォーマンスの低下のために行います。 ここで重要なのは、1つのことを理解することです。アドレスが揃ったときにすべてのプロセッサが同じように動作し、誰が何を気にするか-この要件が満たされない場合。 そのため、アライメントの定義は標準の最初に定義され、言語メモリモデルの説明で常に言及されています。



プログラマーの観点から、これはすべて何につながりますか? これは例によって最もよく示されています。 次のような構造があるとしましょう:
struct Foo {

int iiii;

char c;

};









プログラマの観点からは(経験の浅い)、この構造のサイズはsizeof(int)+ sizeof(char)= 4 + 1 = 5(intのサイズは4バイトであることを意味します)です。 ただし、この型の複数の要素の配列を宣言するとどうなりますか? メモリ内では、次のように配置されます。

画像



または、言い換えると、最初の要素のフィールドiiiiは位置合わせされたアドレスにあり、2番目の要素の場合、これは行われません。 つまり 構造Fooのサイズが5の場合、メモリモデルCの要件を満たすことは不可能です。



羊を安全に保ち、狼をいっぱいにするために、コンパイラは構造体の最後に「プログラマー向けに」「見えない」追加の3バイトを挿入します(いわゆるパディングバイト)。 これにより、構造体のサイズが8になり、メモリ内で配列が次のように配置されるようになります。

画像



ただし、これはコンパイラがこの構造で行うことのすべてではありません。 Fooのサイズが8であるという事実に加えて、コンパイラーは、この構造全体の最小アライメント要件が4バイトであることも覚えています。 次の例では、違いを簡単に示すことができます。
struct Foo {

int iiii;

char c;

};



struct Bar {

char c8[8];

};



struct Test1 {

char c;

Foo foo;

};



struct Test2 {

char c;

Bar bar;

};







ここで興味深い点は、FooとBarの両方のサイズが同じで、8バイトに等しいことです。 ただし、Fooのアライメント要件は4バイトで、Bar-1の要件です。これにより、Test1構造体では、「c」と「foo」の間にコンパイラーが追加の3バイトを挿入し、「foo」フィールドが常に4番目の倍数のアドレスから開始します。 Test2の構造では、このようなことをする必要はありません。その結果、sizeof(Test1)は12で、sizeof(Test2)は9です。つまり、同じサイズの「レンガ」を組み合わせると、異なる結果が得られます。



これがすべて興味深い場合は、トピックを継続できます。



All Articles