noocyte のプログラミング研究室 〜プログラムは楽しげに走らねばならない♪〜

データ型のアラインメントとは何か,なぜ必要なのか?

公開:2007/01/14(日)
最終更新:2017/03/26(日)

以前このサイトとブログに,何度かアラインメントに関する記事を書きました (サイト内関連ページ参照). そのせいか「アラインメント」で検索して来てくれる人が多いので, 過去の記事に加筆修正してこのページを新たに作成しました.

加筆した点は次のとおりです.

たぶんこのページは,日本中で最も (もしかしたら唯一の?) 詳しくアラインメントを解説したページだと思います.
(2007/01/14(日) 現在)

このページの主な更新は Blog でお知らせします.


0.目次

  1. CPU に関する基礎知識
  2. アラインメントとは何か?
  3. アラインされていないデータへのアクセス
    1. アラインメントに寛容な CPU の場合 (alignment-tolerant processors)
    2. アラインメントに厳格な CPU の場合 (alignment-strict processors)
  4. アラインメントの基礎知識のまとめ
  5. 複合データ型 (配列,構造体,共用体) のアラインメント
    1. 複合データ型全体のアラインメント
    2. 複合データ型の各要素のオフセット
    3. sizeof(共用体)
    4. 複合データ型のアラインメントのまとめ
    5. 構造体のアラインメントに関する誤解
  6. C言語におけるアラインメント
    1. sizeof とアラインメントの関係 (sizeof がアラインメントの倍数に切り上げって知ってます?)
    2. malloc() 等が返すアドレス (下位数ビットは常に0って知ってます?)
    3. アラインメントを取得するマクロと演算子
  7. CPU回路の単純化とアラインメントの関係
  8. 補足
    1. 共用体の全メンバのオフセットが0である理由
  9. 参考図書
  10. サイト内関連ページ
  11. 外部へのリンク
  12. 更新履歴

1.CPU に関する基礎知識

以前某所で 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バイトデータの場合とは異なり, 複数バイトデータのバイトアドレスは好きなように決められるわけではないということである.次にこの点について説明する.



2008/04/06(日) 追記
注1:このページの内容は, あくまでも CPU がバイトマシンであることが前提. ワードマシンの場合, そもそも「バイトアドレス」が存在しない機種もある. また Cray (1ワード=8バイトのワードマシン) の場合, short 型の有効ビット数は32ビットだが,1ワードを占有してしまうらしい (リンク先ちゃんと読んでないけど). この変数のアドレスはワードの先頭なのか,それとも有効な4バイトの先頭なのか?

このページの AOF32L(l) というマクロは,変数 l のアドレスを返すが,Cray の場合だけはそれより4バイト後ろのアドレスを返すらしい).ということは,&l がワードの先頭で AOF32L(l) が有効な4バイトの先頭なのか?

Cray では,構造体の先頭メンバや共用体のメンバが short 型の場合, そのオフセットは4になるのかもしれない.


2.アラインメントは何か?

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ビットをハーフワードと呼ぶものもある.)


[画像:CPUとメモリの接続]

したがって4バイト整数または4バイト浮動小数をメモリに格納する場合, 先頭アドレスが4の倍数になるようにすれば上図の1ワードに収まるため, 1回のメモリアクセスで読み書きできる. このようにデータの先頭アドレスを4の倍数にすることを, 「4バイト境界にアライン (align:整列,位置合わせ) する」という.


注2:現実には,Nビット CPU のデータバス幅がNビットであるとは限らない.x86 を例に取ると,32ビット CPU とは言いながら Pentium 以降は64ビット・データバスである. 古くは16ビット CPU 8086 (元祖 x86) の8ビット・データバス版 8088 (初期 IBM PC に採用) というのもあった.

■参考


3.アラインされていないデータへのアクセス

では,4バイトデータが4バイト境界にアラインされていない (4バイト境界をまたぐ,ともいう) 場合はどうなるのか?
この場合,4バイトデータXは下図の例のように複数ワードにまたがっている. (Y,Zは別のデータ)


[画像:4バイト境界にアラインされていない4バイトデータ]

3.1 アラインメントに寛容な CPU の場合 (alignment-tolerant processors)

このようなXを読み書きするように命令された CPU はどうするだろうか?  「2回に分けて読み書きする」と思うだろう.x86 は実際そのように動作する (ただし後述する場合を除く). 当然読み出しにはメモリアクセス1回分余計に時間がかかる. 最近の (キャッシュを持つ) CPU にとって,メモリアクセスは非常に時間のかかる処理である. 例えばメモリ上のデータを読み書きする命令は,それがキャッシュに入っていない場合, 最も速い命令 (整数型の加減算やビット演算など) に比べて約100倍以上の時間がかかる.

参考メモリのアクセス時間とローカルメモリ (コンピュータアーキテクチャの話 135,安藤壽茂,マイナビニュース)

ここまでは、メモリは1サイクルか2サイクルでアクセスができるという想定でパイプラインを考えてきたが、この想定がある程度成り立っていたのは30年以上も昔のことである。それ以降の30年で、プロセサのクロックは1000倍程度速くなったが、メモリのアクセスタイムは10倍程度しか向上しておらず、結果として、現在のマイクロプロセサがDRAMで構成されたメモリをアクセスするには、100サイクル以上を必要とするようになってきている。

メモリのロード、ストアを行う命令の出現頻度は、典型的なプログラムでは30%程度である。
(中略)
実行ユニットは、毎サイクル1命令を実行する能力があるが、メモリアクセスに45倍もの時間がかかり、1秒間に実行できる命令数はクロック周波数の1/46に低下してしまう。

さらに,アラインされていないデータがワード境界どころかキャッシュライン境界をもまたいでいた場合, もう一つのキャッシュライン (普通は32〜256バイトらしい) も読むはめになってしまう!

書き込みの場合はもっとメモリアクセス回数が増える場合もある. 仮に CPU が図2の2ワードに対して単純にXを書き込んだらどうなるだろうか?  YとZが巻き添えを食らって (無効な値に) 書き替えられてしまう. これを防ぐには,書き込み前に図2の2ワードを読み出しておかなければならない. つまり合計4回のメモリアクセスが必要になる.

ただし CPU がワードに書き込みを行う際, ワード内のどのバイトに書き込むかを選択できる回路がついていれば, そのような問題は発生しない. この場合のメモリアクセス回数は読み出しの場合と同じである.

3.2 アラインメントに厳格な CPU の場合 (alignment-strict processors)

x86 以外の多くの CPU (特に RISC) は上記のようには動作せず,エラーとして処理する. つまり「不正アラインメント例外注3」を発生させる. このとき動いている OS が UNIX 系ならば,SIGBUS というシグナルが発生してコアダンプするというオチがつく ( HP-UX での実例 ). 他の OS でも同様だろう.

なぜエラー扱いするのかは CPU の設計者に聞いてみないとわからないが, まず間違いなく CPU の回路を単純にするためだろう. ワード境界をまたぐデータを2回に分けてアクセスするとなると, それだけ制御回路が複雑になる.特に RISC はそもそも次のような設計思想で生まれたものである.

だから1つの基本データ型を2回に分けてアクセスするなどということは, RISC にとっては反革命思想・アラインメント違反罪であり,UNIX 共栄圏では SIGBUS の刑に処せられる.(笑)

(そのうち暇ができれば, アラインメントと CPU 回路の単純化の関係について追記するつもり.)

参考:コンピュータアーキテクチャの話 (安藤壽茂,マイナビニュース)

ARM も RISC なので不正アラインメント例外を発生するが, ARM Linux カーネルは例外ハンドラで x86 と同様の「アラインされていないデータのアクセス」を実現しているらしい. ただしこれは x86 に比べて非常に遅いはず.


注3: 呼び方は CPU によって異なる. ここでいう「例外」は C++ や Java 等のプログラミング言語でいう例外 (try 〜 catch で捕捉できるアレ) ではなく, CPU のハードウェアが発生させる割込みの一種である.
(Visual C/C++ では,__try 〜 __except でハードウェア例外を捕捉できる. コンパイルオプション /EHa を指定すれば,C++ の try 〜 catch でも捕捉可能.)

2007/10/25(木) 追記
CPU によっては,アラインメント例外を発生させる代わりに, アドレスの下位ビットを強制的に0と見なして処理するものもある (↓実例).


2009/05/30(土) 追記
参考:ハードウェアを意識したプログラミングの基礎(後編)
x86,ARM,PowerPC,MicroBlaze でアラインされていないデータにアクセスしてみる実験.
ARM v6 以降は寛容になったらしい.


2010/07/03(土) 追記

逆に x86 がアラインメントに寛容な理由は,最初からそういう仕様で設計されたわけではなく,8bit CPU 時代 (1974年の i8080) からの命令互換性を連綿と維持するためそうせざるを得なかったからにすぎない.
x86 は (アーキテクチャに関してはボロクソに言われながらも) 互換性の維持によって現在の地位を築くことができたのである.
(i8080 → i8086 はアセンブラソースレベル互換,i8086 以後はバイナリ互換.)

■参考


2013/01/02(水) 追記

x86 でも,次の場合は不正アラインメント例外を発生する.

Visual C/C++ では,SSE のためのデータ型 (__m128 など) をサポートしているが, 32bit Windows 用の malloc,new,Win32 アロケータ等は8バイト・アラインメントしか保証していないので, これらを用いて SSE 用のデータ型を含む構造体などを確保すると, アラインメント違反 (一般保護例外 #GP) が発生する場合がある. DirectXMath / XNA Math / D3DX 等,SSE 対応のライブラリを使用する場合は _aligned_malloc() 等を使用する必要がある.

align (Visual C++ Language Reference)

To create an array whose base is properly aligned, use _aligned_malloc, or write your own allocator. Note that normal allocators, such as malloc, C++ operator new, and the Win32 allocators return memory that will most likely not be sufficiently aligned for __declspec(align(#)) structures or arrays of structures.

Windows 7 (SP1) で確認したところ,どうやら DispatchMessage() がこっそり catch して握りつぶしているらしく, 例外が発生してもエラーダイアログが出ないままメッセージループが継続するので気づかない.


4.アラインメントの基礎知識のまとめ

何バイト境界にアラインすればよいか (これをアラインメント注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 の他のシリーズは使ったことがないので未確認.)


●まとめ ●注意

プロセッサによっては, サイズが2の冪乗でない基本データ型 (24ビット整数や48ビット整数,80ビット浮動小数など) を持つものもある. それらのアラインメントについては個別に確認する必要がある. そのようなデータ型をC言語で扱う場合には注意が必要である. (後述の「sizeof とアラインメントの関係」を参照.)


注4: アライメント,境界整列,境界調整[数]などと呼ぶ人もいる.

5.複合データ型 (配列,構造体,共用体) のアラインメント

Warning修正中 (前提条件追記)

5.1 複合データ型全体のアラインメント

ある複合データ型全体のアラインメントを求めるには, 次のように考えてみるとわかりやすいだろう.

複合データが,ある適切な (つまりアラインされた) アドレスに配置されている. このデータのアドレスを少し移動させたい場合, 何バイト単位で移動させることができるか?

複合データ型の変数 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) である.

つまり (バイトマシンでは) 複合データ型のアラインメントは, それに含まれる要素のアラインメントの中で最も大きい (厳しい) ものに等しい. 特に配列の場合はすべての要素が同じ型なので, 配列のアラインメントは要素のアラインメントに等しい.

5.2 複合データ型の各要素のオフセット

複合データ D のバイトアドレスを Addr(D) と書くことにする. D の要素 Di の,D の先頭からのバイトオフセットを Offset(Di) とすると,次式が成立する.

Addr(Di) = Addr(D) + Offset(Di)

D が構造体 (型名を D_t とする) の場合, 上の式をC言語の構文で書けば次のようになる.


assert((char*)&Di == (char*)&D + offsetof(D_t, Di));


 struct D_t
┏━━━┓←┬─ &D (== &D0)
┃D0    ┃  │
┠───┨  │
┃D1    ┃  │offsetof(D_t, Di) … Ai の倍数でなければならない.
┠───┨  │
:      :  │
:      :  ↓
┠───┨←┴─ &Di
┃Di    ┃
┠───┨
:      :
:      :
┠───┨
┃D(n-1)┃
┗━━━┛

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 とアラインメントの関係」を参照).


注5: ここでいう「要素の正味のサイズ」とは,その要素が構造体の場合, 「構造体の最終メンバの後のパディング (tail padding) を含めない」という意味で用いる.したがってそれ以外 (つまりメンバ間) のパディングは「正味のサイズ」に含める.

2017/03/26(日) 追記
Swift の MemoryLayout では正味のサイズ (MemoryLayout<構造体名>.size) と要素間隔 (MemoryLayout<構造体名>.stride) をちゃんと区別しているようだ.(C/C++ もこうだったら,#pragma pack などでアラインメントをないがしろにしなくても (配列要素ではない) 構造体の末尾パディングをなくすことができるのに….)

5.3 sizeof(共用体)

2012/03/03(土) 追記 (「共用体 サイズ」,「共用体 パディング」等で検索して来る人がいるので.)

「sizeof(共用体) はメンバのサイズの最大値」と思っている人が多いと思うが, それだけでは不正確.上記の構造体の場合と同様,sizeof の仕様により共用体全体のアラインメントの倍数に切り上げなければならない.

// 前提:double 型のサイズおよびアラインメントは8バイトとする.
typedef union U {
  char string[17]; // サイズ:17バイト,アラインメント:1バイト.
  double d[2];     // サイズ:16バイト,アラインメント:8バイト.
} U_t;

U_t の正味のサイズはメンバのサイズの最大値 max(17, 16)=17 バイト.
U_t のアラインメントはメンバのアラインメントの最大値 max(1, 8)=8 バイト.
sizeof(U_t) は U_t の正味のサイズを U_t のアラインメントの倍数に切り上げた24バイト
したがって最大メンバ string の後にも7バイトのパディングが入る.
(実例:VC2008 による実験結果)

5.4 複合データ型のアラインメントのまとめ

5.5 構造体のアラインメントに関する誤解

構造体のアラインメントに関して,次のように誤解 (中途半端に理解) している人を見かけたことがある.

誤解1(32ビット CPU では) 構造体は常に4バイト境界に配置される.
     (別表現:構造体のサイズは常に4の倍数である.)

以前から「構造体 4バイト(境界)」とか「構造体 4の倍数」などの検索ワードが多かったので, (32ビット CPU では) 構造体のアラインメントは常に4バイトだと思い込んでいる人が多そうで心配になる. さらに悪いことに,冒頭の図1を見て「そうか,だから4バイトなのか!」と早トチリした人もいそうだ.(苦笑)

実際,(このページを読んでもまだ) この誤解をしている人をネット上で2,3人見かけた.
(ということは全国に少なくとも2〜3億人は隠れてるってことですね,わかります.)

「32 (64) ビット CPU だから構造体のアラインメントは 4 (8) バイト」などと, CPU のビット数やデータバス幅がそのまま構造体のアラインメントになると考えるのはワードマシンの発想であって,バイトマシンのアラインメント規則ではない. ワードマシンでは,ワード単位でしかメモリにアクセスできないのだから, (構造体に限らず) すべての変数のアラインメントがワード単位になるのはむしろ当然. それどころか,ワードマシンはバイトという概念ができる前から存在していた.

昔のコンピュータはワードマシンが多かった (らしい). バイトの概念ができる前は当然すべてワードマシン.
ミニコン (1960年代 〜 1980年代前半) とか, 1980年代の AI (人工知能) 研究ブームでもてはやされた Lisp マシンとか.
(Symbolics の Lisp マシン,使ってみたかった…. ちなみにこれは36ビット (32ビットではない) のワードマシン.)

しかし今時の汎用 CPU のほとんどはバイトマシンであって,ワードマシンはごく一部のメインフレームや特殊な専用プロセッサぐらいしか残っていないだろう. 最近の GPU は (実際のハードウェアはどうか知らないが,Direct3D という API を通して見る限りは) 32ビットワードマシンである. (DSP もワードマシンかもしれないが,使ったことがないのでよく知らない.)

「構造体 32ビット(4バイト)」とか「構造体 64ビット(8バイト)」などで検索して来る人は, 一体どんなワードマシンを使っているんだろう?
(GPU 以外で) 32ビットや64ビットのワードマシンって聞いたことないけど….
そうか,きっと GPU のドライバ開発者か Direct3D プログラマなんだろうね.(笑)

さらに追い討ちをかけると(笑),そもそも「32bit」, 「64bit」と一括りにしている点で最初っからダメ.(64bit の場合について言えば) 自分が使っている処理系が LP64 なのか,LLP64 なのか,それ以外 (ILP64,SILP64 など) なのか,理解してますか?
(もっともこれらは基本データ型のサイズによる分類なので,アラインメントについては結局各処理系のマニュアルで確認する必要がある.)

バイトマシンを使っていて,ここに書いた誤解について身に覚えのある人は, 下記の構造体のサイズとアラインメントがいくつになるか, 自分が使っている処理系で確認して反省してください.(笑)

// ●前提 (char 以外については処理系依存)
// ・char のサイズおよびアラインメントは1バイト (Cの仕様).
// ・short のサイズおよびアラインメントは2バイトとする.
// ・int,long,float のサイズおよびアラインメントは4バイトとする.
// ・long long,double のサイズおよびアラインメントは8バイトとする.
//   x86 用 gcc では -malign-double オプションを指定すること.
//   (デフォルトでは,double および long long のアラインメントが4バイトになっている.
//    これはデータバスが32ビットだった 386,486 との互換性のためだろう.)


// ●構造体/共用体の定義

struct S1 {
  short s;
  unsigned char uc[3];
};

struct S2 {
  char string[10];
  unsigned char uc;
};

struct S3 {
  double d;
  long long ll;
  char c;
};

// sizeof(共用体) で説明した例
typedef union U {
  char string[17];
  double d[2];
} U_t;


// ●型のサイズとアラインメントを出力するマクロ
// sizeof の値を printf("%d") してる人が多いけど,
// size_t 型 (無符号) であって絶対 int 型じゃないよ!
// それに unsigned int と同じとも限らない.
// size_t 型とは,「メモリ上の任意のデータのサイズ」を表すための移植性のある型.
// (MS-DOS 用の Microsoft C コンパイラでは 64KB までしか表せなかったけど….(昔話)))
#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)
// C99 以後
// size_t の printf 書式は %zu.
static const char format[] = "sizeof(%s)=%zu, AlignmentOf(%s)=%zu.\n";

#define PrintSizeAndAlignment(type) \
  printf(format, #type, sizeof(type), #type, AlignmentOf(type))

#elif defined(_MSC_VER)
// Visual C/C++
// size_t の printf 書式は %Iu.
static const char format[] = "sizeof(%s)=%Iu, AlignmentOf(%s)=%Iu.\n";

#define PrintSizeAndAlignment(type) \
  printf(format, #type, sizeof(type), #type, AlignmentOf(type))

#else
// その他の処理系
// size_t の printf 書式が不明なので unsigned long にキャストして出力する.
static const char format[] = "sizeof(%s)=%lu, AlignmentOf(%s)=%lu.\n";

#define PrintSizeAndAlignment(type) \
  printf(format, \
         #type, (unsigned long)sizeof(type), \
         #type, (unsigned long)AlignmentOf(type))
#endif


// ●型のサイズとアラインメントを出力する.
int main(void)
{
  // 基本データ型 (整数型)
  PrintSizeAndAlignment(char);
  PrintSizeAndAlignment(unsigned char);
  PrintSizeAndAlignment(short);
  PrintSizeAndAlignment(unsigned short);
  PrintSizeAndAlignment(int);
  PrintSizeAndAlignment(unsigned int);
  PrintSizeAndAlignment(long);
  PrintSizeAndAlignment(unsigned long);
  PrintSizeAndAlignment(long long);
  PrintSizeAndAlignment(unsigned long long);
  printf("\n");

  // 基本データ型 (浮動小数型)
  PrintSizeAndAlignment(float);
  PrintSizeAndAlignment(double);
  PrintSizeAndAlignment(long double);
  printf("\n");

  // 複合データ型 (ユーザ定義)
  PrintSizeAndAlignment(struct S1);
  PrintSizeAndAlignment(struct S2);
  PrintSizeAndAlignment(struct S3);
  PrintSizeAndAlignment(union U);

  return EXIT_SUCCESS;
}

上記の前提を満たす処理系ならば,結果は次のようになるべき.

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の倍数.)
sizeof(union U)=24, AlignmentOf(union U)=8. (8バイト境界.サイズは8の倍数.)

そうなる理由は「複合データ型のアラインメントのまとめ」を読めばわかるはず.

VC2008 (Target:x86) による実行結果は次のとおり.

sizeof(char)=1, AlignmentOf(char)=1.
sizeof(unsigned char)=1, AlignmentOf(unsigned char)=1.
sizeof(short)=2, AlignmentOf(short)=2.
sizeof(unsigned short)=2, AlignmentOf(unsigned short)=2.
sizeof(int)=4, AlignmentOf(int)=4.
sizeof(unsigned int)=4, AlignmentOf(unsigned int)=4.
sizeof(long)=4, AlignmentOf(long)=4.
sizeof(unsigned long)=4, AlignmentOf(unsigned long)=4.
sizeof(long long)=8, AlignmentOf(long long)=8.
sizeof(unsigned long long)=8, AlignmentOf(unsigned long long)=8.

sizeof(float)=4, AlignmentOf(float)=4.
sizeof(double)=8, AlignmentOf(double)=8.
sizeof(long double)=8, AlignmentOf(long double)=8.

sizeof(struct S1)=6, AlignmentOf(struct S1)=2.
sizeof(struct S2)=11, AlignmentOf(struct S2)=1.
sizeof(struct S3)=24, AlignmentOf(struct S3)=8.
sizeof(union U)=24, AlignmentOf(union U)=8.

この項目は該当する人が多そうなので以前から追加しようと思っていたんだけど, 忙しいので後回しにしていた.
今回追加したきっかけは,同じ誤解をしているコンパイラ(!)を見つけたから.(↓)

[Armadillo:04936] Armadillo-210の構造体について

構造体の配列を共有メモリに展開するプログラムのデバック中に、実サイズより多くの領域を使用している事が解かり
以下のサンプル作りを実行すると、4バイトと表示しました。

struct test{
    char   ChNo[1];
}w_mem;

アラインメントを変更するコンパイル・オプションや #pragma などを指定せずにこうなっているのだとしたら,このコンパイラもまた, 構造体のアラインメントは常に4バイトだと誤解している可能性がある. double 型や long long 型のメンバが含まれている構造体のアラインメントを4バイトとして扱うと, 不正アラインメントエラーが発生する. こういう災厄を招く勘違いコンパイラは,即刻クビにするのが吉.

誤解2構造体のサイズは, メンバの中で最大のサイズのものの整数倍である.
誤解3構造体メンバのオフセットは, そのメンバのサイズの整数倍である.

正解はいずれも,「メンバのサイズ」ではなく「メンバのアラインメント」である. したがってこれらの誤解が「正しい」のは,「サイズ=アラインメント」である場合, つまりメンバが (普通の) 基本データ型の場合に限られる. メンバが複合データ型や,普通でない (サイズが2の冪乗でない, あるいはアラインメントとサイズが一致しない) 基本データ型の場合には通用しない. (アラインメントについて解説している他のサイトでも,上記のような記述を見かけた.)

誤解4アラインメントなんて,普通は (Cコンパイラの) 最適化オプションで調整される
      (ので気にしなくていいと言いたいのかな?)

確かにCコンパイラは,アラインメントの「調整」はしてくれるが, 普通は「最適化」はしない … というより,C言語の仕様上,してはいけない. ここでいう「調整」とは,上で書いたように 「メンバのオフセットをそのメンバのアラインメントの倍数に切り上げる」 ことで,Cコンパイラが普通に行っている.

一方「最適化」となると, 「構造体メンバの順序を入れ替えて,できるだけ隙間ができないようにする」 としか解釈できないが, C言語の仕様上,コンパイラが勝手にメンバの順序を入れ替えることは許されない.

参考:移植性のあるCプログラミング | 構造体メンバの配置

しかし,上の参考ページにも書いてあるが, Cコンパイラの中にはそういう最適化をしてくれる非標準拡張機能を持つものもあるらしい. その機能を ON にすると,Cの規格に違反することになるので普通じゃありません!



■構造体のパディングをできるだけ避ける方法 (2014/05/03(土) 改定)

パディングを避けるために #pragma pack や gcc 拡張の __attribute__((packed)),アラインメントを (小さくする方向に) 変更するコンパイル・オプションなどを使用するのは,CPU のハードウェアの制約に違反して構造体を強引に力で押しつぶす野蛮な方法なので安易に用いるべきではない.CPU の回路について初歩的な知識があれば,すぐに「タダでまともに動くわけがない」とわかるだろう.つまり #pragma pack 等は,文字どおり「構造体のメンバ配置を詰める (pack)」だけで,普通の方法でメンバにアクセスできることを保証していない場合がある (というより,その方が普通?).

  • 実例1:Visual C/C++ の #pragma pack
    If you change the alignment of a structure, it may not use as much space in memory, but you may see a decrease in performance or even get a hardware-generated exception for unaligned access. You can modify this exception behavior by using SetErrorMode.

    訳:構造体のアラインメントを変更した場合,メモリの利用効率は上がるかもしれないが,アラインメントが合っていないメンバにアクセスすると性能低下を引き起こしたり,ハードウェア例外が発生したりする可能性がある.(以下略)

    x86/x64 で (SSE 以外の) 非整列メンバにアクセスする場合は遅くなるだけだが,ARM や Itanium の場合,あるいは x86/x64 でも SSE データにアクセスする場合はハードウェア例外が発生する.

  • 実例2:Sun Studio 12: C ユーザーズガイド,2.8.19 pack
    注 -
    #pragma pack を使用して構造体または共用体のメンバーを自然境界以外の境界で整列させると、 通常、これらのフィールドへのアクセスが発生した場合に SPARC 上でバスエラーが起きます。 このエラーを避けるには (以下略)

    ちなみにこのマニュアル内の「厳密な」の原文は "strict" のはずなので, (アラインメントが)「厳しい (つまり大きい)」と訳すべき.

  • 実例(?) 3
    昨日 (2013/8/29(木)),「alignment trap って何 packed」で検索してきた人がいた. アラインメントがなぜ必要なのかを全く理解していない初心者が, パディングを単に無駄な隙間だと思って安易に #pragma pack などを使ったらアラインメント違反例外が発生したんだろうな….

たとえアラインされていないメンバにアクセスできるとしても,

  • 多かれ少なかれ,速度低下の原因になるのは確実.
  • メンバの読み書きをアトミックに行えなくなる場合がある注6
  • アラインメントに厳格な CPU の場合は (メンバへのアクセスを複数の命令に分けて行うコードを生成する必要があるため) コードサイズが増加する.(参考:ARM の場合の実例)

それどころか,そもそもコンパイラが #pragma pack 相当の機能をサポートしていないかもしれない.

パディングができるだけ入らないようにしたければ, アラインメントを考慮して自分で構造体メンバの順序を決めるのが正統なやり方. #pragma pack などを使わなくても, 構造体メンバを適切な順序で並べればすべてのメンバ間のパディングを必ず0にできる (問題:数学的に証明せよ).ただし最後のメンバの後のパディングは sizeof の仕様のため0にできない場合がある. 順序の決め方は中学1年レベルの数学 (倍数と冪乗) が理解できていればわかるはず.


注6: 更新はもちろんだが, 読むだけ・書くだけの場合も複数回に分けてアクセスするのでアトミック性は全く保証されない. このようなメンバを割込み (シグナル) ハンドラや他のスレッドなどが変更する可能性がある場合に排他制御もせずに読み出すと, 変更前の値でも変更後の値でもなく,両者が混ざったデタラメな値 (例えば下位バイトが変更後の値で上位バイトが変更前の値,など) が得られるおそれがある. (2011/08/07(日) 追記)

実例:LONG 型変数についてアトミック操作を行う Win32 API 関数 InterlockedIncrement() などは,CPU が x86 以外の場合や,たとえ x86 であってもマルチプロセッサの場合, 変数が適切にアラインされていないと予期せぬ結果を引き起こす.(2014/03/22(土) 追記)

InterlockedIncrement function

Remarks

The variable pointed to by the Addend parameter must be aligned on a 32-bit boundary; otherwise, this function will behave unpredictably on multiprocessor x86 systems and any non-x86 systems.

参考



それから「(アラインメントを) 気にしなくていい」という点については「あなたが気にしなくても,(大抵の) CPU は厳しく気にします!
それに,次のような点も気にしないといけない.(とりあえず,今思いついたものだけ.)

最初の2つの場合に上記の「最適化オプション」を使うと, メンバの順序が勝手に変えられてとんでもないことになる. Memory Mapped I/O において, ハードウェアレジスタを構造体として記述している場合も同様.


●余談

構造体メンバの順序を入れ替えて隙間を最小にする最適化アルゴリズムは昔趣味で考えたことがあり, 仕事でも10年ほど前に使った.いずれこのサイトで公開しようと思って, トップページには開設当初から目次に (淡色表示で) 書いてあるが,いまだに公開していない….(^^;)
このページへのアクセスが最近急増したので,そろそろ公開しようかな….

「"構造体" "メンバの順序" "C言語"」で Google 検索してみたところ, ビットフィールドのメンバの順序を入れ替えて最適化を行うというコンパイラを見つけた. ↓これは違反じゃない … よね?

ルネサス テクノロジ - よくあるお問い合わせ検索結果

6.C言語におけるアラインメント

6.1 sizeof とアラインメントの関係 (sizeof がアラインメントの倍数に切り上げって知ってます?)

複合データ型の各要素のオフセット」でも書いたとおり, sizeof(X) の値は,正味のXのサイズ (バイト数) ではなく, それをXのアラインメントの倍数に切り上げた値である.

なぜ sizeof がそういう仕様なのか,わかりますか?

C言語の規格では,n個の要素からなる配列 array[] に関して,

n == (sizeof(array) / sizeof(array[0]))
となることを要求している.この仕様は事実上次のことを意味する.
(割り算 (包含除) を習ったばかりの小学3年生でもわかるはず.)

sizeof(X) が返す値は,X の正味のサイズではなく, X を配列要素にした場合の配置間隔 (stride) でなければならない.

両者が異なるのは,構造体型ではよくあることだが, サイズが2の冪乗でない基本データ型でも起こりうるので注意が必要.

余談:C言語の中・上級者向けのサイトのいくつかで, 著者が明らかに上記のことを理解して書いているとわかる記述を見たことがある. しかしなぜか上記のことを明言しているのを見たことがない.なんでだろう?

6.2 malloc() 等が返すアドレス (下位数ビットは常に0って知ってます?)

Cの標準ライブラリに含まれる malloc() (およびそのファミリーである calloc(),realloc()) が返すアドレスは, すべてのデータ型に適合するようにアラインされている (malloc() の仕様).また,コンパイラが alloca() をサポートしていれば,それも同様のアドレスを返すはずである.

多くの32ビット CPU では,(標準的なCがサポートするデータ型の中で) 最もアラインメントの厳しいデータ型は double および long long 型 (8バイト・アラインメント) である (たぶん). したがって多くの32ビット CPU 用の malloc() は, 8バイトの倍数のアドレスを返す.(実例:VC の malloc)

この話題については,こちらのページもご覧ください.

ポインタの下位数ビットは常に0なので, これらをポインタが参照するデータの型を表すタグ等に転用することができる. これをタグ付きポインタ (tagged pointer) という. 1970〜1980年代の LISP マシンは高速化のためタグ付きポインタをハードウェアで処理していた (タグ・アーキテクチャ tagged architecture).

6.3 アラインメントを取得するマクロと演算子

標準的なC言語では,データ型のサイズを取得する sizeof 演算子はあるものの, アラインメントを取得する演算子はない. そこで私は昔 (1993年),アラインメントを取得するための処理系・OS 独立なマクロ AlignmentOf() を考案し, 今でもメモリ管理によく使っている.

Microsoft C では,同様の機能を持つ演算子 __alignof() という演算子があるが, これは実際のアラインメントと一致しない場合があるようである (現在調査中,バグというわけではなくそういう仕様らしい).

GCC には __alignof__ という演算子があるが,これについては未調査.


7.CPU回路の単純化とアラインメントの関係

(そのうち暇ができれば書くつもり.)


8.補足

8.1 共用体の全メンバのオフセットが0である理由

複合データ型の各要素のオフセット」で 「共用体の場合はメンバのオフセットは常に0」と書いた点について, 「C/C++ の規格で保証されているのか?」という質問を受けたので追記. 私は C/C++ の規格には全然詳しくないし, 規格書も持っていないので, 規格に関する質問には直接回答できないことを最初にお断りしておきます. (詳しい方,教えてください. ところで IS って何?  Defect Report って何?  おいしいの?)

とりあえず手持ちの本などを調べてみると,次のような記述があった.


なお,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 ができなくなってしまう (もっとも,変数のアドレスを別の型のポインタにキャストするという代替手段もあるが).


注意:この内容は, あくまでも CPU がバイトマシンであることが前提 (注1参照).

9.参考図書

省メモリプログラミング

楽天で買う

価格:4,410円(税込、送料別)

省メモリプログラミング―メモリ制限のあるシステムのためのソフトウェアパターン集 (Software patterns series)
ジェイムズ ノーブル チャールズ ウィアー
ピアソンエデュケーション
売り上げランキング: 80302
おすすめ度の平均: 5.0
5 メモリ制限のあるシステム
5 分類が上手い
5 組み込み向けのデザインパターンとしてはまともです。
5 すべての設計者・プログラマに必須

「省メモリ」とあるが,メモリ管理の高速化についても参考になる技法が解説されている. 時々「malloc 高速(化)」などで検索して来る人がいるが, malloc の速度をこれ以上大きく改善する余地はあまりないと思う (あるとしても非常に難しいだろう).その理由は,

それでもなお malloc の高速化それ自体を目指したい人には「(悲愴な顔で) 頑張ってください」としか言えないが, 「アプリケーションのメモリ管理を高速化したいから高速な malloc が欲しい」というのならあまりにも芸がなさすぎる. そういう人はこの本の「第5章 Memory Allocation:メモリ割当て」を読んで反省してください.(笑)

アプリを高速化したいなら,できるだけ malloc/free を呼び出す頻度を減らすこと. そのためには1回の malloc で確保した大きな領域 (メモリプール) に多数のオブジェクトを詰め込む必要がある (これは省メモリにもなる) が, どのオブジェクトを同じ領域に入れるべきかはオブジェクトの寿命 (extent),サイズ, アラインメントなどを考慮して決める必要がある. 特に,寿命を知っているのはアプリケーションだけだ. 目的に合ったメモリプールならば,malloc/free をそのまま使用する場合に比べて数十倍以上速くなることもある.

■参考

メモリー管理の内側  動的アロケーションの選択肢とトレードオフ、そして実装 (原文)

ところで,省メモリが高速化につながる場合も多い.昔からメモリと速度のトレードオフ (高速でメモリを大量に使用するアルゴリズム (例えばテーブル参照) を使うか, それとも低速でメモリを少ししか使用しないアルゴリズムを使うか) がよく問題になるので,省メモリと高速化は両立しないと思い込んでいる人もいるだろう. しかし最近の CPU は命令実行速度に比べてメモリアクセス速度がはるかに遅いので, 無駄なメモリを削減したり,メモリ上のデータ配置を変える (同時期に頻繁に使用するデータをなるべく少数のキャッシュラインに集中させる) と高速化されることも多い. (1970年代以前の CPU は命令実行とメモリアクセスが同期していたので同程度の速度だった.)

さて,ここで問題.次のコードで大きな2次元配列 (例えば画像データ) をコピーする場合,(1) と (2) のどちらが速いか.またその理由を述べよ. (理由を書かなければ0点)

int src[M][N], dest[M][N];
unsigned i, j;

// (1)
for(i = 0;  i < M;  i++)
  for(j = 0;  j < N;  j++)
    dest[i][j] = src[i][j];

// (2)
for(j = 0;  j < N;  j++)
  for(i = 0;  i < M;  i++)
    dest[i][j] = src[i][j];



はじめて読む486

楽天で買う

価格:2,548円(税込、送料別)

はじめて読む486―32ビットコンピュータをやさしく語る
蒲地 輝尚
アスキー
売り上げランキング: 10017
おすすめ度の平均: 5.0
5 intel80286で挫折し、486から勉強し始めました
5 素晴らしい486アーキテクチャ本
5 この本は達者!
5 本当に傑作の一品です。
5 OSを勉強をしている人にもお勧めの一冊



SPARCアーキテクチャ・マニュアル バージョン 8
SPARC International
トッパン
売り上げランキング: 1057910
おすすめ度の平均: 5.0
5 RISCといえばSPARC



【送料無料】ARM組み込みソフトウェア入門

楽天で買う
価格:4,620円(税込、送料別)




【送料無料】ARM Cortex-M3システム開発ガイド

楽天で買う
価格:3,360円(税込、送料別)

CQ出版社の書籍案内 (内容見本PDFあり,立ち読み可)




【送料無料】世界の定番ARMマイコン超入門キットSTM32ディスカバリ〔完全版〕

楽天で買う
価格:4,200円(税込、送料別)




【送料無料】ARM9/11/XScaleハンドブック

楽天で買う
価格:2,520円(税込、送料別)

ARM9/11/XScaleハンドブック (TECH I Processor)

CQ出版
売り上げランキング: 324832



【送料無料】ARMでOS超入門

楽天で買う
価格:2,730円(税込、送料別)

ARMでOS超入門 (ARMマイコン)
桑野 雅彦 岡田 好一 共著
CQ出版
売り上げランキング: 108107



アセンブリ言語の教科書

楽天で買う

価格:3,990円(税込、送料別)

アセンブリ言語の教科書
愛甲 健二
データハウス
売り上げランキング: 72168
おすすめ度の平均: 3.5
3 アセンブリ言語を広範囲に解説した本
1 冗長感は否めない、そして索引も必要
1 ゆとり教育の教科書
5 アセンブリ言語の良書
5 著者の熱意が伝わります

著者サイト



【送料無料】 ハッカーのたのしみ 本物のプログラマはいかにして問題を解くか / ヘンリー・S・...

楽天で買う
価格:3,570円(税込、送料込)

ハッカーのたのしみ―本物のプログラマはいかにして問題を解くか
ジュニア,ヘンリー・S. ウォーレン
エスアイビーアクセス
売り上げランキング: 27265
おすすめ度の平均: 5.0
5 ビットの楽しみ
5 たのしみ? たしなみ?
5 ちゃんと読むと得した気分になれます
5 最後の頑張りに効きます
5 Hackっていうのは、こういうコトさ

主に2進整数やビットパターンのさまざまな演算技法について解説している. 基本的には (特定のプログラミング言語に依存しない) 数学的な解説が中心だが,C言語によるサンプルコードも示している.

アラインメントやオフセットの計算に使える「2の冪乗の倍数への切り上げ/切り下げ」や, メモリブロックの管理に使える「次の2の冪乗への切り上げ/切り下げ」, (ビット/バイト) エンディアン変換や FFT (高速フーリエ変換) で使われるビットリバース (ビット逆順) などを含む「ビットやバイト単位の並べ替え」など.




珠玉のプログラミング

楽天で買う

価格:3,570円(税込、送料別)

珠玉のプログラミング―本質を見抜いたアルゴリズムとデータ構造
ジョン ベントリー
ピアソンエデュケーション
売り上げランキング: 7857
おすすめ度の平均: 4.5
5 アルゴリズムについて勉強したい人には必読の本です
5 楽しく読めるプログラミングの本
5 「プログラミング」と言う作業を見つめなおすのに最適。「設計する」と言う概念がよく分からない初級プログラマにも
5 納得!アルゴリズムは重要
5 プログラマなら読むべき本



プログラミング作法

楽天で買う

価格:2,940円(税込、送料別)

プログラミング作法
プログラミング作法
posted with amazlet at 10.06.20
ブライアン カーニハン ロブ パイク
アスキー
売り上げランキング: 20088
おすすめ度の平均: 4.5
5 良著です。
5 入門書の次の次の次くらいに!
3 繰り返し読む必要あり
5 絶対にお勧めの本です
5 良いプログラマになりたいあなたに



ガベージコレクションのアルゴリズムと実装

楽天で買う

価格:3,360円(税込、送料別)

ガベージコレクションのアルゴリズムと実装
中村 成洋 相川 光
秀和システム
売り上げランキング: 4312
おすすめ度の平均: 3.5
2 擬似コードのバグは見て見ぬふり
5 GCの入門書として今のところ最強!



【送料無料】アルゴリズムクイックリファレンス

楽天で買う

価格:3,360円(税込、送料別)

アルゴリズムクイックリファレンス
George T. Heineman Gary Pollice Stanley Selkow
オライリージャパン
売り上げランキング: 28776



BINARY HACKS

楽天で買う

価格:3,360円(税込、送料別)

Binary Hacks ―ハッカー秘伝のテクニック100選
高林 哲 鵜飼 文敏 佐藤 祐介 浜地 慎一郎 首藤 一幸
オライリー・ジャパン
売り上げランキング: 35295
おすすめ度の平均: 5.0
5 組み込み系の開発者は必携です
5 ハードコア?なソフトウエア
5 大工さんにおける電動工具の紹介本
5 当然教科書ではない。でも、とても参考になります。
5 バイナリアンの基本



【送料無料】リンカ・ローダ実践開発テクニック

楽天で買う

価格:2,940円(税込、送料別)

著者サポートページ




Linkers & loaders

楽天で買う

価格:3,360円(税込、送料別)

Linkers & Loaders
Linkers & Loaders
posted with amazlet at 10.06.17
John R. Levine
オーム社
売り上げランキング: 139529
おすすめ度の平均: 3.0
4 プログラムが実行される仕組みが良く分かる
1 概要が書かれた本
1 ひどい訳
5 dllのしくみがわかる!
5 パッケージソフト開発者の必読書



実践デバッグ技法

楽天で買う

価格:2,940円(税込、送料別)

実践 デバッグ技法 ―GDB、DDD、Eclipseによるデバッギング
Norman Matloff Peter Salzman
オライリージャパン
売り上げランキング: 79214



デバッガの理論と実装 (ASCII SOFTWARE SCIENCE Language)
ジョナサン・B. ローゼンバーグ
アスキー
売り上げランキング: 295384
おすすめ度の平均: 5.0
5 デバッグできないとき
5 普通に読んでいくだけでも面白い
5 デバッカの理論には必読



【送料無料】いかにして問題をとくか第11版

楽天で買う
価格:1,575円(税込、送料別)

いかにして問題をとくか
G. ポリア
丸善
売り上げランキング: 41

10.サイト内関連ページ


11.外部へのリンク


12.更新履歴

このページの主な更新は Blog でお知らせします.

  1. 2007/01/13(土) 作成開始
  2. 2007/01/14(日) 公開
  3. 2007/01/15(月) 過去の記事へのリンクを削除.
  4. 2007/02/03(土)
  5. 2007/03/12(月) 記号の誤記訂正 (GCM → LCM)
  6. 2007/04/13(金) ここに書くまでもない,ほんの些細な追記,リンクを追加.
  7. 2007/09/30(日) 「サイズ=アラインメント」の例外についてちょっと追記, リンクをいくつか追加,その他わずかな変更.
  8. 2008/04/06(日) 質問を受けたので,「共用体の全メンバのオフセットが0である理由」と注1を追加.
  9. 2008/04/08(火)「複合データ型 (配列,構造体,共用体) のアラインメント」修正中 (前提条件追記).
  10. 2009/02/08(日) アラインメントが特定のプログラミング言語の仕様ではない点についてちょっと追記.
  11. 2010/02/04(木) 「構造体のアラインメントに関する誤解」に, 「(32ビット CPU では) 構造体は常に4バイト境界に配置される.
    (別表現:構造体のサイズは常に4の倍数である.)
    」を追記.
  12. 2010/06/12(土) 参考図書追加.
  13. 2010/07/03(土) x86 がアラインメントに寛容な理由と,RISC の設計思想について追記.
  14. 2011/07/24(日)「構造体のパディングをできるだけ避ける方法」を追記.
  15. 2011/08/07(日) 注6を追記.
  16. 2011/09/17(土) 「誤解1」に, 型のサイズとアラインメントを出力するサンプルコードを追加.
  17. 2012/02/23(木)「構造体のパディングをできるだけ避ける方法」に,#pragma pack をちゃんとサポートしていない (?) 処理系の実例を追加.
  18. 2012/03/03(土)「sizeof(共用体)」を追加.
  19. 2012/04/08(日) タグ付きポインタについて追記.
  20. 2012/06/10(日)「構造体のアラインメントに関する誤解1」にワードマシンについて追記.
  21. 2013/01/02(水)「x86 でアラインメント例外が発生する場合について追記.
  22. 2013/01/20(日) 図を AA から GIF 画像に変更.
  23. 2014/03/22(土) 注6に実例 (InterlockedIncrement()) を追記.
  24. 2014/04/16(水)「構造体のパディングをできるだけ避ける方法」を少し改定.
  25. 2014/05/03(土)「構造体のパディングをできるだけ避ける方法」を少し改定.
  26. 2015/01/31(土)「構造体のアラインメントに関する誤解1」に,64bit データモデルの違いについてちょっと追記.
  27. 2016/05/28(土)「アラインメントに厳格な CPU の場合」の記述を整理中.
  28. 2017/03/26(日)「注5」に Swift の MemoryLayout についてちょっと追記.
  29. 2020/01/19(日) リンク修正 (一部)


Copyright © 2006-2020 noocyte, All rights reserved.
E-mail: relipmoced (a) yahoo.co.jp
  (" (a) " を半角のアットマークに書き替えてください.)
リンクはご自由に.
「noocyte のプログラミング研究室」トップページに戻る.