C言語で画面に値を表示する最も基本的な方法がprintf
です。
数字や文字列を整った形で出力でき、学習の初期段階からデバッグの現場まで幅広く使われます。
本記事では、printf
の基本的な使い方から書式指定子、幅や精度、フラグの指定、さらにエラーの見つけ方まで、入門者向けに段階を踏んで丁寧に解説します。
C言語のprintf()の基本と画面表示
printf()とは何か
printf
は、指定した書式に従って値を文字列に変換し、標準出力に書き出す関数です。
C標準ライブラリのstdio.h
で宣言されており、書式文字列と値の組を引数に取ります。
戻り値は出力した文字数で、出力に失敗した場合は負の値となります。
もっとも簡単な例
#include <stdio.h>
int main(void) {
// 文字列をそのまま出力します
int n = printf("Hello, printf!\n");
// 出力した文字数を確認します(改行も1文字に数えられます)
printf("printed = %d chars\n", n);
return 0;
}
Hello, printf!
printed = 15 chars
stdio.hと標準出力(stdout)
stdio.h
には標準入出力に関する関数群が宣言されています。
printf
は標準出力(識別子はstdout
)に書き出します。
通常、標準出力はコンソール画面に接続されていますが、リダイレクトされるとファイルやパイプにつながることもあります。
デバッグメッセージには標準エラー出力(stderr
)を使うと、標準出力のリダイレクトに影響されずに表示できます。
stdout
: 通常の結果を表示stderr
: エラーやデバッグの表示に適切
基本の書き方(サンプル)
書式文字列にプレースホルダを書き、後ろに続く引数で値を渡します。
プレースホルダを「書式指定子」と呼びます。
#include <stdio.h>
int main(void) {
int age = 20;
double height = 172.5;
const char *name = "Taro";
// %dは10進整数、%fは浮動小数、%sは文字列
printf("Name: %s\n", name);
printf("Age: %d years\n", age);
// 小数点以下2桁で表示したいときは%.2fのように精度を指定します
printf("Height: %.2f cm\n", height);
return 0;
}
Name: Taro
Age: 20 years
Height: 172.50 cm
改行(\n)とバッファリング
標準出力は多くの環境で行バッファリングされます。
これは、改行文字(\n
)が出るまで出力を内部バッファに溜め、まとめて表示する仕組みです。
そのため改行を忘れると、すぐに画面に出ないことがあります。
確実に出したいときは改行を入れるか、fflush(stdout)
で手動フラッシュします。
#include <stdio.h>
int main(void) {
printf("Processing..."); // 改行なし(バッファに溜まる可能性)
fflush(stdout); // すぐに画面へ出す
// ここで長い処理があると仮定
printf("\rProcessing...done\n"); // 行頭に戻って上書きし、改行で確定
return 0;
}
Processing...done
※ Processing...も出力されるが、一瞬で上書きされて見えなくなる
リダイレクト先がファイルやパイプの場合、バッファリングの挙動が異なり、改行があってもすぐに書かれないことがあります。
頻繁に結果を確認したいときはfflush(stdout)
の使用が有効です。
書式指定子の使い方
書式指定子は、出力する値の種類をprintf
に教えるための印です。
主なものは次のとおりです。
表は主な指定子の概要です。
詳細は後続の節で具体例とともに説明します。
分類 | 指定子 | 意味の概要 |
---|---|---|
整数 | %d, %i | 符号付き10進整数(int) |
整数 | %u | 符号なし10進整数(unsigned int) |
整数 | %x, %X | 16進整数(小文字/大文字) |
浮動小数 | %f | 固定小数点表記 |
浮動小数 | %e, %E | 指数表記 |
浮動小数 | %g, %G | 短い方(%fか%e)を自動選択 |
文字/文字列 | %c | 1文字 |
文字/文字列 | %s | 文字列(ヌル終端) |
ポインタ | %p | ポインタのアドレス |
その他 | %% | パーセント記号自身 |
整数(%d %u %x)
%d
は符号付き、%u
は符号なし、%x
は16進数の出力です。
16進で接頭辞を付けたいときはフラグ#
を併用します。
#include <stdio.h>
int main(void) {
int a = -42;
unsigned int b = 42u;
unsigned int mask = 0x5A3u;
printf("a as signed: %d\n", a);
printf("b as unsigned: %u\n", b);
printf("mask in hex: %x\n", mask);
// 接頭辞0xを付けたい場合は#フラグを使います
printf("mask in hex with prefix: %#x\n", mask);
return 0;
}
a as signed: -42
b as unsigned: 42
mask in hex: 5a3
mask in hex with prefix: 0x5a3
異なる型で出力すると未定義動作になるため、unsigned int
を%d
で出力するなどの間違いに注意します。
浮動小数(%f %e %g)
%f
は小数を固定小数点で表示し、%e
は指数表記、%g
は短くなる形式を自動選択します。
既定の小数点以下の桁数は6桁です。
#include <stdio.h>
int main(void) {
double x = 12345.6789;
printf("%%f: %f\n", x); // 既定は小数点以下6桁
printf("%%.2f: %.2f\n", x); // 小数点以下2桁
printf("%%e: %e\n", x); // 指数表記
printf("%%g: %g\n", x); // 短い形式を自動選択
return 0;
}
%f: 12345.678900
%.2f: 12345.68
%e: 1.234568e+04
%g: 12345.7
%g
は不要な末尾の0や小数点を削除する特性があります。
必ず小数点を出したい場合は後述の#
フラグが有効です。
文字と文字列(%c %s)
%c
は1文字、%s
はヌル終端文字列を表示します。
%s
に渡すのはchar*
で、終端の'\0'
が必要です。
#include <stdio.h>
int main(void) {
char ch = 'A';
const char *msg = "Hello";
printf("char: %c\n", ch);
printf("string: %s\n", msg);
// 文字列の最大表示長を制限する場合は精度を使います
printf("string (max 3 chars): %.3s\n", msg);
return 0;
}
char: A
string: Hello
string (max 3 chars): Hel
未初期化のポインタやヌル終端がない配列を%s
に渡すとクラッシュなどの未定義動作の原因になります。
ポインタ(%p)とアドレス
アドレスを表示するには%p
を使います。
引数はvoid*
が想定されているため、他のポインタ型は(void*)
にキャストするのが安全です。
#include <stdio.h>
int main(void) {
int value = 10;
int *ptr = &value;
// ポインタ値は%pで表示します(実装依存の形式ですが多くは0x先頭)
printf("address of value: %p\n", (void*)&value);
printf("ptr itself: %p\n", (void*)ptr);
return 0;
}
address of value: 0x7ffeefbff4ac
ptr itself: 0x7ffeefbff4ac
%d
や%x
でポインタを出力するのは未定義動作なので避けてください。
長さ修飾子(h l ll z t)
整数系の書式には「長さ修飾子」を付けて型の大きさを合わせます。
環境によってlong
のビット幅が異なるため、正しい修飾子の使用は重要です。
h
…short
やunsigned short
用に%hd
や%hu
l
…long
やunsigned long
用に%ld
や%lu
ll
…long long
やunsigned long long
用に%lld
や%llu
z
…size_t
用に%zu
(符号なし)t
…ptrdiff_t
用に%td
(符号付き)
#include <stdio.h>
#include <stddef.h> // size_t, ptrdiff_t
int main(void) {
short s = -12;
long l = 1234567890L;
long long ll = 9223372036854775807LL;
size_t sz = (size_t)1024;
int arr[10];
ptrdiff_t diff = &arr[9] - &arr[2];
printf("short: %hd\n", s);
printf("long: %ld\n", l);
printf("long long: %lld\n", ll);
printf("size_t: %zu\n", sz);
printf("ptrdiff_t: %td\n", diff);
return 0;
}
short: -12
long: 1234567890
long long: 9223372036854775807
size_t: 1024
ptrdiff_t: 7
固定幅整数型(例: int32_t
, uint64_t
)を出したい場合はinttypes.h
のPRId32
やPRIu64
マクロの利用を検討すると移植性が高まります。
幅・精度・フラグの指定
書式指定子は一般形%[フラグ][幅][.精度][長さ]変換
で表現されます。
例えば%#08x
は「#
フラグ」「幅8」「0
埋め」「16進整数」といった意味になります。
桁幅(最小幅)
桁幅を指定すると、出力が短い場合に空白で左側を詰めて最小幅を満たします。
数や文字列の見栄えを揃えるのに使います。
#include <stdio.h>
int main(void) {
int a = 7, b = 123, c = 12345;
// 幅6で右寄せ(既定)。短いものは左に空白が入る
printf("|%6d|\n", a);
printf("|%6d|\n", b);
printf("|%6d|\n", c);
// 左寄せは-フラグを使います
printf("|%-6d|\n", a);
// 文字列でも使えます
printf("|%10s|\n", "cat");
printf("|%-10s|\n", "cat");
return 0;
}
| 7|
| 123|
| 12345|
|7 |
| cat|
|cat |
精度(小数点・文字数)
精度は型によって意味が異なります。
- 浮動小数の精度…小数点以下の桁数
- 文字列の精度…最大表示文字数の制限
- 整数の精度…最小桁数(不足分は0で左側を埋める。
0
フラグより強い)
#include <stdio.h>
int main(void) {
double x = 3.1415926535;
printf("float default: %f\n", x);
printf("float precision: %.3f\n", x);
int n = 42;
printf("int default: %d\n", n);
printf("int precision 5: %.5d\n", n); // 00042
const char *s = "Hello, world!";
printf("string precision 5: %.5s\n", s); // 先頭から5文字まで
return 0;
}
float default: 3.141593
float precision: 3.142
int default: 42
int precision 5: 00042
string precision 5: Hello
整数の精度を指定した場合、0
フラグは無視される点に注意します。
フラグ(- + 0 空白 #)
フラグは出力の見た目や記号の扱いを細かく制御します。
-
…左寄せ+
…正の数にも+
を付ける- 空白…正の数の先頭に空白を1つ付ける(
+
があれば無視) 0
…幅を満たすまで左側を0で埋める(整数の精度があると無視)#
…代替形式(16進に0x
、8進に0
、%f/%g
で小数点を強制など)
#include <stdio.h>
int main(void) {
int n = 123;
int m = -123;
unsigned u = 48879; // 0xBEEF
printf("plus flag: %+d / %+d\n", n, m);
printf("space flag: % d / % d\n", n, m);
printf("zero padding: |%08d|\n", n);
printf("left align: |%-8d|\n", n);
printf("hex with #: %#x\n", u);
printf("float with # and zero precision: %#.0f\n", 12.0);
return 0;
}
plus flag: +123 / -123
space flag: 123 / -123
zero padding: |00000123|
left align: |123 |
hex with #: 0xbeef
float with # and zero precision: 12.
#
と%g
を組み合わせると、末尾の小数点や0の削除を抑制できます。
可変幅・精度(*指定)
幅や精度を*
にすると、対応する引数で値を渡せます。
幅が負の場合は左寄せ(-
フラグ)になります。
#include <stdio.h>
void show(double x, int width, int prec) {
// 幅と精度を引数で可変に
printf("width=%d, prec=%d -> |%*.*f|\n", width, prec, width, prec, x);
}
int main(void) {
show(12.3456, 10, 3);
show(12.3456, -10, 2); // 負の幅は左寄せ
show(12.3456, 6, 0);
return 0;
}
width=10, prec=3 -> | 12.346|
width=-10, prec=2 -> |12.35 |
width=6, prec=0 -> | 12|
よくあるエラーとデバッグ
型と書式の不一致
書式と引数の型が一致しないと未定義動作です。
コンパイラ警告を必ず有効にしてください。
#include <stdio.h>
int main(void) {
unsigned int u = 4000000000u; // 32bitなら範囲内の大きな値
// 間違いの例(コメントアウト): 符号なしを%dで出力 -> 未定義動作
// printf("%d\n", u);
// 正しい指定子は%u
printf("correct: %u\n", u);
double x = 3.14;
// 間違いの例(コメントアウト): 浮動小数を%dで出力 -> 未定義動作
// printf("%d\n", x);
// 正しくは%f
printf("correct: %f\n", x);
return 0;
}
correct: 4000000000
correct: 3.140000
GCCやClangでは-Wall -Wextra -Wformat
を付けると不一致を警告してくれます。
可変引数の性質上、実行時に型情報は保持されないため、コンパイル時の警告を重視します。
引数個数のミスマッチ
書式指定子の数と引数の数は一致させます。
足りない、または多すぎると未定義動作です。
#include <stdio.h>
int main(void) {
int a = 10, b = 20;
// 間違いの例(コメントアウト): 引数が1つ足りない
// printf("%d %d\n", a);
// 正しい呼び出し
printf("%d %d\n", a, b);
// 間違いの例(コメントアウト): 余分な引数がある(多くは無視されますが未定義)
// printf("%d\n", a, b);
return 0;
}
10 20
ユーザ入力をそのまま書式文字列に使うのも危険です。
printf(user_input)
のようなコードはフォーマット文字列脆弱性の原因になります。
必ずprintf("%s", user_input)
のように書式は固定し、値は引数で渡します。
改行抜けによる表示遅延
改行なしでprintf
した結果が「出ていない」ように見えることがあります。
前述のとおりバッファリングが原因です。
改行を付ける、fflush(stdout)
を呼ぶ、あるいはstderr
に出力することで対処できます。
#include <stdio.h>
int main(void) {
printf("step1..."); // 改行なし
fflush(stdout); // いったん画面へ
printf("done\n"); // 改行で確定
// デバッグならstderrに出すのも有効(多くの環境で行バッファリングされない)
fprintf(stderr, "debug: %s\n", "immediate message");
return 0;
}
step1...done
debug: immediate message
パイプやリダイレクト先ではstderr
もバッファリングされる場合があるため、確実性が必要な場面では都度fflush
を検討します。
printfでのデバッグのコツ
printf
は軽量なデバッグに有効です。
関数名や行番号、可視化しづらい文字のエスケープを組み合わせると読みやすくなります。
#include <stdio.h>
#include <ctype.h>
// デバッグ出力の簡易マクロ(ファイル名と行番号を付ける)
#define DBG(fmt, ...) \
fprintf(stderr, "[%s:%d] " fmt "\n", __FILE__, __LINE__, __VA_ARGS__)
// 非表示の制御文字を見える形で出すヘルパ
void dump_char(int ch) {
if (isprint(ch)) {
fprintf(stderr, "'%c' (0x%02X)\n", ch, ch);
} else {
fprintf(stderr, "NONPRINT (0x%02X)\n", ch);
}
}
int main(void) {
int x = 42;
const char *s = "sample";
DBG("x=%d, s=%s", x, s);
dump_char('\n'); // 改行は不可視なのでコードで確認
dump_char('A');
return 0;
}
[main.c:15] x=42, s=sample
NONPRINT (0x0A)
'A' (0x41)
より体系的なデバッグにはデバッガ(gdbなど)の利用が有効ですが、printf
は再現性のある軽微な調査に素早く効きます。
まとめ
本記事では、printf
の基礎から実用的な使い方まで、入門者がつまずきやすい点を中心に解説しました。
標準出力とバッファリングの仕組み、代表的な書式指定子の意味、幅と精度やフラグの効果、さらに型不一致や引数ミスマッチといったエラーの回避方法を理解すれば、画面表示だけでなく日常的なデバッグにも自信を持って活用できます。
まずは%d
や%s
から始め、必要に応じて%.2f
や%#x
、%zu
などを使い分け、常にコンパイラの警告に耳を傾けることが上達の近道です。