オブジェクトとアライメント
C のプログラムは、オブジェクトを作成、破棄、アクセス、および操作します。
C では、オブジェクトとは、その内容によって値を表現できる、実行環境のデータ記憶域の領域です (値とは、具体的な型として解釈したときの、オブジェクトの内容の意味です)。
すべてのオブジェクトには以下のものがあります。
- サイズ (
sizeofで調べられます) - アライメント要件 (
_Alignofで調べられます) (C11以上) - 記憶域期間 (自動、静的、確保された、スレッドローカル)
- 生存期間 (記憶域期間と等しい、または、一時的)
- 実効型 (後述)
- 値 (不定の場合もあります)
- オプションで、そのオブジェクトを表す識別子
オブジェクトは、宣言、確保関数、文字列リテラル、複合リテラル、および配列メンバを持つ構造体または共用体を返す非左辺値式によって作成されます。
オブジェクト表現
ビットフィールドを除き、オブジェクトは1個以上のバイトの連続する並びから構成され、 memcpy で unsigned char[n] 型のオブジェクトにコピーできます (ただし n はオブジェクトのサイズです) (各バイトは CHAR_BIT 個のビットから構成されます)。 結果の配列の内容はオブジェクト表現と言います。
2つのオブジェクトが同じオブジェクト表現を持つ場合、それらは等しくなります (ただし、それらが浮動小数点の NaN である場合は除きます)。 その逆は真ではありません。 2つの等しいオブジェクトが異なるオブジェクト表現を持つことがあります。 これはオブジェクトのすべてのビットが値に寄与するとは限らないためです。 そのようなビットはアライメント要件を満たすためのパディングやトラップ表現を示すためのパリティチェックなどに使用されるかもしれません。
オブジェクト表現がそのオブジェクトの型のいかなる値も表さない場合、それはトラップ表現であると言います。 文字型の左辺値式を通して読み込む以外の方法でのトラップ表現へのアクセスは未定義動作です。 構造体または共用体の値がトラップ表現になることはありません (特定のメンバがそうなることはあります)。
char、 signed char、および unsigned char 型のオブジェクトの場合、オブジェクト表現のすべてのビットが値表現に寄与し、有り得るすべてのビットパターンが異なる値を表現することが要求されます (パディングやトラップビット、複数の表現はありません)。
整数型 (short、 int、 long、 long long) のオブジェクトが複数のバイトを占めるとき、それらのバイトの使用方法は処理系定義ですが、ビッグエンディアン (POWER, Sparc, Itanium) とリトルエンディアン (x86, x86_64) の2種類が支配的です。 ビッグエンディアンのプラットフォームでは整数が占める記憶領域の最低位アドレスに最上位バイトを格納し、リトルエンディアンのプラットフォームでは最低位アドレスに最下位バイトを格納します。 詳細はエンディアンを参照してください。 下の例も参照してください。
ほとんどの処理系は整数型に対してトラップ表現、パディングビット、複数の表現を使用しませんが、例外もあります。 例えば、 Itanium では整数型の値がトラップ表現になることがあります。
実効型
すべてのオブジェクトには実効型があります。 これはどの左辺値アクセスが有効でどの左辺値アクセスが厳密なエイリアシングルールに違反するかを決定します。
オブジェクトが宣言によって作成された場合、そのオブジェクトの宣言された型がそのオブジェクトの実効型です。
オブジェクトが確保関数 (realloc を含みます) によって作成された場合、そのオブジェクトは宣言された型を持ちません。 そのようなオブジェクトは実効型を以下のように取得します。
- 文字型以外の左辺値を通してそのオブジェクトに最初に書き込んだとき、その左辺値の型が、その書き込みおよびその後のすべての読み込みに対する、そのオブジェクトの実効型になります。
- memcpy または memmove で別のオブジェクトからそのオブジェクトにコピーしたとき、そのコピー元のオブジェクトの実効型 (もしあれば) が、その書き込みおよびその後のすべての読み込みに対する、そのオブジェクトの実効型になります。
- 宣言された型を持たないオブジェクトへのそれ以外のすべてのアクセスでは、実効型はそのアクセスのために使用された左辺値の型です。
厳密なエイリアシング
実効型 T1 のオブジェクトがあったとき、異なる型 T2 の左辺値式 (一般的にはポインタの逆参照) の使用は、以下の場合を除いて、未定義動作です。
- T2 と T1 が互換な型である。
- T2 が T1 と互換な型の cvr 修飾されたバージョンである。
- T2 が T1 と互換な型の符号有無が異なるバージョンである。
- T2 がそのメンバに上記のいずれかの型を含む集成体型または共用体型である (再帰的に含む場合を含みます)。
- T2 が文字型 (char、 signed char、または unsigned char) である。
int i = 7;
char* pc = (char*)(&i);
if (pc[0] == '\x7') { // char を通したエイリアシングは OK です。
puts("This system is little-endian");
} else {
puts("This system is big-endian");
}
float* pf = (float*)(&i);
float d = *pf; // 未定義動作、 float の左辺値 *p は int をアクセスするためには使用できません。
これらのルールは、2つのポインタを取る関数が、一方に書き込んだ後、他方を読み直す必要があるかどうか制御します。
// int* と double* はエイリアスできません。
void f1(int *pi, double *pd, double d)
{
// *pi からの読み込みはループの前に1度だけ行えば済みます。
for (int i = 0; i < *pi; i++) *pd++ = d;
}
struct S { int a, b; };
// int* と struct S* はエイリアスできます。 S は int 型のメンバを持つ集成体型であるためです。
void f2(int *pi, struct S *ps, struct S s)
{
// *pi からの読み込みは *ps を通して書き込んだ後に毎回行わなければなりません。
for (int i = 0; i < *pi; i++) *ps++ = s;
}
たとえ上記のルールによってエイリアスできる場合でも restrict 修飾子を使用して2つのポインタがエイリアスしないことを示せることに注意してください。
共用体の非アクティブメンバを通した型パニングも許容されることに注意してください。
アライメント
すべての完全オブジェクト型にはアライメント要件という性質があります。 これはその型のオブジェクトを割り当てることができる連続するアドレス間のバイト数を表す size_t 型の整数値です。 有効なアライメントの値は2の非負整数乗です。
|
型のアライメント要件は |
(C11以上) |
構造体のすべてのメンバのアライメント要件を満たすために、一部のメンバの後にパディングが挿入されることがあります。
#include <stdio.h>
#include <stdalign.h>
// 構造体 S のオブジェクトは任意のアドレスに割り当てられます。
// S.a と S.b はどちらも任意のアドレスに割り当てられるためです。
struct S {
char a; // サイズ: 1、アライメント: 1
char b; // サイズ: 1、アライメント: 1
}; // サイズ: 2、アライメント: 1
// 構造体 X のオブジェクトは4バイト境界に割り当てなければなりません。
// X.n を4バイト境界に割り当てなければならないためで、
// それは int のアライメント要件が (通常は) 4であるためです。
struct X {
int n; // サイズ: 4、アライメント: 4
char c; // サイズ: 1、アライメント: 1
// ここに3バイトのパディングが挿入されます。
}; // サイズ: 8、アライメント: 4
int main(void)
{
printf("sizeof(struct S) = %zu\n", sizeof(struct S));
printf("alignof(struct S) = %zu\n", alignof(struct S));
printf("sizeof(struct X) = %zu\n", sizeof(struct X));
printf("alignof(struct X) = %zu\n", alignof(struct X));
}
出力例:
sizeof(struct S) = 2
alignof(struct S) = 1
sizeof(struct X) = 8
alignof(struct X) = 4
それぞれのオブジェクト型は、その型のすべてのオブジェクトに対して、そのアライメント要件を課します。 あらゆる型の中で最も厳しい (大きい) 基本アライメントは、 max_align_t のアライメントです。 最も弱い (小さい) アライメントは、 char、 signed char、および unsigned char のアライメントであり、1と等しくなります。
|
|
(C11以上) |
参考文献
- C11 standard (ISO/IEC 9899:2011):
- 3.15 object (p: 6)
- 6.2.6 Representations of types (p: 44-46)
- 6.5/6-7 Expressions (p: 77)
- 6.2.8 Alignment of objects (p: 48-49)
- C99 standard (ISO/IEC 9899:1999):
- 3.2 alignment (p: 3)
- 3.14 object (p: 5)
- 6.2.6 Representations of types (p: 37-39)
- 6.5/6-7 Expressions (p: 67-68)
- C89/C90 standard (ISO/IEC 9899:1990):
- 1.6 Definitions of terms
関連項目
オブジェクト の C++リファレンス
|