0Pricing
C Academy · レッスン

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フィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. stdarg マクロ
  2. printf 風関数の記述
  3. 型安全性に関する注意点
  4. 実践例
← C Academyに戻る