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 & Loaders の 10章に解説があります。
環境は Debian GNU/Linux sarge + GCC 3.3.5 です。