printf 風関数の記述
可変長引数を扱います。
「printf 風関数の記述」はCoddyKit上の無料C Academyレッスンです。 これはレッスン2/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはC Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 C Academyコースには全4レッスンが含まれています。
フォーマット文字列を受け取る関数
可変個引数の最も強力な用途は、printf スタイルでフォーマット文字列を解釈する関数を作ることです。
フォーマットによって、後続する引数の個数と、それぞれの型を関数に伝えます。
#include <stdio.h>
#include <stdarg.h>
void myprint(const char *fmt, ...) {
printf("format was: %s\n", fmt);
}
int main(void) {
myprint("%d items\n", 5);
return 0;
}フォーマット文字列を走査する
フォーマット文字列を1文字ずつ調べます。% が見つかったら、次の文字が変換指定子です。変換指定子によって、読み取る型が決まります。
それ以外の文字はすべてそのまま出力します。
#include <stdio.h>
#include <stdarg.h>
void scan(const char *fmt) {
for (const char *p = fmt; *p; p++) {
if (*p == '%') printf("[spec:%c]", *(p+1));
else putchar(*p);
}
putchar('\n');
}
int main(void) {
scan("x=%d y=%s");
return 0;
}対応する型を読み取る
指定子が %d の場合は int を読み取ります。%s の場合は char * を読み取ります。フォーマットは実際の引数と完全に一致していなければなりません。
#include <stdio.h>
#include <stdarg.h>
void mini(const char *fmt, ...) {
va_list args;
va_start(args, fmt);
for (const char *p = fmt; *p; p++) {
if (*p == '%' && *(p+1) == 'd') {
printf("%d", va_arg(args, int));
p++;
} else putchar(*p);
}
va_end(args);
}
int main(void) {
mini("count=%d\n", 42);
return 0;
}複数の指定子を処理する
スキャナーを拡張して、複数の変換を処理できるようにします。%d、%s、%c のケースを追加し、そのたびに正しい型を読み取ります。
#include <stdio.h>
#include <stdarg.h>
void fmt_print(const char *fmt, ...) {
va_list args;
va_start(args, fmt);
for (const char *p = fmt; *p; p++) {
if (*p != '%') { putchar(*p); continue; }
p++;
if (*p == 'd') printf("%d", va_arg(args, int));
else if (*p == 's') printf("%s", va_arg(args, char *));
else if (*p == 'c') putchar(va_arg(args, int));
}
va_end(args);
}
int main(void) {
fmt_print("%s is %d\n", "age", 30);
return 0;
}パーセント記号をエスケープする
実際のフォーマット関数では、%% を文字どおりのパーセント記号として扱います。実際の % を出力できるようにケースを追加しましょう。
#include <stdio.h>
#include <stdarg.h>
void fp(const char *fmt, ...) {
va_list args;
va_start(args, fmt);
for (const char *p = fmt; *p; p++) {
if (*p == '%') {
p++;
if (*p == '%') putchar('%');
else if (*p == 'd') printf("%d", va_arg(args, int));
} else putchar(*p);
}
va_end(args);
}
int main(void) {
fp("%d%% done\n", 75);
return 0;
}vprintf に転送する
フォーマット処理を自分で再実装したくない場合も多いでしょう。標準ライブラリには、va_list を直接受け取る vprintf が用意されています。
ラッパーは引数をそのまま渡すだけです。
#include <stdio.h>
#include <stdarg.h>
void logline(const char *fmt, ...) {
va_list args;
va_start(args, fmt);
vprintf(fmt, args);
va_end(args);
}
int main(void) {
logline("value = %d, name = %s\n", 7, "box");
return 0;
}プレフィックスを追加する
vprintf を使う方法なら、すべてのメッセージの前にタグを出力するなど、標準のフォーマット処理の前後に処理を追加するのも簡単です。
#include <stdio.h>
#include <stdarg.h>
void info(const char *fmt, ...) {
printf("[INFO] ");
va_list args;
va_start(args, fmt);
vprintf(fmt, args);
va_end(args);
}
int main(void) {
info("server started on port %d\n", 8080);
return 0;
}vsnprintf でバッファーに書き込む
出力する代わりに文字列としてフォーマットするには、vsnprintf(buf, size, fmt, args) を使います。
size によって書き込み量が制限されるため、バッファーオーバーフローを防げます。また、必ず null 終端されます。
#include <stdio.h>
#include <stdarg.h>
void format_into(char *buf, size_t n, const char *fmt, ...) {
va_list args;
va_start(args, fmt);
vsnprintf(buf, n, fmt, args);
va_end(args);
}
int main(void) {
char out[64];
format_into(out, sizeof(out), "id-%d", 123);
printf("%s\n", out);
return 0;
}長さを返す
vsnprintf は、実際には書き込まれるはずだった文字数を返します。これを使って、切り詰めが発生したかどうかを検出したり、バッファーのサイズを決めたりできます。
#include <stdio.h>
#include <stdarg.h>
int build(char *buf, size_t n, const char *fmt, ...) {
va_list args;
va_start(args, fmt);
int len = vsnprintf(buf, n, fmt, args);
va_end(args);
return len;
}
int main(void) {
char b[10];
int needed = build(b, sizeof(b), "%d-%d", 11, 22);
printf("%s (needed %d)\n", b, needed);
return 0;
}方法を選ぶ
方法は2つあります。完全に制御するためにフォーマットを自分で解析する方法と、正確さを確保してコード量を減らすために vprintf/vsnprintf に転送する方法です。
実際のコードの多くでは、転送する方が安全で簡単です。
#include <stdio.h>
#include <stdarg.h>
void say(const char *fmt, ...) {
va_list args;
va_start(args, fmt);
vfprintf(stdout, fmt, args);
va_end(args);
}
int main(void) {
say("%s = %.1f\n", "pi", 3.14);
return 0;
}型が一致しないと未定義動作
フォーマット文字列が示す型と実際の引数の型が一致しない場合、動作は未定義です。
printf("%d", "text") はクラッシュする可能性があります。フォーマットと引数が常に対応するようにしてください。
#include <stdio.h>
int main(void) {
int n = 5;
printf("%d\n", n);
return 0;
}理解度チェック
printf スタイルのラッパーについての理解度を確認しましょう。
まとめ
printf 形式の関数の作り方を学びました。
- 名前付きパラメーターとしてフォーマット文字列を受け取り、その後に
...を置きます。 %の指定子を自分で解析して対応する型を読み取るか、- 安全で正確なフォーマット処理のために
va_listを vprintf / vsnprintf に転送します。 - 未定義動作を避けるため、フォーマットと引数の型を一致させます。
よくある質問
「printf 風関数の記述」レッスンは無料ですか?
はい。「printf 風関数の記述」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、C Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 C Academyコースには全4レッスンが含まれています。
「printf 風関数の記述」で何を学びますか?
可変長引数を扱います。 ブラウザで直接実行するハンズオンコードでC Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
C Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのC Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン2/4です。
「printf 風関数の記述」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このC Academyレッスンでコードを書いて実行できますか?
はい。すべてのC Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- stdarg マクロ
- printf 風関数の記述
- 型安全性に関する注意点
- 実践例