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

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

境界調整とは (2)

このエントリは境界調整とは (1)の続きです。
C言語の現規格C99における境界調整(alignment, アラインメント)についてさらに。

境界調整は推移的

前回のエントリではいくつかの型が同じ境界調整を持つことを説明しました。実は「境界を正しく調整する」という概念は推移的です。例えば,型Aのポインタが型Bのポインタとして境界を正しく調整されていて,型Bのポインタが型Cのポインタとして境界を正しく調整されているならば,型Aのポインタは型Cのポインタとしても境界を正しく調整されています。

境界調整と関係のある事柄

間接演算子

正しくない値がポインタに代入されている場合,間接演算子(単項*演算子)の動作は未定義になります。この「正しくない値」には,指されるオブジェクトの型に対して正しく境界調整されていないアドレスが含まれます。

int main(){
 char buf[sizeof(int)];
 int* const p = buf;
 *p = -1414677826; // *p... 境界調整されてるの?
}
構造体と共用体

構造体または共用体の持つビットフィールドのアドレス付け可能な記憶域単位の境界調整は未規定となっています*1。そして,ビットフィールド以外の各メンバの境界は,その型に適した処理系定義の方法で調整されます。この詰め物(パディング)は境界調整(アラインメント)のために入れられるんですね。なお,穴の内容は不定なので,memcmp関数での比較で問題が起こる可能性があります。
フレキシブル配列メンバを持つ構造体の場合,この配列の長さは未規定のため,個々の場合にこの構造体の境界調整が変わるかもしれません。

struct s{
 char c;
 char flexible[];
};
void f(){
 struct s s_of_f;
}
int main(){
 struct s s_of_main;
}

このとき,s_of_f と s_of_main の境界調整は異なるかもしれません。*2

記憶域管理関数

calloc 関数,malloc 関数および realloc 関数の割付けが成功したときに返されるポインタは,どんな型へのポインタへ代入したりオブジェクトへアクセスしたりできるように適切に境界調整されています。おそらく,これらの関数は全ての型の境界調整の公倍数となる値を返すように実装されているのでしょう。

#include<stdlib.h>
int main(){
 char* const pc = malloc(sizeof(char));
 short* const ps = malloc(sizeof(short) * 2);
 int* const pi = malloc(sizeof(int) * 3);
 long* const pl = calloc(1, sizeof(long));
 long long* const pll = calloc(2, sizeof(long long));
 float* const pf = calloc(3, sizeof(float));
 double* const pd = realloc(0, sizeof(double));
 struct s{char c; short s;};
 struct s* const pstruct = realloc(0, sizeof(struct s) * 2);
 void* const pv = realloc(0, sizeof(struct s) * 3);
 // 以上の全てのポインタは境界調整されている値。
 // 正しい代入とオブジェクトへのアクセスができる

 free(pc), free(ps), free(pi),
 free(pl), free(pll), free(pf),
 free(pd), free(pstruct), free(pv);
}

参照元

X 3010:2003 (ISO/IEC 9899:1999)
6.3.2.3 ポインタ
6.7.2.1 構造体指定子及び共用体指定子
7.20.3 記憶域管理関数
7.21.4.1 memcmp 関数

*1:ビットフィールドは境界調整以外のことも処理系定義のことが多いです

*2:余談。サイズの場合は単に最後の配列メンバのオフセットになります。変わることはありません。