C言語には、文字表示のためにprintf関数が有ります。
printfは%指定で変数を書式指定付きで埋め込める高機能な関数ですが、高機能な分、処理が遅いというデメリットもあり、書籍によっては “固定の文字を出すだけであればprintf()の替わりにputs()を使用すべし” というHowToが書かれてたりもします。
と今まで思ってましたが…
gccの出力を見ると、時によっては”printfをputs呼び出しに自動で変換してくれる場合がある”ようです。
雰囲気では、printf( “hello\n” );のように、”変数を含まず、かつ、改行で終わる場合”が対象っぽいのですが、どのような場合でprintf呼び出しの最適化が行われるのか、確認してみました。
というわけで、確認用のプログラムです。
面倒なので、確認結果(最適化後の置き換え関数)も先にコメントで書いておきました。
#include<stdio.h>
int main()
{
printf( "test0\n" ); // puts
printf( "test1" ); // printf
printf( "test2:%s\n", "test2" ); // printf
printf( "%s", "test3\n" ); // puts
printf( "%s\n", "test4" ); // puts
printf( "test5%s\n", "test5" ); // puts
printf( "A" ); // putchar
printf( "%c", '6' ); // putchar
printf( "%c\n", '7' ); // printf
printf( "%%" ); // printf
} |
説明の順番が前後しますが、上記コードのアセンブラ出力です。
$ gcc -S opt.c
$ cat opt.s
.file "opt.c"
.section .rodata
.LC0:
.string "test0"
.LC1:
.string "test1"
.LC2:
.string "test2:%s\n"
.LC3:
.string "test2"
.LC4:
.string "test3"
.LC5:
.string "test4"
.LC6:
.string "test5%s\n"
.LC7:
.string "test5"
.LC8:
.string "%c\n"
.LC9:
.string "%%"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
andl $-16, %esp
subl $16, %esp
movl $.LC0, (%esp)
call puts
movl $.LC1, %eax
movl %eax, (%esp)
call printf
movl $.LC2, %eax
movl $.LC3, 4(%esp)
movl %eax, (%esp)
call printf
movl $.LC4, (%esp)
call puts
movl $.LC5, (%esp)
call puts
movl $.LC6, %eax
movl $.LC7, 4(%esp)
movl %eax, (%esp)
call printf
movl $65, (%esp)
call putchar
movl $54, (%esp)
call putchar
movl $.LC8, %eax
movl $55, 4(%esp)
movl %eax, (%esp)
call printf
movl $.LC9, %eax
movl %eax, (%esp)
call printf
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Ubuntu/Linaro 4.6.1-9ubuntu3) 4.6.1"
.section .note.GNU-stack,"",@progbits |
この呼び出し関数変更による最適化は、1パターンを除いて、gccに-O0オプション(-O0:最適化しない)をつけた場合でも行われました。
結果を見ると、下記のパターンの時にputs関数に置き換わりました。
printf( "test0\n" ); // puts printf( "%s", "test3\n" ); // puts printf( "%s\n", "test4" ); // puts printf( "test5%s\n", "test5" ); // puts (-O0だとprintfになる) |
仮に%s等の置換部があったとしても、コンパイル時に表示すべき文字列が確定してればputsに置き換えてくれるようです。
4つ目のパターンだけは、なぜか-O0と最適化オプション無しで出力が異なっていました。
以下のパターンでは最適化は行われません。
printf( "test1" ); |
これは、出力文字の終端が改行文字ではないパターンは、putsに置き換えできないからです。
putsは引数で与えられた文字列に改行文字を加えて、標準出力に印字します。
また、出力結果が1文字で、かつ末尾に改行が無い場合はputcharに置き換わります。
(例には有りませんが、”\n”だけの出力もputcharになりました)
printf( "A" ); // putchar printf( "%c", '6' ); // putchar |
また、以外な事に”%%\n”や”%%”だと、printfそのままです。
printf( "%%\n" ); // printf |
これは、putchar()に代替できそうな気もしますが、ダメでした
gccのソースを見たところ以下のコメントがあったので、現状では対応できてないみたいです。
/* We can’t handle anything else with % args or %% … yet. */
なお、このprintf->putchar/putsへの最適化ですが、gccのソースコード的には、c-common.cにあるc_expand_builtin_printf()という関数で行われているようです。
ちょっと長いですが、c_expand_builtin_printf関数のソースをまるっと引用しますので、興味がある方は見てみるのも良いかと思います。
(当然ながら,c_expand_builtin_printf関数に対するのライセンスはGPLです)
細かい事はさておき、コメントを見ながら流れを追うとある程度の雰囲気はつかめるかと思います。
static rtx c_expand_builtin_printf (arglist, target, tmode, modifier, ignore, unlocked) tree arglist; rtx target; enum machine_mode tmode; enum expand_modifier modifier; int ignore; int unlocked; { tree fn_putchar = unlocked ? built_in_decls[BUILT_IN_PUTCHAR_UNLOCKED] : built_in_decls[BUILT_IN_PUTCHAR]; tree fn_puts = unlocked ? built_in_decls[BUILT_IN_PUTS_UNLOCKED] : built_in_decls[BUILT_IN_PUTS]; tree fn, format_arg, stripped_string; /* If the return value is used, or the replacement _DECL isn't initialized, don't do the transformation. */ if (!ignore || !fn_putchar || !fn_puts) return 0; /* Verify the required arguments in the original call. */ if (arglist == 0 || (TREE_CODE (TREE_TYPE (TREE_VALUE (arglist))) != POINTER_TYPE)) return 0; /* Check the specifier vs. the parameters. */ if (!is_valid_printf_arglist (arglist)) return 0; format_arg = TREE_VALUE (arglist); stripped_string = format_arg; STRIP_NOPS (stripped_string); if (stripped_string && TREE_CODE (stripped_string) == ADDR_EXPR) stripped_string = TREE_OPERAND (stripped_string, 0); /* If the format specifier isn't a STRING_CST, punt. */ if (TREE_CODE (stripped_string) != STRING_CST) return 0; /* OK! We can attempt optimization. */ /* If the format specifier was "%s\n", call __builtin_puts(arg2). */ if (strcmp (TREE_STRING_POINTER (stripped_string), "%s\n") == 0) { arglist = TREE_CHAIN (arglist); fn = fn_puts; } /* If the format specifier was "%c", call __builtin_putchar (arg2). */ else if (strcmp (TREE_STRING_POINTER (stripped_string), "%c") == 0) { arglist = TREE_CHAIN (arglist); fn = fn_putchar; } else { /* We can't handle anything else with % args or %% ... yet. */ if (strchr (TREE_STRING_POINTER (stripped_string), '%')) return 0; /* If the resulting constant string has a length of 1, call putchar. Note, TREE_STRING_LENGTH includes the terminating NULL in its count. */ if (TREE_STRING_LENGTH (stripped_string) == 2) { /* Given printf("c"), (where c is any one character,) convert "c"[0] to an int and pass that to the replacement function. */ arglist = build_int_2 (TREE_STRING_POINTER (stripped_string)[0], 0); arglist = build_tree_list (NULL_TREE, arglist); fn = fn_putchar; } /* If the resulting constant was "string\n", call __builtin_puts("string"). Ensure "string" has at least one character besides the trailing \n. Note, TREE_STRING_LENGTH includes the terminating NULL in its count. */ else if (TREE_STRING_LENGTH (stripped_string) > 2 && TREE_STRING_POINTER (stripped_string) [TREE_STRING_LENGTH (stripped_string) - 2] == '\n') { /* Create a NULL-terminated string that's one char shorter than the original, stripping off the trailing '\n'. */ const int newlen = TREE_STRING_LENGTH (stripped_string) - 1; char *newstr = (char *) alloca (newlen); memcpy (newstr, TREE_STRING_POINTER (stripped_string), newlen - 1); newstr[newlen - 1] = 0; arglist = fix_string_type (build_string (newlen, newstr)); arglist = build_tree_list (NULL_TREE, arglist); fn = fn_puts; } else /* We'd like to arrange to call fputs(string) here, but we need stdout and don't have a way to get it ... yet. */ return 0; } return expand_expr (build_function_call (fn, arglist), (ignore ? const0_rtx : target), tmode, modifier); } |
関連記事
コメントを残す