関数ポインタとラベル変数の怪しい関係(あるいはキャストの気持ち悪さについて)
>さて。まず前提知識として関数ポインタとラベル変数について解説しましょう。
>
>関数ポインタはさすがに知っていると思いますが、関数のアドレスを持つポインタですね。
>
>int f()
{
return 1;
}
void *ptr;
LABEL:
ptr = &&LABEL;
printf("test\n");
goto *ptr;
} > >かように無限ループを作成できます。 && は gcc で独自に定義される1項演算子であり(&が二重になっているのではない)、これによってラベルのアドレスが ptr に渡せます。そして void* な変数を参照する goto を書くことができるのでした。 > >さて、両者を混ぜたらどうなるか? > >まず、ラベルに対して関数呼び出ししてみましょう。 > >int main(){
void (*ptr)();
int i = 0;
ptr = (void(*)())&&LABEL;
LABEL:
printf("%d\n", i++);
if (i < 10) {
ptr();
printf("called\n");
}
return 0;
} > >さてこのコードの動作を正しく推定できますか。ちなみに答えは「0〜9までが印字される」です。私も間違えました。まず、 ptr には LABEL の位置が渡されるわけです(キャストで無理矢理あてはめています)。これを ptr() のように指定するのがまず通ってしまう。そして実際に LABEL へ処理が移ります。 x86 で gcc -S するとわかりますが、これには call を使っています。しかし戻り先のアドレスをプッシュするわけではありませんから、 called のパートは最後まで実行されることはありません。 > >なかなか気持ち悪いですね。では次は逆です。 > >int f() {
printf("f\n");
return 1;
}
f
zsh: 48087 bus error ./a.out > >というわけでバスエラーになってしまいました。ちょっと gdb してみましょう。 > >(gdb) run
Starting program: /home/mukai/./a.out
f
{
printf("f\n");
return 1;
}
f
zsh: 48116 segmentation fault ./a.out > >おお、 segmentation fault になってしまいました。意味がわからん。では gdb してみましょう。 > >(gdb) run
Starting program: /home/mukai/a.out
f
printf("f\n");
return 1;
}
{
return 1;
}
int main(){
int (*g)();
g = f;
printf("%d\n", f());
printf("%d\n", g());
printf("%p\n", f);
}
>
void *ptr;
LABEL:
ptr = &&LABEL;
printf("test\n");
goto *ptr;
} > >かように無限ループを作成できます。 && は gcc で独自に定義される1項演算子であり(&が二重になっているのではない)、これによってラベルのアドレスが ptr に渡せます。そして void* な変数を参照する goto を書くことができるのでした。 > >さて、両者を混ぜたらどうなるか? > >まず、ラベルに対して関数呼び出ししてみましょう。 > >int main(){
void (*ptr)();
int i = 0;
ptr = (void(*)())&&LABEL;
LABEL:
printf("%d\n", i++);
if (i < 10) {
ptr();
printf("called\n");
}
return 0;
} > >さてこのコードの動作を正しく推定できますか。ちなみに答えは「0〜9までが印字される」です。私も間違えました。まず、 ptr には LABEL の位置が渡されるわけです(キャストで無理矢理あてはめています)。これを ptr() のように指定するのがまず通ってしまう。そして実際に LABEL へ処理が移ります。 x86 で gcc -S するとわかりますが、これには call を使っています。しかし戻り先のアドレスをプッシュするわけではありませんから、 called のパートは最後まで実行されることはありません。 > >なかなか気持ち悪いですね。では次は逆です。 > >int f() {
printf("f\n");
return 1;
}
int main(){
void *ptr;
ptr = (void*)f;
goto *ptr;
}
>
f
zsh: 48087 bus error ./a.out > >というわけでバスエラーになってしまいました。ちょっと gdb してみましょう。 > >(gdb) run
Starting program: /home/mukai/./a.out
f
Program received signal SIGBUS, Bus error.
0xbfbff6d1 in ?? ()
(gdb) bt
#0 0xbfbff6d1 in ?? ()
#1 0x8048406 in _start ()
>
{
printf("f\n");
return 1;
}
int f2(){
void *ptr;
ptr = (void*)f;
goto *ptr;
return 2;
}
int main(){
f2();
}
>
f
zsh: 48116 segmentation fault ./a.out > >おお、 segmentation fault になってしまいました。意味がわからん。では gdb してみましょう。 > >(gdb) run
Starting program: /home/mukai/a.out
f
Program received signal SIGSEGV, Segmentation fault.
0x28069080 in ?? ()
(gdb) bt
#0 0x28069080 in ?? ()
#1 0x80484e3 in main () at test.c:14
>
printf("f\n");
return 1;
}
int main(){
f(10);
}
>