以前このサイトとブログに,何度かアラインメントに関する記事を書きました (サイト内関連ページ参照). そのせいか「アラインメント」で検索して来てくれる人が多いので, 過去の記事に加筆修正してこのページを新たに作成しました.
加筆した点は次のとおりです.
C言語等の解説サイトでは, 構造体のアラインメントについて解説しているところも多いのですが, その場合問題にしているのはメンバのアラインメントであって, 構造体全体のアラインメントについては何も触れていません. そのうえメンバは (暗黙のうちに) 基本データ型という前提になっています.
つまり複合データ型それ自体のアラインメントはどうなるのか,
ということに触れているサイトはまだ見たことがありません.
しかし上の追記に挙げたのサイトも, 「なぜ複合データ型のアラインメント規則がそうなっているのか」 ということは説明していません.
たぶんこのページは,日本中で最も (もしかしたら唯一の?)
詳しくアラインメントを解説したページだと思います.
(2007/01/14(日) 現在)
このページの主な更新は Blog でお知らせします.
以前某所で malloc() のアラインメントについて書いたとき, 「それはC言語の仕様なのか?」と訊かれたことがある. また「C言語 アラインメント」などで検索してここに来る人も多い. アラインメントを C/C++ の問題だと思っている人が多いようだが, 特定のプログラミング言語に起因する問題ではない. アラインメントは機能ではなく制約なので, わざわざそんなものを好きこのんで言語仕様に入れるメリットは何もない.
アラインメントは CPU のハードウェアに起因する問題であり, データのメモリアドレスに関する制約である. C/C++ (そしてアセンブラ) でそれが問題となる理由は, データのメモリアドレスがプログラマに見える (操作できる) 言語だからである.C/C++ のアラインメントに関する仕様は, CPU の制約を「追認 (または代弁)」しているにすぎない.
CPU が扱うデータ型は,基本的には整数型と浮動小数型だけである. 文字列は文字コード (整数型) の配列として表現される. 32ビット CPU では普通,扱える整数型のサイズは1,2,または4バイトである. これらはC言語ではそれぞれ,(signed または unsigned) char,short,int (または long) 型と呼ばれる. 浮動小数型には4バイト長の単精度型 (C言語の float 型) と, 8バイト長の倍精度型 (C言語の double 型) がある. (x86 ファミリーの中には,8バイト整数,16バイト整数, 10バイト拡張倍精度浮動小数を扱えるものもあるが,ここでは割愛する.)
最近のほとんどの CPU は,メモリ上のデータを1バイト単位で読み書きできる (バイトマシン)注1. つまりメモリは,巨大なバイト配列と考えることができる. 1バイト整数をメモリに格納する場合,どれでもいいから1つの配列要素を選び, そこに格納すればよい.その配列要素の番号がメモリアドレスとなる. (C言語の配列と同じく,要素番号 (メモリアドレス) は0から始まる.)
2バイト以上の整数型や浮動小数型を格納するには複数の配列要素が必要なので, 例えば4バイト整数なら4つの連続する配列要素に格納する. この場合,最初の配列要素の番号が4バイト整数のメモリアドレスとなる. 注意すべき点は,1バイトデータの場合とは異なり, 複数バイトデータのメモリアドレスは好きなように決められるわけではないということである.次にこの点について説明する.
このページの AOF32L(l) というマクロは,変数 l
のアドレスを返すが,Cray
の場合だけはそれより4バイト後ろのアドレスを返すらしい (マクロ定義は こちら ).ということは,&l がワードの先頭で
AOF32L(l) が有効な4バイトの先頭なのか?
Cray では,構造体の先頭メンバや共用体のメンバが short 型の場合, そのオフセットは4になるのかもしれない.
CPU とメモリの間は,データをやりとりするための電線の束で結ばれている.
これをデータバスという.32ビット CPU では普通,この電線は32本あり
(32ビット・データバス,下図),CPU はメモリ上のデータを一度に32ビット
(=4バイト) 読み書きすることができる注2.
このため,メモリの最初の4バイト (アドレス0〜3),次の4バイト
(アドレス4〜7),更に次の4バイト (アドレス8〜11)
… は,それぞれ一度で読み書きできる.
普通,このような4バイトをワード (word:語) と呼ぶ.
(注意:x86 など 8/16 ビットから進化してきた CPU
シリーズでは,16ビット時代の名残で2バイトをワード,
4バイトをダブルワードなどと呼ぶものが多い.
逆に最初から32ビットとして登場した CPU シリーズでは,
32ビットをワード,16ビットをハーフワードと呼ぶものもある.)
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ 32ビットCPU ┃
┗┯┯┯┯┯┯┯┯━┯┯┯┯┯┯┯┯━┯┯┯┯┯┯┯┯━┯┯┯┯┯┯┯┯┛
↑↑↑↑↑↑↑↑ ↑↑↑↑↑↑↑↑ ↑↑↑↑↑↑↑↑ ↑↑↑↑↑↑↑↑
データバス ││││││││ ││││││││ ││││││││ ││││││││
↓↓↓↓↓↓↓↓ ↓↓↓↓↓↓↓↓ ↓↓↓↓↓↓↓↓ ↓↓↓↓↓↓↓↓
アドレス ┏┷┷┷┷┷┷┷┷┳┷┷┷┷┷┷┷┷┳┷┷┷┷┷┷┷┷┳┷┷┷┷┷┷┷┷┓┐
0x00000000┃ バイト ┃ バイト ┃ バイト ┃ バイト ┃│
┠────────╂────────╂────────╂────────┨│
0x00000004┃ ┃ ┃ ┃ ┃│
┠────────╂────────╂────────╂────────┨│
0x00000008┃ ┃ ┃ ┃ ┃│
┠────────╂────────╂────────╂────────┨│
0x0000000C┃ ┃ ┃ ┃ ┃│メモリ
┠────────╂────────╂────────╂────────┨│
: : : : : :│
: : : : : :│
┠────────╂────────╂────────╂────────┨│
0xFFFFFFFC┃ ┃ ┃ ┃ ┃│
┗━━━━━━━━┻━━━━━━━━┻━━━━━━━━┻━━━━━━━━┛┘
(ワード内 +0x00000003 +0x00000002 +0x00000001 +0x00000000 ← リトルエンディアンの場合
アドレス) +0x00000000 +0x00000001 +0x00000002 +0x00000003 ← ビッグエンディアンの場合
←─────────────── ワード ───────────────→
したがって4バイト整数または4バイト浮動小数をメモリに格納する場合, 先頭アドレスが4の倍数になるようにすれば上図の1ワードに収まるため, 1回のメモリアクセスで読み書きできる. このようにデータの先頭アドレスを4の倍数にすることを, 「4バイト境界にアライン (align:整列,位置合わせ) する」という.
では,4バイトデータが4バイト境界にアラインされていない
(4バイト境界をまたぐ,ともいう) 場合はどうなるのか?
この場合,4バイトデータXは下図の例のように複数ワードにまたがっている.
(Y,Zは別のデータ)
: : : : : : : : : : ┠────────╂────────╂────────╂────────┨ ┃ Y ┃ Y ┃ Y ┃ X ┃ ┠────────╂────────╂────────╂────────┨ ┃ X ┃ X ┃ X ┃ Z ┃ ┠────────╂────────╂────────╂────────┨ : : : : : : : : : :
このようなXを読み書きするように命令された CPU はどうするだろうか? 「2回に分けて読み書きする」と思うだろう.x86 は実際そのように動作する. 当然読み出しにはメモリアクセス1回分余計に時間がかかる. 最近の (キャッシュを持つ) CPU にとって,メモリアクセスは非常に時間のかかる処理である.さらに, アラインされていないデータがワード境界どころかキャッシュライン境界をもまたいでいた場合, もう一つのキャッシュライン (普通は32〜256バイトらしい) も読むはめになってしまう!
書き込みの場合はもっとメモリアクセス回数が増える場合もある. 仮に CPU が上図の2ワードに対して単純にXを書き込んだらどうなるだろうか? YとZが巻き添えを食らって (無効な値に) 書き替えられてしまう. これを防ぐには,書き込み前に上図の2ワードを読み出しておかなければならない. つまり合計4回のメモリアクセスが必要になる.
ただし CPU がワードに書き込みを行う際, ワード内のどのバイトに書き込むかを選択できる回路がついていれば, そのような問題は発生しない. この場合のメモリアクセス回数は読み出しの場合と同じである.
x86 以外の多くの CPU (特に
RISC)
は上記のようには動作せず,エラーとして処理する.
つまり「不正アラインメント例外注3」を発生させる.
このとき動いている OS が UNIX 系ならば,
SIGBUS
というシグナルが発生してコアダンプするというオチがつく
( HP-UX での実例 ).
他の OS でも同様だろう.
なぜエラー扱いするのかは CPU の設計者に聞いてみないとわからないが, まず間違いなく CPU の回路を単純にするためだろう. ワード境界をまたぐデータを2回に分けてアクセスするとなると, それだけ制御回路が複雑になる.
(そのうち暇ができれば, アラインメントと CPU 回路の単純化の関係について追記するつもり.)
ARM も RISC なので不正アラインメント例外を発生するが, ARM Linux カーネルは例外ハンドラで x86 と同様の「アラインされていないデータのアクセス」を実現しているらしい. ただしこれは x86 に比べて非常に遅いはず.
2007/10/25(木) 追記
CPU によっては,アラインメント例外を発生させる代わりに,
アドレスの下位ビットを強制的に0と見なして処理するものもある
(例:H8/300 シリーズ → 実験結果 ).
2009/05/30(土) 追記
参考:ハードウェアを意識したプログラミングの基礎(後編)
x86,ARM,PowerPC,MicroBlaze でアラインされていないデータにアクセスしてみる実験.
ARM v6 以降は寛容になったらしい.
何バイト境界にアラインすればよいか (これをアラインメント注4 (alignment) という) は,CPU とデータ型によって異なるが,一般には2の冪乗 (1,2,4,8,16,…) になる. 基本的なデータ型のアラインメントは,その型のサイズ (バイト数) に一致することが多い. つまり1バイト整数のアラインメントは1バイト, 2バイト整数のアラインメントは2バイト, 4バイト整数/単精度浮動小数のアラインメントは4バイト, 8バイト倍精度浮動小数のアラインメントは8バイトである.
ただし例外もある.例えばデータバス幅より大きい基本データ型をサポートする CPU では,データバス幅がアラインメントの上限になる場合もある (最初に書いたメモリアクセス回数の話からすれば当然). 例えば16ビット CPU H8/300 シリーズは4バイト整数型をサポートするが,そのアラインメントは2バイトである. (H8 の他のシリーズは使ったことがないので未確認.)
たまに「(CPU名) (データ型名) アラインメント」等で検索してくる人がいるけど, 最初に見るべきなのはコンパイラか CPU のマニュアル. そんな検索ワードじゃ無関係なページが山盛り出てくるうえ, 自分の環境と違うものを間違えて見つけてしまうかもしれない. 手抜きをするつもりが時間の浪費になってしまう. 最初からマニュアルを見る方が早くて確実.
プロセッサによっては, サイズが2の冪乗でない基本データ型 (24ビット整数や48ビット整数,80ビット浮動小数など) を持つものもある. それらのアラインメントについては個別に確認する必要がある. そのようなデータ型をC言語で扱う場合には注意が必要である. (後述の「sizeof とアラインメントの関係」を参照.)
修正中 (前提条件追記)
- ここでは説明の都合上C言語の用語や構文を使うが,特定の言語 (特に C/C++) の複合型の仕様についての話ではなく, メモリ上の物理的なデータ配置に関する CPU の制約の話である. したがって言語仕様よりも強制力が強い. (言語の規格に従わない「なんちゃって言語」でも動作するプログラムは書けるが, アラインメント制約を守らなければ動作しなかったり, 速度低下を招いたりする.)
- ここでいう「構造体」,「共用体」,「配列」の定義 (たぶんCの規格よりも厳しい定義).
- 不要なパディングを入れない (特に先頭部分).
⇒ 構造体の最初のメンバ,配列の先頭要素, 共用体の全メンバのオフセットは必ず0.- 構造体は指定された順序でメンバが並ぶ.
- 「正味のサイズ」の定義 (配列と共用体の場合についても追記すべき).
- C++ の class/struct は,隠れたメンバ変数 (仮想関数テーブルへのポインタ) を持つ場合があったり, オフセット計算の基準位置が状況によって変わったりするので, ここでは全く対象外.
ある複合データ型全体のアラインメントを求めるには, 次のように考えてみるとわかりやすいだろう.
複合データが,ある適切な (つまりアラインされた) アドレスに配置されている. このデータのアドレスを少し移動させたい場合, 何バイト単位で移動させることができるか?
複合データ型の変数 D に含まれる各要素データを Di,そのアラインメントを Ai とする (i=1,2,…,n). Di は Ai バイト単位でしか移動できないので, D もまた Ai バイト単位でしか移動できない. これをすべての Di について満たすためには, D は A1,A2,…,An の最小公倍数 LCM(A1,A2,…,An) バイト単位でしか移動できない.したがって D のアラインメントは LCM(A1,A2,…,An) である. アラインメントは2の冪乗でなければならないから, それらの最小公倍数は最大値に等しい.したがって D のアラインメントは Max(A1,A2,…,An) である.
つまり複合データ型のアラインメントは, それに含まれる要素のアラインメントの中で最も大きい (厳しい) ものに等しい. 特に配列の場合はすべての要素が同じ型なので, 配列のアラインメントは要素のアラインメントに等しい.
複合データ D のアドレスを Addr(D) と書くことにする. D の要素 Di の,D の先頭からのバイト・オフセットを Offset(Di) とすると,次式が成立する.
Addr(Di) = Addr(D) + Offset(Di)
D が構造体 (型名を D_t とする) の場合, 上の式をC言語の構文で書けば次のようになる.
(char*)&Di == (char*)&D + offsetof(D_t, Di)
Di のアラインメントを Ai とすると, Addr(Di) は Ai の倍数である (アラインメントの定義). また「複合データ型全体のアラインメント」 に書いたことから,Addr(D) も Ai の倍数である. したがって Offset(Di) もまた Ai の倍数でなければならない.つまり複合データ型の要素のオフセットは, その要素のアラインメントの倍数でなければならない.
「構造体メンバのオフセットは, そのメンバのアラインメントの倍数に切り上げられる (パディングが挿入される)」ということは, C言語を解説している本やサイトでよく見かけるが,その理由は上記のとおりである. これをちゃんと説明しているサイトはほとんどない. まして,複合データ型のアラインメントを解説しているサイトは全く見たことがない (本はどうだか知らないが).
なお,共用体の場合はメンバのオフセットは常に0なので, このことを考慮する必要はない.
D が配列の場合,最初の要素はオフセット0なので何の問題もない. 要素のサイズがアラインメントの倍数の場合には, 次の要素は前の要素のすぐ後に続けて配置すればよい. そうでない場合には, 要素サイズをアラインメントの倍数に切り上げたオフセットに配置しなければならない. 3番目以降の要素も同様である.したがって配列要素の間隔 (stride) は, 要素の正味のサイズ注5ではなく, それをアラインメントの倍数に切り上げた値でなければならない.
普通の基本データ型ではサイズとアラインメントが同じなのでこの点は問題ないが, 構造体を配列にしようとすると問題になる可能性がある. 例えば次の構造体を配列にすることを考えよう.
typedef struct {
int a; // サイズ,アラインメントとも4バイトとする.
char b; // サイズ,アラインメントとも1バイト (Cの仕様).
} S_t;
b は1バイト・アラインメントなので, アラインメント調整は必要なく,a と b の間にパディング (隙間) ができることもない.したがってこの構造体 S_t の正味のサイズは4+1=5バイト, アラインメントは最も厳しいメンバである a と同じ4バイトである. S_t を配列にする場合,要素間隔は正味の S_t のサイズ (5バイト) を S_t のアラインメント (4バイト) の倍数に切り上げた8バイトとなる. つまり b の後ろには3バイトのパディングが必要.
実は,C言語の sizeof 演算子が返すのは構造体の正味のサイズではなく, それを配列にしたときの要素間隔である.つまり sizeof(S_t) == 8 (「sizeof とアラインメントの関係」を参照).
構造体のアラインメントに関して,次のように誤解 (中途半端に理解) している人を見かけたことがある.

以前から「構造体 4バイト(境界)」とか「構造体 4の倍数」などの検索ワードが多かったので, (32ビット CPU では) 構造体のアラインメントは常に4バイトだと思い込んでいる人が多そうで不安になる. さらに悪いことに, 冒頭の図をみて「そうか,だから4バイトなのか!」と早トチリした人もいそうだ.(苦笑)
実際,(このページを読んでもまだ) この誤解をしている人をネット上で2,3人見かけた.
(ということは全国に少なくとも2〜3千人は隠れてるってことですね,わかります.)
身に覚えのある人は, 下記の構造体のサイズとアラインメントがいくつになるか, 自分が使っている処理系で確認して反省してください.(笑)
// ●前提 (char 以外については処理系依存)
// ・char のサイズおよびアラインメントは1バイト (Cの仕様).
// ・short のサイズおよびアラインメントは2バイトとする.
// ・int,long,float のサイズおよびアラインメントは4バイトとする.
// ・long long,double のサイズおよびアラインメントは8バイトとする.
struct S1 {
short s;
unsigned char uc[3];
};
struct S2 {
char string[10];
unsigned char uc;
};
// x86 用 gcc では -malign-double オプションを指定すること.
// (デフォルトでは,double および long long のアラインメントが4バイトになっている.
// これはデータバスが32ビットだった 386,486 との互換性のためだろう.)
struct S3 {
double d;
long long ll;
char c;
};
上記の前提を満たす処理系ならば,結果は次のようになるべき.
sizeof(struct S1)=6,AlignmentOf(struct S1)=2.(2バイト境界に配置される.サイズは2の倍数.) sizeof(struct S2)=11,AlignmentOf(struct S2)=1.(任意のアドレスに配置される.サイズはメンバのサイズの単純合計.) sizeof(struct S3)=24,AlignmentOf(struct S3)=8.(8バイト境界に配置される.サイズは8の倍数.)
そうなる理由は「複合データ型のアラインメントのまとめ」を読めばわかるはず.
この項目は該当する人が多そうなので以前から追加しようと思っていたんだけど,
忙しいので後回しにしていた.
今回追加したきっかけは,同じ誤解をしているコンパイラ(!)を見つけたから.(↓)
[Armadillo:04936] Armadillo-210の構造体について
構造体の配列を共有メモリに展開するプログラムのデバック中に、実サイズより多くの領域を使用している事が解かり
以下のサンプル作りを実行すると、4バイトと表示しました。struct test{ char ChNo[1]; }w_mem;
アラインメントを変更するコンパイル・オプションや #pragma などを指定せずにこうなっているのだとしたら,このコンパイラもまた, 構造体のアラインメントは常に4バイトだと誤解している可能性がある. double 型や long long 型のメンバが含まれている構造体のアラインメントを4バイトとして扱うと, 不正アラインメントエラーが発生する. こういう災厄を招く勘違いコンパイラは,即刻クビにするのが吉.
正解はいずれも,「メンバのサイズ」ではなく「メンバのアラインメント」である.
したがってこれらの誤解が「正しい」のは,「サイズ=アラインメント」である場合,
つまりメンバが (普通の) 基本データ型の場合に限られる.
メンバが複合データ型や,普通でない (サイズが2の冪乗でない,
あるいはアラインメントとサイズが一致しない) 基本データ型の場合には通用しない.
(アラインメントについて解説している他のサイトでも,上記のような記述を見かけた.)
確かにCコンパイラは,アラインメントの「調整」はしてくれるが, 普通は「最適化」はしない … というより,C言語の仕様上,してはいけない. ここでいう「調整」とは,上で書いたように 「メンバのオフセットをそのメンバのアラインメントの倍数に切り上げる」 ことで,Cコンパイラが普通に行っている.
一方「最適化」となると, 「構造体メンバの順序を入れ替えて,できるだけ隙間ができないようにする」 としか解釈できないが, C言語の仕様上,メンバの順序を入れ替えることは許されない.
参考:移植性のあるCプログラミング | 構造体メンバの配置
しかし,上の参考ページにも書いてあるが, Cコンパイラの中にはそういう最適化をしてくれる非標準拡張機能を持つものもあるらしい. その機能を ON にすると,Cの規格に違反することになるので普通じゃありません!
それから「(アラインメントを) 気にしなくていい」という点に関して言えば, 次のような場合 (とりあえず,今思いついたものだけ) には気にしないといけません.
最初の2つの場合に上記の「最適化オプション」を使うと, メンバの順序が勝手に変えられてとんでもないことになる. Memory Mapped I/O において, ハードウェアレジスタを構造体として記述している場合も同様.
構造体メンバの順序を入れ替えて隙間を最小にする最適化アルゴリズムは昔趣味で考えたことがあり,
仕事でも10年ほど前に使った.いずれこのサイトで公開しようと思って,
トップページには開設当初から目次に
(淡色表示で)
書いてあるが,いまだに公開していない….(^^;)
このページへのアクセスが最近急増したので,そろそろ公開しようかな….
「"構造体" "メンバの順序" "C言語"」で Google 検索してみたところ, ビットフィールドのメンバの順序を入れ替えて最適化を行うというコンパイラを見つけた. ↓これは違反じゃない … よね?
ルネサス テクノロジ - よくあるお問い合わせ検索結果
「複合データ型の各要素のオフセット」でも書いたとおり, sizeof(X) の値は,正味のXのサイズ (バイト数) ではなく, それをXのアラインメントの倍数に切り上げた値である.
なぜ sizeof がそういう仕様なのか,わかりますか?
C言語の規格では,n個の要素からなる配列 array[] に関して,
n == (sizeof(array) / sizeof(array[0]))となることを要求している.この仕様は事実上次のことを意味する.
sizeof(X) が返す値は,X の正味のサイズではなく, X を配列要素にした場合の配置間隔 (stride) でなければならない.
両者が異なるのは,構造体型ではよくあることだが, サイズが2の冪乗でない基本データ型でも起こりうるので注意が必要.
余談:C言語の中・上級者向けのサイトのいくつかで, 著者が明らかに上記のことを理解して書いているとわかる記述を見たことがある. しかしなぜか上記のことを明言しているのを見たことがない.なんでだろう?
Cの標準ライブラリに含まれる malloc() (およびそのファミリーである calloc(),realloc()) が返すアドレスは, すべてのデータ型に適合するようにアラインされている (malloc() の仕様).また,コンパイラが alloca() をサポートしていれば,それも同様のアドレスを返すはずである.
多くの32ビット CPU では,(標準的なCがサポートするデータ型の中で) 最もアラインメントの厳しいデータ型は double 型 (8バイト・アラインメント) である (たぶん). したがって多くの32ビット CPU 用の malloc() は, 8バイトの倍数のアドレスを返す.
この話題については,こちらのページもご覧ください.
標準的なC言語では,データ型のサイズを取得する sizeof 演算子はあるものの, アラインメントを取得する演算子はない. そこで私は昔 (1993年),アラインメントを取得するための処理系・OS 独立なマクロ AlignmentOf() を考案し, 今でもメモリ管理によく使っている.
Microsoft C では,同様の機能を持つ演算子 __alignof() という演算子があるが, これは実際のアラインメントと一致しない場合があるようである (現在調査中,バグというわけではなくそういう仕様らしい).
GCC には __alignof__ という演算子があるが,これについては未調査.
(そのうち暇ができれば書くつもり.)
「複合データ型の各要素のオフセット」で 「共用体の場合はメンバのオフセットは常に0」と書いた点について, 「C/C++ の規格で保証されているのか?」という質問を受けたので追記. 私は C/C++ の規格には全然詳しくないし, 規格書も持っていないので, 規格に関する質問には直接回答できないことを最初にお断りしておきます. (詳しい方,教えてください. ところで IS って何? Defect Report って何? おいしいの?)
とりあえず手持ちの本などを調べてみると,次のような記述があった.
実際には, 共用体は全メンバーの起点からのオフセットが0である構造体である。(p.180)
5.7.1 共用体構成要素の配置 (p.157)
共用体型のオブジェクトは、そこに含まれるどの 構成要素にも合った記憶域整列境界から始まる。(中略)static union U { ... int C; ... } object, *P = &object;この共用体では、次の2つの等値式が成立する。(union U *) &(P->C) == P &(P->C) == (int *) Pまたこれらの等値式は、構成要素 C の型や、 C の前後の構成要素が何であるかに関係なく成立する。
Unions in various programming languages
C/C++
In C and C++, untagged unions are expressed nearly exactly like structures (structs), except that each data member begins at the same location in memory.
なお,C/C++ の規格書の文言に頼らず自分の頭で考えてみると次のようになる.
まず,共用体を本来の目的である「複数の変数が同じメモリ領域を共用する」 ためだけに使うのであれば, すべてのメンバのオフセットが同じである必要はない. 例えば次の共用体 (各メンバのアラインメントはサイズと同じとする) では, u32 のオフセットは0だが,u16 のオフセットは0または2,u8 のオフセットは0〜3のいずれでもよいはずである.
union {
uint32_t u32;
uint16_t u16;
uint8_t u8;
};
しかし現実には,共用体は昔から type punning (あるデータ型のバイト列を別の型として解釈する) のために多用されている. 例えば次のような共用体を用いて float 型のエンディアン変換を行ったり, 浮動小数演算ではなくビット演算だけで符号を高速判定したりすることができる.
union {
float f;
uint32_t u32;
uint8_t u8[4];
};
このような使い方を規格が容認しているのであれば, すべてのメンバを同じオフセットに配置する必要がある. そしてそれは0以外にはない (共用体のサイズを無駄に大きくしない限り). もし容認していないとすると, 低レベルプログラミング言語として必須の type punning ができなくなってしまう (もっとも,変数のアドレスを別の型のポインタにキャストするという代替手段もあるが).
このページの主な更新は Blog でお知らせします.
|
Copyright © 2006-2010 noocyte, All rights reserved. E-mail: relipmoced (a) yahoo.co.jp (" (a) " を半角のアットマークに書き替えてください.) リンクはご自由に. 「noocyte のプログラミング研究室」トップページに戻る. |
|