とくにあぶなくないRiSKのブログ

危ないRiSKのブログだったかもしれない。本当はRiSKだけどググラビリティとか取得できるIDの都合でsscriskも使ったり。

境界調整とは (1)

C言語の現規格C99における境界調整(alignment, アラインメント)とは何でしょうか?
以下,一般的な「アラインメント」ではなく,規格で用いられている「境界調整」という語を使って説明します。というか,正確さ優先でほとんど規格からの抜き出しです。そのため境界調整と関係のない部分も説明してます。

境界調整とは?

3.2 境界調整(alignment) 特定の型のオブジェクトを特定のバイトアドレスの倍数のアドレスをもつ
記憶域境界に割り付ける要求。
X 3010:2003 (ISO/IEC 9899:1999)

と定義されています。
つまり

#include<stdint.h>
int main(){
 const int i = 0;
 const intptr_t address_of_i = &i;
}

ここで,address_of_i は const int 型特有のバイトアドレスの倍数の値を持ちます。それは4バイトかな?8バイトかな?何バイトかは処理系定義です。
もし,ある二つの型の値の表現と境界調整要求が同じであれば,関数の引数として,関数からの返却値として,又は共用体のメンバとして,両方の値を交換できます。

型変換

整数→ポインタ型

整数は任意のポインタ型に型変換できます。しかし特別な場合を除いて,結果は処理系定義です。それは正しく境界調整されていなかったり,実体を指していなかったり,トラップ表現であるかもしれないからです。

#include<stdint.h>
#include<stdio.h>
int main(){
 const char str[] = "abcde";
 const intptr_t num = str + 1; // とある整数を
 const int* const p = num;     // ポインタ型へ変換
 printf("%d", *p);             // 境界調整されてるの?おそらく処理系定義の動作
}
あるポインタ型→別のポインタ型

オブジェクト型又は不完全型へのポインタは,他のオブジェクト型又は不完全型へのポインタに型変換できます。しかし,その結果のポインタが新しい被参照型に正しく境界調整されていなければ未定義動作になります。

#include<stdio.h>
int main(){
 const int i = 128;        // とあるオブジェクト型
 const long* const p = &i; // へのポインタを,別のポインタ型へ変換
 printf("%ld", *p);        // 境界調整されてるの?おそらく未定義動作
}

境界調整が同じ型

符号付き整数型と符号無し整数型

ある符号付き整数型は対応する符号無し整数型と同じ大きさの記憶域と同じ境界調整要求を持ちます。
つまり,

  • signed char と unsigned char は,サイズと境界調整が同じ
  • signed short と unsigned long は,サイズと境界調整が同じ
  • signed int と unsigned int は,サイズと境界調整が同じ
  • signed long と unsigned long は,サイズと境界調整が同じ
  • signed long long と unsinged long long は,サイズと境界調整が同じ
複素数型と二要素の配列

ある複素数型は対応する実数型の二つの要素を持つ配列型と同じ表現と同じ境界調整を持ちます。
つまり,

  • float _Complex と float[2] は,表現と境界調整が同じ
  • double _Complex と double[2] は,表現と境界調整が同じ
  • long double _Complex と long double[2] は,表現と境界調整が同じ
非修飾版と修飾版

ある非修飾型は対応する const, volatile, restrict の組合せの修飾版と同じ型分類と同じ表現と同じ境界調整要求を持ちます。
つまり,

  • int と const int は,型分類と表現と境界調整が同じ
  • int と volatile int は,型分類と表現と境界調整が同じ
  • int と const volatile int は,型分類と表現と境界調整が同じ
  • void* と void* const は,型分類と表現と境界調整が同じ
  • void* と void* volatile は,型分類と表現と境界調整が同じ
  • void* と void* restrict は,型分類と表現と境界調整が同じ
  • void* と void* const volatile は,型分類と表現と境界調整が同じ
  • void* と void* const volatile restrict は,型分類と表現と境界調整が同じ
  • (他の組合せは省略しますが上と同様です)
void へのポインタと文字型へのポインタ

void へのポインタは文字型へのポインタと同じ表現と同じ境界調整要求を持ちます。
つまり,

  • void* と char* は,表現と境界調整が同じ
  • void* と signed char* は,表現と境界調整が同じ
  • void* と unsigned char* は,表現と境界調整が同じ
適合する型へのポインタ

適合する型*1へのポインタ同士は同じ表現及び同じ境界調整要求を持ちます。
つまり

typedef int compatible_with_int;

としたときの

  • compatible_with_int* と int* は表現と境界調整が同じ

また,

enum hoge{fuga};

としたときの,enum hoge が int と適合する場合において*2

  • enum hoge* と int* は表現と境界調整が同じ
構造体型へのポインタ

構造体型へのポインタは全て同じ表現と同じ境界調整要求を持ちます。
つまり,

struct s1{int i;};
struct s2{long l;};

としたときの

  • struct s1* と struct s2* は表現と境界調整が同じ
共用体型へのポインタ

共用体型へのポインタはすべて同じ表現と同じ境界調整要求を持ちます。
つまり,

union u1{char c; short s;};
union u2{int i; long l;};

としたときの

  • union u1* と union u2* は表現と境界調整が同じ

境界調整が違う型

上で説明していないその他の型へのポインタは,同じ表現又は同じ境界調整要求を持つとは限りません。

参照元

X 3010:2003 (ISO/IEC 9899:1999)
3.2 境界調整
6.2.5 型
6.3.2.3 ポインタ
6.5.3.2 アドレス及び間接演算子

もっと境界調整

続きがあります。境界調整とは (2)もご覧ください。

*1:この「適合する型」というのが難しい。int と int のように同じ型同士は適合する。これは分かりやすいんだけど,それ以外の例外がややこしい…。

*2:enum hoge は char,符号付き整数型または符号無し整数型のいずれかと適合する。どの型になるかは処理系定義。