Menu

C言語のprintf:書式文字列、変換指定子、浮動小数点の表示

C言語で printf が実際にどう動くのか - 書式文字列のモデル、毎日使う変換指定子、桁揃えのための幅と精度、浮動小数点をきれいに表示する方法、戻り値、そして printf(user_input) がセキュリティホールである理由。

このページのコードはエディタで実行できます - 編集してすぐに結果を確認できます。

printf はすべてのCプログラマが最初に覚える関数であり、その大半がマニュアルを最後まで読まないままの関数でもあります。10分をかける価値はあります。その書式文字列はそれ自体が小さな言語であり、変換指定子を間違えるのは打ち間違いではなく未定義動作だからです。

printf<stdio.h> にあるので、これを使うプログラムはすべて #include <stdio.h> で始まります。

書式文字列のモデル

printf は書式文字列を1文字ずつたどります。% でないものはすべて、書かれたとおりに表示されます。%変換指定の始まりで、次の引数を1つ食べて、要求された形で表示します。

引数は厳密に位置で変換指定子に対応づけられます。最初の %count を取り、2つ目が name を取る、という具合です。printf には、約束したものを実際に渡したかどうかを確かめる手段がありません - 型が見えないのです - ので、書式文字列はあなたが守るべき契約です。だからこそ -Wall を有効にする価値があります。GCC はコンパイル時にリテラルの書式文字列を読み、引数が一致しないときに警告します。

改行は自動では入りません。printf("Hello") はカーソルを o の直後に残すので、ほとんどすべての書式文字列が \n で終わるのです。

毎日使う変換指定子

5つでたいていのコードは足ります。

変換指定子表示するもの呼び出しの例出力
%d符号付きの intprintf("%d", -42)-42
%fdoubleprintf("%f", 2.5)2.500000
%s文字列(char *printf("%s", "hi")hi
%c1文字printf("%c", 'A')A
%pポインタのアドレスprintf("%p", (void*)&x)0x7ffd...

この断片には2つのルールが隠れています。リテラルの %%% と書きます - 書式文字列の末尾にぽつんと置かれた % は未定義動作です。そして %pvoid * を要求するので、ポインタはキャストしてください。int * をそのまま渡すのは、実際にはうまく表示されるとしても厳密には未定義です。

完全な表 - すべての変換指定子、すべての長さ修飾子、scanf 用の列 - は書式指定子のページにあります。このページは、手が自然に伸びるものだけを扱います。

幅、精度、桁揃え

% と末尾の文字の間には、フラグ・幅・精度を置けます。ループを表に変えるのはこれです。

は最小値であって、最大値では決してありません。%3d に値 123456 を渡せば6桁すべてが表示されます。精度は型ごとに意味が違います - %f では小数点以下の桁数、%s では最大文字数、%d では最小桁数です。

どちらも * を使って実行時に与えられます。*int の引数を1つ読みます。

width を変えれば表全体が組み直されます。これが、桁の揃った列をC言語で表示する標準的な方法です - 文字列を詰めるヘルパーは要りません。

浮動小数点をきれいに表示する

精度を付けない %f は常に小数点以下6桁を表示しますが、それが望みであることはめったにありません。

選択肢は3つ、それぞれに目安があります。

  • %.Nf はお金、計測値、小数桁数が自然に決まるもの向け。通貨には %.2f が欲しいはずです。
  • %e は指数表記用で、常に d.dddddde±dd の形です。
  • %g は桁の大きさが事前に分からないとき。%f%e のうち短いほうを選び、末尾のゼロを落とします - 見たことのない値をログに出すときの、いちばん正気なデフォルトです。

はっきり言っておく価値のある罠が1つ。printf に %lf は存在しませんfloat の引数は printf が見る前に double へ昇格されるので %f が両方を扱い、%lf が意味を持つのは書き込み先のサイズが問題になる scanf だけです。(C99 は printf での %lf を同義語として受け付けますが、それを書く癖が scanf でも %f と書く癖につながり、そちらは本当に壊れます。)

ほかの幅の整数

%dint 用です。それより大きい型や符号なしの型には長さ修飾子が必要で、これを取り違えるのは丸め誤差ではなく未定義動作です。

size_t 用の %zu は、いちばん多く間違えられるものです - sizeof が返すのは int ではなく size_t なので、printf("%d", sizeof x) は32ビット環境ではたまたま動き、64ビット環境でおかしくなるバグです。

戻り値

printf は書き出した文字数を返し、失敗時には負の数を返します。たいていは無視されますし、ほとんどの場合それで構いません - ただし出力の幅を知りたいときには本当に役立ちます。

親戚の sprintfsnprintf は画面ではなくバッファに書き込み、生成した長さを返します。バッファサイズを受け取るため溢れることのない snprintf を常に選びましょう。

snprintf は完全な文字列が必要としたはずの長さを返すので、戻り値がバッファサイズ以上なら出力が切り詰められたと分かります。

書式文字列脆弱性

これは printf にある唯一のセキュリティ問題であり、深刻です。自分で書いていないデータを書式文字列として渡してはいけません。

/* 危険 - 決してこうしないこと */
printf(user_input);

/* 正しい書き方 */
printf("%s", user_input);

user_input%s が含まれていると、printf は渡されてもいない引数を読み、そこで見つけたポインタらしきゴミをたどり、たいていクラッシュします。%x を繰り返せばスタックの中身が画面に吐き出され、そこにパスワードや鍵が含まれるかもしれません。そして文字数を引数へ書き込む %n は、歴史的にこれを任意のメモリ破壊とリモートコード実行に変えてきました。

ルールは絶対で、コストはゼロです。**書式文字列は常に、あなたが書いたリテラルである。**可変なものはすべて引数として渡します。GCC の -Wformat-security がこの間違いを指摘し、これは -Wall -Wextra に含まれています。

バッファリング:出力が遅れて現れることがある理由

printf はすぐに端末へ書き込むわけではありません。出力はバッファリングされ、バッファが一杯になったとき、端末で改行が現れたとき、あるいはプログラムが正常終了したときにフラッシュされます。帰結が2つあります。

  • クラッシュしたプログラムは「すでに表示された」出力を失うことがあります。printf でデバッグしていて最後のメッセージが現れないなら、クラッシュはその行ので起きたのであって、前ではありません。fflush(stdout) を足すか、バッファリングされない stderr にデバッグ出力を送りましょう。
  • エラーの表示には fprintf(stderr, ...) が正解です。バッファリングされず、通常の出力とは独立にユーザーがリダイレクトできる別のストリームへ流れます。

よくある質問

C言語の printf はどう動きますか?

printf は書式文字列を1文字ずつ走査します。普通の文字はそのまま表示され、% のたびに変換指定が始まって、後続の引数を1つ消費し要求された形で表示します。printf("%d apples\n", 5)5 apples と改行を表示します。

C言語で小数点以下2桁の浮動小数点を表示するには?

精度を使います:printf("%.2f\n", 3.14159)3.14 を表示します。ドットの後の数字が、小数点以下に続く桁数です。精度を付けない %f は常に6桁表示するので、書式を指定しない浮動小数点はあれほど騒がしく見えるのです。

なぜ printf がゴミを表示したりクラッシュしたりするのですか?

ほぼ必ず、変換指定子と引数の不一致です。printf("%d", 3.5)printf("%s", 42) は未定義動作です - printf は実際の型を見ることができず、書式文字列を信じるだけです。-Wall を付けてコンパイルしてください。GCC が書式と引数を突き合わせて検査してくれます。

なぜ printf(user_input) は危険なのですか?

ユーザーのテキストに % の並びが含まれていると、printf はそれに従って、渡されてもいない引数を読み取ります - スタックのメモリが漏れ、%n があればメモリへの書き込みまで起こります。これが古典的な書式文字列脆弱性です。必ず printf("%s", user_input) と書きましょう。

Coddy programming languages illustration

Coddyでコードを学ぼう

始める