Namespaces
Variants

Objects and alignment

From cppreference.net

Cプログラムはオブジェクトの作成、破棄、アクセス、操作を行います。

C言語におけるオブジェクトは、実行環境内の データストレージ の領域であり、その内容は を表現することができます (値とは、特定の を持つものとして解釈された場合のオブジェクトの内容の意味です)。

すべてのオブジェクトは

  • サイズ( sizeof で決定可能)
  • アライメント要件 _Alignof (C23まで) alignof (C23以降) で決定可能) (C11以降)
  • ストレージ期間 (自動、静的、割り当て済み、スレッドローカル)
  • 生存期間 (ストレージ期間と等しい、または一時的)
  • 有効型(下記参照)
  • 値(不定値の場合あり)
  • オプションで、このオブジェクトを表す 識別子

オブジェクトは、 宣言 動的確保関数 文字列リテラル 複合リテラル 、および 配列メンバを持つ構造体または共用体 を返す非左辺値式によって作成されます。

目次

翻訳内容: - 「Contents」→「目次」 - C++専門用語(Object representation、Effective type、Strict aliasing、Alignment、Defect reports、References、See also)は原文のまま保持 - HTMLタグ、属性、数値は一切変更せず - 書式と構造を完全に維持

オブジェクト表現

bit-fields を除き、オブジェクトは1つ以上の連続したバイト列で構成され、各バイトは CHAR_BIT ビットから成ります。これらは memcpy を使用して型 unsigned char [ n ] のオブジェクトにコピーできます。ここで n はオブジェクトのサイズです。結果の配列の内容は オブジェクト表現 として知られています。

2つのオブジェクトが同じオブジェクト表現を持つ場合、それらは等しいと比較されます(浮動小数点数のNaNを除く)。逆は真ではありません:等しいと比較される2つのオブジェクトは、異なるオブジェクト表現を持つ可能性があります。なぜなら、オブジェクト表現のすべてのビットが値に関与する必要はないからです。そのようなビットは、アライメント要件を満たすためのパディング、パリティチェック、トラップ表現の表示などに使用される可能性があります。

オブジェクト表現がそのオブジェクト型のいずれの値も表現しない場合、それは trap representation として知られています。trap representationへのアクセスは、文字型のlvalue式を通じて読み取る以外の方法では未定義動作となります。構造体または共用体の値は、たとえ特定のメンバーがtrap representationであっても、決してtrap representationにはなりません。

char 型、 signed char 型、および unsigned char 型のオブジェクトについては、オブジェクト表現のすべてのビットが値表現に参加することが要求され、可能なすべてのビットパターンが異なる値を表す(パディング、トラップビット、または複数の表現は許可されない)。

整数型( integer types )のオブジェクト( short int long long long )が複数バイトを占有する場合、それらのバイトの使用法は実装定義ですが、主要な2つの実装方式は big-endian (POWER、Sparc、Itanium)と little-endian (x86、x86_64)です:ビッグエンディアンプラットフォームは整数が占有する記憶領域の最下位アドレスに最上位バイトを格納し、リトルエンディアンプラットフォームは最下位アドレスに最下位バイトを格納します。詳細は Endianness を参照してください。以下の例も参照してください。

ほとんどの実装ではトラップ表現、パディングビット、または整数型の複数表現は許可されていませんが、例外があります。例えばItanium上の整数型の値は トラップ表現となる可能性があります

有効型 (Effective type)

すべてのオブジェクトには effective type があり、これによってどの lvalue アクセスが有効で、どのアクセスがstrict aliasing規則に違反するかが決定されます。

オブジェクトが 宣言 によって作成された場合、そのオブジェクトの宣言型がオブジェクトの 実効型 となります。

オブジェクトが アロケーション関数 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を介したエイリアシングは許可される
    puts("This system is little-endian");
else
    puts("This system is big-endian");
float* pf = (float*)(&i);
float d = *pf; // UB: float左辺値*pはintへのアクセスに使用できない

これらのルールは、2つのポインタを受け取る関数をコンパイルする際に、コンパイラが一方への書き込み後に他方を再読み取りするコードを生成する必要があるかどうかを制御します:

// int* と double* はエイリアスできない
void f1(int* pi, double* pd, double d)
{
    // *pi からの読み取りは、ループ前に一度だけ実行可能
    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 qualifier を使用することで、上記の規則で許可されている場合でも、2つのポインタがエイリアスしないことを示すことができる点に注意してください。

型のパンニングは、 union の非アクティブメンバを通じて実行される場合もあることに注意してください。

アラインメント

すべての完全な object type alignment requirement と呼ばれる特性を持ち、これは size_t 型の整数値で、この型のオブジェクトが割り当て可能な連続するアドレス間のバイト数を表します。有効なアライメント値は非負の2のべき乗です。

型のアライメント要件は、 _Alignof (until C23) alignof (since C23) で問い合わせることができます。

(since C11)

構造体のすべてのメンバーのアライメント要件を満たすために、一部のメンバーの後にパディングが挿入される場合があります。

#include <stdalign.h>
#include <stdio.h>
// struct Sのオブジェクトは任意のアドレスに配置可能
// S.aとS.bの両方が任意のアドレスに配置可能なため
struct S
{
    char a; // サイズ: 1, アライメント: 1
    char b; // サイズ: 1, アライメント: 1
}; // サイズ: 2, アライメント: 1
// struct 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

各オブジェクト型は、その型のすべてのオブジェクトに対してアライメント要件を課します。最も弱い(最小の)アライメントは、 char signed char 、および unsigned char の型のアライメントであり、 1 に等しくなります。最も厳しい(最大の) 基本アライメント は実装定義であり、 max_align_t のアライメントと等しい (C11以降) です。

基本的なアライメントは、あらゆるストレージ期間のオブジェクトに対してサポートされています。

オブジェクトのアラインメントが max_align_t よりも厳格(大きい)に設定された場合、 _Alignof (C23まで) alignof (C23以降) を使用して、それは 拡張アラインメント要件 を持つ。拡張アラインメントを持つメンバーを含む構造体または共用体型は オーバーアラインド型 である。オーバーアラインド型がサポートされるかどうかは実装定義であり、そのサポートは 記憶域期間 の種類ごとに異なる場合がある。

構造体または共用体型 S がオーバーアラインド型のメンバーを持たないか、拡張アラインメントを指定するアラインメント指定子で宣言されていない場合、 S は基本アラインメントを持つ。

すべての 算術型 または ポインタ型 アトミック バージョンは基本アラインメントを持つ。

(C11以降)

不具合報告

以下の動作変更に関する不具合報告は、以前に公開されたC規格に対して遡及的に適用されました。

DR 適用対象 公開時の動作 正しい動作
DR 445 C11 型が _Alignas を使用せずに拡張アラインメントを持つ可能性がある 基本アラインメントを持たなければならない

参考文献

  • C17規格 (ISO/IEC 9899:2018):
  • 3.15 オブジェクト (p: 5)
  • 6.2.6 型の表現 (p: 33-35)
  • 6.2.8 オブジェクトのアライメント (p: 36-37)
  • 6.5/6-7 式 (p: 55-56)
  • C11規格 (ISO/IEC 9899:2011):
  • 3.15 object (p: 6)
  • 6.2.6 Representations of types (p: 44-46)
  • 6.2.8 Alignment of objects (p: 48-49)
  • 6.5/6-7 Expressions (p: 77)
  • C99規格 (ISO/IEC 9899:1999):
  • 3.2 アライメント (p: 3)
  • 3.14 オブジェクト (p: 5)
  • 6.2.6 型の表現 (p: 37-39)
  • 6.5/6-7 式 (p: 67-68)
  • C89/C90規格 (ISO/IEC 9899:1990):
  • 1.6 用語の定義

関連項目