閉じる

【C言語】 sprintfの使い方を解説|文字列整形・数値変換・snprintf比較

C言語で文字列を扱う際、数値を文字列に変換したり、複数の値を1つの文字列に整形したい場面は非常に多いです。

その中心となる関数がsprintfです。

本記事では、sprintfの基本から、書式指定子の詳しい使い方、安全に使えるsnprintfとの違いまで、豊富なコード例と図解を交えて丁寧に解説します。

sprintfとは

sprintfの基本構文と戻り値

まずはsprintfがどのような関数なのか、その基本から確認します。

基本構文

C言語
int sprintf(char *str, const char *format, ...);

この関数は標準ライブラリ<stdio.h>で定義されています。

利用するには、ソースの先頭で次のようにインクルードします。

C言語
#include <stdio.h>

引数の意味

  • char *str
    書式化された文字列を書き込む出力先バッファへのポインタです。呼び出し側で十分なサイズの配列を用意しておきます。
  • const char *format
    書式文字列です。普通の文字列に%d%sなどの書式指定子を埋め込んで、後続の可変長引数をどのように整形するか指定します。
  • ...
    書式指定子に対応する実際の値(引数列)です。型と順番が書式指定子と正確に対応している必要があります。

戻り値

戻り値は、出力された文字列の長さ(終端の'\0'は含まない)です。

エラー時の扱いは処理系によって異なる場合がありますが、一般的には負の値が返る可能性があります。

簡単なイメージとしては、次のように理解できます。

このように、printfの「画面に出す」処理をバッファに書き込む形に変えたものがsprintfだと考えると理解しやすくなります。

printfとの違いと使い分け

printfとsprintfは、書式指定の仕組みがほぼ同じです。

ただし、出力先が異なります。

  • printf
    出力先は標準出力(通常はコンソール画面)です。
  • sprintf
    出力先はメモリ上の文字列バッファです。

挙動の違いをコードで確認

C言語
#include <stdio.h>

int main(void) {
    char buffer[100];

    int x = 42;

    // 画面に直接表示
    printf("printf: x = %d\n", x);

    // 文字列としてバッファに書き込み
    sprintf(buffer, "sprintf: x = %d", x);

    // バッファに入った文字列を表示
    printf("buffer = \"%s\"\n", buffer);

    return 0;
}
実行結果
printf: x = 42
buffer = "sprintf: x = 42"

使い分けのポイントとして、画面に表示するだけでよければprintf、後でログとしてまとめて保存したい場合や、ネットワーク送信前に文字列化したい場合などプログラム内部で文字列として扱いたいときはsprintfを選びます。

バッファオーバーフローの危険性

sprintfを使うときに最も重要な注意点が、バッファオーバーフローです。

sprintfは出力文字数を一切チェックしません

そのため、出力がバッファサイズを超えると、隣接メモリを書き換えてしまいます。

これはクラッシュや脆弱性の原因になります。

危険なコード例を見てみます。

C言語
#include <stdio.h>

int main(void) {
    char buf[10];  // たった10バイトのバッファ

    // 非常に長い文字列をsprintfしようとしている
    sprintf(buf, "value = %d, name = %s", 12345, "very_long_name");

    printf("%s\n", buf);  // この時点で未定義動作の可能性

    return 0;
}

このコードはコンパイルは通りますが、実行結果は未定義動作です。

動作環境によってはたまたま動いているように見えますが、別の環境や別のコンパイルオプションではクラッシュするかもしれません。

この問題への対策として登場したのがsnprintfです。

後半で詳しく扱いますが、新しいコードでは基本的にsnprintfを使うと考えておくと安全です。

sprintfの基本的な使い方

ここからは、sprintfの基本的な活用例を順番に見ていきます。

文字列を連結・整形するsprintfの例

sprintfは文字列の連結にもよく使われます。

複数の値を1つの読みやすいメッセージに整形できます。

C言語
#include <stdio.h>

int main(void) {
    char buf[100];

    const char *name = "Taro";
    const char *lang = "C";

    // 文字列を整形してひとまとめの文にする
    sprintf(buf, "Hello, %s! Welcome to %s programming.", name, lang);

    printf("%s\n", buf);

    return 0;
}
実行結果
Hello, Taro! Welcome to C programming.

このように複数の文字列や値を1つのバッファにまとめる用途でsprintfは非常に便利です。

整数を文字列に変換するsprintfの例

C言語標準にはintをstringに変換する専用関数はありませんが、sprintfを使えば簡単に実現できます。

C言語
#include <stdio.h>

int main(void) {
    char buf[32];

    int value = 12345;

    // 整数を10進数の文字列に変換
    sprintf(buf, "%d", value);
    printf("10進数: %s\n", buf);

    // 16進数に変換(小文字)
    sprintf(buf, "%x", value);
    printf("16進数(小文字): %s\n", buf);

    // 16進数に変換(大文字)
    sprintf(buf, "%X", value);
    printf("16進数(大文字): %s\n", buf);

    // 符号付きで表示(+を明示)
    sprintf(buf, "%+d", value);
    printf("符号付き: %s\n", buf);

    return 0;
}
実行結果
10進数: 12345
16進数(小文字): 3039
16進数(大文字): 3039
符号付き: +12345

このように%dや%xなどの書式指定子を変えることで、同じ整数をさまざまな表現の文字列に変換できます。

浮動小数点を文字列に変換するsprintfの例

浮動小数点数(float, double)もsprintfで文字列化できます。

C言語
#include <stdio.h>

int main(void) {
    char buf[64];

    double pi = 3.141592653589793;

    // デフォルトの浮動小数点表示(%fは小数点以下6桁がデフォルト)
    sprintf(buf, "%f", pi);
    printf("デフォルト: %s\n", buf);

    // 小数点以下3桁に丸めて表示
    sprintf(buf, "%.3f", pi);
    printf("小数点以下3桁: %s\n", buf);

    // 指数表記
    sprintf(buf, "%e", pi);
    printf("指数表記(e): %s\n", buf);

    // 大文字Eの指数表記
    sprintf(buf, "%E", pi);
    printf("指数表記(E): %s\n", buf);

    // 自動選択(%gは%fと%eを状況に応じて選択)
    sprintf(buf, "%g", pi);
    printf("自動選択(g): %s\n", buf);

    return 0;
}
実行結果
デフォルト: 3.141593
小数点以下3桁: 3.142
指数表記(e): 3.141593e+00
指数表記(E): 3.141593E+00
自動選択(g): 3.14159

小数点以下の桁数は%.3fのように.の後に数字を書くことで指定できます。

この部分を精度(precision)と呼びます。

日付や時刻などのフォーマット例

日付や時刻のように複数の数値を1つの見やすい形式にまとめる用途でもsprintfはよく使われます。

C言語
#include <stdio.h>

int main(void) {
    char buf[64];

    int year = 2025;
    int month = 1;
    int day = 9;
    int hour = 13;
    int minute = 45;

    // 0埋めと桁数指定を組み合わせて日付時刻を整形
    sprintf(
        buf,
        "%04d-%02d-%02d %02d:%02d",
        year, month, day, hour, minute
    );

    printf("日時: %s\n", buf);

    return 0;
}
実行結果
日時: 2025-01-09 13:45

この例では、%04d%02dといったゼロ埋め付きの幅指定を利用して、常に同じ桁数で表示しています。

書式指定子の詳細は次の章で整理します。

sprintfの書式指定子

ここでは、sprintf(およびprintf系)で共通に使われる書式指定子について整理します。

代表的な書式指定子一覧

代表的な書式指定子を表にまとめます。

書式指定子対応する型意味・例
%d, %iint10進整数(符号付き)
%uunsigned int10進整数(符号なし)
%xunsigned int16進整数(小文字)
%Xunsigned int16進整数(大文字)
%ounsigned int8進整数
%fdouble浮動小数点(固定小数点)
%e, %Edouble指数表記
%g, %Gdouble%fと%eを自動選択
%cint(文字コード)1文字
%schar *文字列(終端’\0’まで)
%%(引数なし)%そのものを表示
%pvoid *ポインタ(実装依存の形式)
%ld などlong, long long など長整数(long系)

型と書式指定子の組み合わせを間違えると未定義動作になるため、特に整数のサイズ(long, long long)などには注意が必要です。

幅・桁数・左寄せなど書式フラグの使い方

書式指定子は、単に%dだけでなく、%5d%-10sのようにオプションを組み合わせて使うことができます。

一般的な構造は次のようになります。

% [フラグ] [幅] [.精度] [長さ修飾子] 変換指定子

ここでは特によく使う左寄せフラグを例で確認します。

C言語
#include <stdio.h>

int main(void) {
    char buf[128];

    int x = 123;
    const char *s = "ABC";

    // 幅を指定して右寄せ
    sprintf(buf, "[%5d]", x);
    printf("数値 右寄せ: %s\n", buf);

    // 幅を指定して左寄せ(フラグ '-' を付ける)
    sprintf(buf, "[%-5d]", x);
    printf("数値 左寄せ: %s\n", buf);

    // 文字列の幅指定(右寄せ)
    sprintf(buf, "[%8s]", s);
    printf("文字列 右寄せ: %s\n", buf);

    // 文字列の幅指定(左寄せ)
    sprintf(buf, "[%-8s]", s);
    printf("文字列 左寄せ: %s\n", buf);

    return 0;
}
実行結果
数値 右寄せ: [  123]
数値 左寄せ: [123  ]
文字列 右寄せ: [     ABC]
文字列 左寄せ: [ABC     ]

幅指定%5dのように数字を入れるだけで、全体の文字数を指定します。

左寄せしたい場合は%-5dのように-フラグを付けます。

ゼロ埋め・符号表示・精度指定のテクニック

次に、実用上よく使うゼロ埋め符号表示、そして精度指定をまとめて見てみます。

C言語
#include <stdio.h>

int main(void) {
    char buf[128];

    int value = 42;
    double pi = 3.14159265;

    // 幅5、右寄せ(空白埋め)
    sprintf(buf, "[%5d]", value);
    printf("幅5 空白埋め: %s\n", buf);

    // 幅5、ゼロ埋め
    sprintf(buf, "[%05d]", value);
    printf("幅5 ゼロ埋め: %s\n", buf);

    // 符号を常に表示
    sprintf(buf, "[%+5d]", value);
    printf("幅5 符号付き: %s\n", buf);

    // 小数点以下2桁に丸め
    sprintf(buf, "[%.2f]", pi);
    printf("小数点以下2桁: %s\n", buf);

    // 幅8、小数点以下2桁
    sprintf(buf, "[%8.2f]", pi);
    printf("幅8 小数点以下2桁: %s\n", buf);

    // ゼロ埋め + 精度は浮動小数点には注意が必要(実装依存の挙動もあるため)
    sprintf(buf, "[%08.2f]", pi);
    printf("幅8 ゼロ埋め 小数点以下2桁: %s\n", buf);

    return 0;
}
実行結果
幅5 空白埋め: [   42]
幅5 ゼロ埋め: [00042]
幅5 符号付き: [  +42]
小数点以下2桁: [3.14]
幅8 小数点以下2桁: [    3.14]
幅8 ゼロ埋め 小数点以下2桁: [00003.14]

要点を整理すると次の通りです。

  • ゼロ埋め:
    %05dのように0フラグ + 幅で、空白の代わりに'0'を使います。
  • 符号表示:
    %+dとすると、正の数も+付きで表示します。
  • 精度:
    浮動小数点では%.2fのように.数字で小数点以下の桁数を指定します。文字列%sに対して精度を指定すると最大文字数の制限になります。

文字列と精度の組み合わせもよく使われます。

C言語
#include <stdio.h>

int main(void) {
    char buf[32];

    const char *s = "abcdef";

    // 文字列を先頭から3文字だけ出力
    sprintf(buf, "%.3s", s);
    printf("先頭3文字: %s\n", buf);

    return 0;
}
実行結果
先頭3文字: abc

文字コードと%s利用時の注意点

%sを使うときには、文字列の終端と文字コードについて注意が必要です。

終端’\0’必須

%s終端の'\0'が来るまで読み続けます。

終端がなければ、メモリの先まで読み込もうとしてクラッシュや情報漏えいの原因になります。

C言語
#include <stdio.h>

int main(void) {
    char buf[16];
    char s[4] = { 'A', 'B', 'C', '\0' };  // 正しい終端付き

    sprintf(buf, "%s", s);
    printf("%s\n", buf);

    return 0;
}
実行結果
ABC

終端'\0'の入れ忘れは、自前で文字列を扱うときの典型的なバグです。

配列を手動で埋める場合は必ず意識してください。

文字コードの違い

C言語自体は文字コードを規定していません。

実際には、以下のような状況があります。

  • 多くのUnix系環境: UTF-8
  • WindowsのマルチバイトAPI: Shift_JIS互換(cp932)が多い

%sバイト列をそのまま扱うだけで、文字コードの変換はしません。

そのため、マルチバイト文字(日本語など)を扱うときは、文字数とバイト数が一致しない点に注意が必要です。

C言語
#include <stdio.h>

int main(void) {
    char buf[64];

    // ここでは例としてUTF-8で"あ"が3バイトと仮定
    const char *s = "あい";

    // 幅指定: バイト数単位で空白が入るため、見た目の幅がずれる可能性
    sprintf(buf, "[%5s]", s);
    printf("%s\n", buf);

    return 0;
}

このように、日本語などを等幅で揃えたい場合は、単純な%sの幅指定では期待通りに整列しないことがあります。

必要に応じて、ワイド文字(wchar_t)やロケール対応関数(wprintfなど)の利用を検討します。

snprintfとの違いと使い分け

ここからは、安全な代替としてよく推奨されるsnprintfについて解説します。

snprintfの基本構文と安全性

snprintf出力バッファのサイズを指定できるsprintfです。

C99以降の標準で定義されています。

C言語
int snprintf(char *str, size_t size, const char *format, ...);

引数の意味

  • char *str
    出力先バッファ。
  • size_t size
    バッファのサイズ(バイト数)です。終端の'\0'も含めたサイズを指定します。
  • const char *format, ...
    sprintfと同じです。

安全性のポイントは、sizeで指定したサイズを超えて書き込まないことです。

必ずバッファの最後に'\0'終端が入るように動作します。

C言語
#include <stdio.h>

int main(void) {
    char buf[10];

    int n = snprintf(buf, sizeof(buf), "value = %d", 12345);

    printf("buf = \"%s\"\n", buf);
    printf("戻り値 n = %d\n", n);

    return 0;
}
実行結果
buf = "value = 12"
戻り値 n = 12

この例では、本来の出力は"value = 12345"で文字数は13ですが、バッファは10バイトしかありません。

そのためsnprintf入りきるところまでだけを書き込み、それでも'\0'終端を保証します。

戻り値nには<l-cst-strong>本来必要だった文字数(終端を除く)</l-cst-strong>が返ります。

この値がsizeof(buf)以上であれば、出力が切り捨てられたことが分かります。

sprintfとsnprintfの挙動比較

sprintfとsnprintfの違いを、同じ状況で比較してみます。

C言語
#include <stdio.h>

int main(void) {
    char buf1[10];
    char buf2[10];

    const char *fmt = "value = %d";
    int value = 12345;

    // 危険なsprintf(オーバーフローの可能性)
    sprintf(buf1, fmt, value);

    // 安全なsnprintf(サイズを指定)
    int n2 = snprintf(buf2, sizeof(buf2), fmt, value);

    printf("sprintf結果(未定義動作の可能性): \"%s\"\n", buf1);
    printf("snprintf結果: \"%s\", 戻り値: %d\n", buf2, n2);

    return 0;
}
実行結果
sprintf結果(未定義動作の可能性): "value = 12345"  ← 環境によってはクラッシュもありうる
snprintf結果: "value = 12", 戻り値: 12

重要な違いは次の通りです。

  • sprintf
    • バッファサイズを知らない
    • 書き込みがあふれても止まらない
    • 戻り値は「書き込まれた文字数」(ただし未定義動作が発生している可能性)
  • snprintf
    • バッファサイズを指定する
    • あふれそうになったら途中までしか書き込まない
    • 戻り値は「本来必要だった文字数」

新規コードでは基本的にsnprintfを使うと覚えておくと、セキュリティと安定性の面で大きなメリットがあります。

バッファサイズの決め方と実装例

snprintfを使うときに悩みがちなのがバッファサイズをどう決めるかという点です。

方法1: 固定サイズ + 余裕を持たせる

最も単純な方法は、ある程度余裕のある固定サイズを用意することです。

C言語
#include <stdio.h>

#define LOG_BUF_SIZE 256

int main(void) {
    char logbuf[LOG_BUF_SIZE];

    int user_id = 1001;
    const char *name = "Taro";
    const char *action = "login";

    // ログメッセージを組み立て
    int n = snprintf(
        logbuf, LOG_BUF_SIZE,
        "user_id=%d, name=%s, action=%s",
        user_id, name, action
    );

    if (n >= LOG_BUF_SIZE) {
        // メッセージが途中で切れた可能性がある
        // ここで警告ログを出すなどの対応も可能
    }

    printf("%s\n", logbuf);

    return 0;
}
実行結果
user_id=1001, name=Taro, action=login

この方法は実装が簡単で、多くのアプリケーションログなどに向いています。

方法2: 2段階でちょうどよいサイズを確保する

より厳密にやる場合は、まずサイズ0でsnprintfを呼んで必要サイズを調べ、その後ちょうどよいサイズをmallocで確保するという手法があります。

C言語
#include <stdio.h>
#include <stdlib.h>

char *make_message(const char *name, int score) {
    int needed;
    char *buf;

    // 1回目: NULLとサイズ0を渡して必要なサイズを調べる
    needed = snprintf(NULL, 0, "name=%s, score=%d", name, score);
    if (needed < 0) {
        return NULL;
    }

    // 必要なサイズ + 終端用1バイトを確保
    buf = (char *)malloc((size_t)needed + 1);
    if (!buf) {
        return NULL;
    }

    // 2回目: 実際に書き込む
    snprintf(buf, (size_t)needed + 1, "name=%s, score=%d", name, score);

    return buf;  // 呼び出し側でfreeが必要
}

int main(void) {
    char *msg = make_message("Taro", 95);
    if (msg) {
        printf("%s\n", msg);
        free(msg);
    }
    return 0;
}
実行結果
name=Taro, score=95

このようにしておけば、どんな長さの文字列でも無駄なく扱えるようになります。

ただしmalloc/freeの管理が必要になる点には注意が必要です。

既存コードでsprintfからsnprintfへ置き換えるポイント

既存プロジェクトでは、昔からのコードにsprintfが多用されていることがあります。

そうしたコードを安全なsnprintfベースに置き換えるときのポイントを整理します。

1. バッファサイズをきちんと把握する

置き換えの前提として、バッファがどこで宣言されているかを確認します。

C言語
// 例: グローバルまたはローカルで
char msg[256];

void func(void) {
    sprintf(msg, "error: code=%d", err);
}

この場合、常にsizeof(msg)が使えるので、置き換えは比較的簡単です。

C言語
void func(void) {
    snprintf(msg, sizeof(msg), "error: code=%d", err);
}

2. バッファポインタしか持っていない場合

関数引数などでchar *bufだけ渡されている場合、そのポインタの先のサイズ情報は失われていることが多いです。

C言語
void make_message(char *buf) {
    sprintf(buf, "hello");
}

このようなケースでは、次のような対策を取ります。

  • 関数シグネチャを変更して、size_t bufsizeも一緒に受け取る
  • あるいは、バッファのサイズをグローバル定数として共有する
C言語
void make_message(char *buf, size_t bufsize) {
    snprintf(buf, bufsize, "hello");
}

既存コードへの影響は大きくなりますが、本質的には「バッファサイズを知らないまま書き込む」設計をやめることが重要です。

3. 戻り値を活用するかどうか決める

snprintfの戻り値をどう扱うかは、用途によります。

  • ログなど多少切れても致命的でない場合:
    とりあえずsnprintfに置き換えるだけでも、オーバーフローの危険は大きく減ります。必要に応じてn >= bufsizeをチェックして警告を出す程度の対応も可能です。
  • プロトコルメッセージなど途中で切れてはいけない場合:
    戻り値nbufsize以上なら、再送の準備をする、より大きなバッファを確保して再生成するなどのロジックが必要です。
C言語
#include <stdio.h>

int send_message(char *buf, size_t bufsize, int code, const char *msg) {
    int n = snprintf(buf, bufsize, "code=%d, msg=%s", code, msg);

    if (n < 0) {
        // 何らかのエラー
        return -1;
    }
    if ((size_t)n >= bufsize) {
        // メッセージが途中で切れた
        // ここでエラー扱いにして送信しないなどの判断も可能
        return -2;
    }

    // buf内のデータを送信する処理につなげる
    // send(sock, buf, n, 0); など

    return 0;
}

このように、snprintfの戻り値を正しく扱うことで、安全かつ堅牢な文字列処理が実現できます。

まとめ

本記事では、C言語のsprintfを中心に、書式指定子の使い方、整数や浮動小数点、日付時刻の整形例を詳しく見てきました。

sprintfは柔軟な文字列整形と数値の文字列化を実現する強力な関数ですが、バッファオーバーフローの危険を常に伴います。

そのため、実務や新規開発ではsnprintfを基本とし、バッファサイズを意識しながら設計することが重要です。

書式指定子やフラグ、精度指定を正しく理解すれば、ログ生成やメッセージフォーマットなど多くの場面で安全かつ読みやすいコードを書くことができます。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!