2005年10月17日

GCC の最適化で printf が puts になる場合

GCC の最適化により printf の呼び出しが puts に置き換わることがある、と 先日、教えてもらったので試してみました。

 

次のような hello.c をまず最適化なしでコンパイルします。

#include <stdio.h>
int main ()
{
    printf("hello, world\n");
    return 0;
}
% gcc -o hello hello.c

そして、実行可能ファイル hello を readelf コマンドで覗いてみると、 printf という文字列が見つかります。 puts は見あたりません。

% readelf -a hello | egrep 'printf|puts'
080495d0  00000207 R_386_JUMP_SLOT   00000000   printf
     2: 00000000    57 FUNC    GLOBAL DEFAULT  UND printf@GLIBC_2.0 (2)
    96: 00000000    57 FUNC    GLOBAL DEFAULT  UND printf@@GLIBC_2.0

次に、同じ hello.c を gcc の最適化オプション -O2 つきでコンパイルして、同様に readelf で実行可能ファイル中身を覗いてみます。すると、今度は printf が消えて puts だけが見つかります。

% readelf -a hello2 | egrep 'printf|puts'
080495cc  00000107 R_386_JUMP_SLOT   00000000   puts
     1: 00000000   383 FUNC    GLOBAL DEFAULT  UND puts@GLIBC_2.0 (2)
    87: 00000000   383 FUNC    GLOBAL DEFAULT  UND puts@@GLIBC_2.0

この最適化は文字列の末尾が \n で終わり、%d などのフォーマット指定がないときなどに行われるようです。鵜飼さんによると GCC の gcc/src/builtins.c の expand_builtin_printf に最適化の条件が記述されているとのこと。

ところで、上のコンパイルの方法では printf, puts ともに共有ライブラリ libc.so (/lib/libc.so.6 など) のものが使われます。「080495cc 00000107 R_386_JUMP_SLOT 00000000 puts」の部分は、動的共有ライブラリ内の puts を呼び出す際に使われる情報の一部を表しています。この辺りの仕組みは Linkers & Loaders10章に解説があります。

環境は Debian GNU/Linux sarge + GCC 3.3.5 です。