2009年07月28日
C言語での変数の実体の割り付けられかた
定義宣言と参照宣言の話に関連して、実際にこのコードをコンパイルして、変数がどう割り当てられるかをみてみましょう。
v.c
int x; /* (1) */ int y = 0; /* (2) */ int z = 1; /* (3) */ extern int x; /* (4) */
このファイルをコンパイルしてアセンブラ出力の結果を見てみましょう。
gcc -S v.c
(4)は宣言なので実際のコードには現れません。
(1)は以下のようになります。
.comm x,4,4
.comm セクションに割りつけられました。これはリンカが最後に.commセクションを集めて重複するシンボルは統合して.bssセクションに変換してくれます。
(2)は以下のようになります。
.global y .bss .align 2 .type y, %object .size y, 4 y: .space 4
0で初期化しているときには、.bssセクションに割りつけられます。リンクする時にシンボルが重複しているとエラーになります。
(3)は以下のようになります。
.global z .data .align 2 .type z, %object .size z, 4 z: .word 1
0以外で初期化しているときには、.dataセクションに割りつけられます。リンクする時にシンボルが重複しているとエラーになります。
プログラムをROM化するときには特にどの変数がどのセクションに割りつけられたかに気を配る必要があります。.dataセクションは初期値をROM上に置いて、起動時にそれをRAM上にコピーします。 .bssセクションは起動時にRAMのその範囲をゼロクリアします。つまり.bssに割り当てられた変数はRAM上のそのサイズを占有するだけですが、.dataセクションに割り当られた変数はRAMだけでなく、その初期値の保持のためにROMも占有します。
今のgccでは上記のように0で初期化している場合は.bssセクションに割りつけられるようになっていますが、昔のCコンパイラでは初期化している場合は全て.dataセクションに割りつけられていました。そのため0で初期化する場合でも.dataセクションに0の初期値が割り当てられてしまうため、その分だけROM領域を無駄に消費していました。特に配列の場合は気をつける必要がありました。
というわけで、グローバル変数の配列を
int a[100] = {0};
と書いていると、先輩から以下のように書き直すように指導を受けたものです。
int a[100]; /* = {0}; */
20年前の話ですが。(笑)
今のgccならば上のように書いてもROM領域に400バイトのゼロがとられることはありません。