パスワードを忘れた? アカウント作成
1506839 journal
プログラミング

Yoh2の日記: 無限再帰を一瞬で 5

日記 by Yoh2

clang-3.0にて。

// hoge.c
#include <stdio.h>
 
void func(void)
{
        func();
}
 
int main(void)
{
        func();
        puts("The code clang generates is so fast.");
        return 0;
}

ビルド & 実行。

$ clang -O2 hoge.c
$ ./a.out
The code clang generates is so fast.

わー。すごくはやーい。むげんさいきよびだしをいっしゅんでぬけたぞー……?

  • 普通はスタックオーバーフローで Segmentation fault になるよね…と思ったところで、関数の無限再帰呼出によるスタックオーバーフロー時の動作って規格的にはどうなっているのかが気になりました。

    未定義とかなら、結果として puts(...); による出力が行われるのもアリかもしれないと思ったので。

    で、規格書はまだ見てないんですが、『スタックオーバーフローの仕様 - まめめも [hatena.ne.jp]』・『スタックオーバーフローの仕様の続き - まめめも [hatena.ne.jp]』あたりによると、Cの規格の範囲では無限に関数を呼び出し続けなければならないと考えていいのかな?

    それと、アセンブリ出力も見てみたいなぁ、とリクエスト。

    --
    The Only Nerd Thing To Do
    -たったひとつのアレゲなやりかた-
    • それと、アセンブリ出力も見てみたいなぁ、とリクエスト。

      ほい。clang-3.0で -O2 を付けた場合。アーキテクチャはamd64ね。

      func:                                   # @func
      .Ltmp0:
          .cfi_startproc
      # BB#0:
          ret
      .Ltmp1:
          .size    func, .Ltmp1-func
      .Ltmp2:
          .cfi_endproc
      .Leh_func_end0:

      要するに、関数に入って即座にret。
      実際の所は、main()の中で、func()がインライン展開されちゃってfunc()が呼ばれることはありませんが。

      再帰呼び出しの最適化がどうかかるか調べていたらおかしなアセンブリコードが見えたのでおや? と思ったのが発端。

      おまけ: gcc-4.5.3で -O2 を付けた場合:

      func:
      .LFB22:
          .cfi_startproc
          .p2align 4,,10
          .p2align 3
      .L2:
          jmp    .L2
          .cfi_endproc

      単なる無限ループに。clangでもこうなることを期待してたんだけどなぁ。

      --
      巧妙に潜伏したバグは心霊現象と区別が付かない。
  • FreeBSD 上の gcc 4.2.1 20070719 (x86) でも、 -O1・-O2 で clang 3.0 と同じようなことが起きました。

    こちらのアセンブリ出力は以下の通りで、即座に ret ではなく余計な pushl・movl・popl が入っていました。

    func:
            pushl   %ebp
            movl    %esp, %ebp
            popl    %ebp
            ret

    ちなみに試したのはさくらのレンタルサーバ上で。

    --
    The Only Nerd Thing To Do
    -たったひとつのアレゲなやりかた-
typodupeerror

普通のやつらの下を行け -- バッドノウハウ専門家

読み込み中...