gcc4.7.3におけるvoid型の実装
void型について実験。gccのバージョンは、以下です。
gcc バージョン 4.7.3 (Ubuntu/Linaro 4.7.3-1ubuntu1)
われわれはvoidを当たり前のように使っているけど、そもそもvoidとは何だろ?実装レベルで、どのようにコンパイラに解釈されているのか、また逆アセンブルしてみました。
「Cリファレンスマニュアル*1」によると、void型の使い方として、以下のケースが挙げられています。
・関数の返却値の型として、その関数は値を返さないことを表す. ・値を捨てることを明示したい場合にキャスト式の中で. ・「万能」データポインタvoid *型を作るために. ・関数が引数を持たないことを示すために、仮引数の並びの代わりに.
上記と矛盾するケース、つまり「void型の使用が許されないケース」において、実験的にvoid型を使用してみたところ、以下のコンパイルエラーが発生しました。メモとして記しておきます。
ケース | ソースの例 | 使用した場合の物理的挙動 |
---|---|---|
void型変数を宣言 | void a; | コンパイルエラー「‘n’ の記憶域サイズが不明です」 |
復帰値void型の関数の戻り値を呼出元で参照 | (省略) | コンパイルエラー「void の値が本来の意味通りに無視されませんでした」 |
復帰値void型の関数内でreturn 0 | (省略) | 同上 |
void型ポインタは何バイトで実装されているか
void型ポインタの領域を確保する際に、何バイト確保されるか?という実験。1 void func() 2 { 3 char a = 0x12; 4 void *n = (void *)0x7F; 5 char b = 0x34; 6 int c = 0xFFFFFFFF; 7 8 return; 9 }
00000000 <func>: 0: 55 push ebp 1: 89 e5 mov ebp,esp 3: 83 ec 10 sub esp,0x10 6: c6 45 f6 12 mov BYTE PTR [ebp-0xa],0x12 a: c7 45 f8 7f 00 00 00 mov DWORD PTR [ebp-0x8],0x7f 11: c6 45 f7 34 mov BYTE PTR [ebp-0x9],0x34 15: c7 45 fc ff ff ff ff mov DWORD PTR [ebp-0x4],0xffffffff 1c: 90 nop 1d: c9 leave 1e: c3 ret
局所変数に値を格納した後のスタックは、以下のようになっています。「7F」の格納先には、4バイト分の領域を確保しています。
ebp | offset | Stack | esp |
---|---|---|---|
-0x10 | (空き) | ← | |
-0x0C | __ __ 12 34 | ||
-0x08 | ★7F __ __ __ | ||
-0x04 | FF FF FF FF | ||
→ | 0x00 | Based EBP |
まあ、参照先のデータ型によってポインタ型のサイズが異なる処理系はまれでしょうから、あまり意味の無い実験でした。
void型ポインタをポインタ演算
int型(を指す)ポインタ変数を+1すると、ポインタ値としては4加算されますが、ではvoid型ポインタ変数を+1すると、加算される値はいくつでしょうか。そこで、こんなコードを入れてみると…printf("%p -> %p\n", n, n+1);
0x7f -> 0x80
結果は「+1される」でした。もちろん、処理系定義でしょうが。
復帰値がvoid型である関数における、eaxレジスタの扱い
leave/ret命令の前に、eaxレジスタに何かをセットするコードは存在しませんでした。つまり、呼出元で関数の復帰値を破棄する(void)のキャストはNOPであり、単に可読性のためでしょう。ちなみに、returnある/なしのコードをそれぞれコンパイルしたものを比較してみると、「return」ありのオブジェクトファイルにおいては、leave/retの直前にNOP命令が配置されていました。今回の実験は、だから?って感じで、あまりパッとしなかったなあ。