自作出力関数snapshot関数の解説

制作日:2024年12月05日

コード

C

snapshot.h

#include <stdio.h>
#include <stdarg.h>
#include <time.h>

#define MAX_SIZE 4096

extern int snapshot(const char *fmt, ...)
{
    char tstr[MAX_SIZE];
    char buff[MAX_SIZE];
    char message[MAX_SIZE];
    time_t ct;
    struct tm *lst;
    va_list ap;
    int ret;

    time(&ct);
    lst = localtime(&ct);
    strftime(tstr, sizeof(tstr), "%Y/%m/%d %H:%M:%S", lst);

    va_start(ap, fmt);

    ret = vsnprintf(message, sizeof(message), fmt, ap);
    if (ret < 0)
    {
        va_end(ap);
        return -1; /* vsnprintf error */
    }
    else if (ret >= sizeof(message))
    {
        va_end(ap);
        return -2; /* message buffer overflow */
    }

    ret = snprintf(buff, sizeof(buff), "| %s | %s", tstr, message);
    if (ret < 0)
    {
        va_end(ap);
        return -3; /* snprintf error */
    }
    else if (ret >= sizeof(buff))
    {
        va_end(ap);
        return -4; /* buff buffer overflow */
    }

    va_end(ap);

    fprintf(stdout, "%s\n", buff); /* Added newline for standard log behavior */

    return 0;
}

使用しているライブラリ

<stdio.h>

標準入出力を行うためのライブラリで、fprintfsnprintf などの関数を提供します。

<stdarg.h>

可変長引数を処理するためのライブラリで、va_listva_startva_end などのマクロを提供します。

<time.h>

時刻や日付を扱うためのライブラリで、timelocaltimestrftime などの関数を提供します。


使用している関数とその役割

time_t ct; time(&ct);

現在のカレンダー時刻を取得し、ct に格納します。

struct tm *lst; lst = localtime(&ct);

ct を現地時間の構造体 tm に変換し、lst に格納します。

strftime(tstr, sizeof(tstr), "%Y/%m/%d %H:%M:%S", lst);

lst に基づいて日時を指定の形式で文字列 tstr に変換します。

va_list ap; va_start(ap, fmt);

可変長引数を処理するために、va_list 型の変数 ap を初期化します。

vsnprintf(message, sizeof(message), fmt, ap);

可変長引数 apfmt に従ってフォーマットし、結果を message に格納します。

snprintf(buff, sizeof(buff), "| %s | %s", tstr, message);

tstrmessage を組み合わせて最終的な出力文字列を buff に格納します。

va_end(ap);

可変長引数の処理を終了します。

fprintf(stdout, "%s\n", buff);

標準出力に buff の内容を出力します(末尾に改行を追加)。


特徴点

バッファオーバーフローの防止

vsnprintfsnprintf を使用して、指定したサイズ内に収まるように出力を制限しています。これにより、バッファオーバーフローを防止しています。

エラーチェック

vsnprintfsnprintf の戻り値を確認し、出力がバッファサイズを超えた場合やエラーが発生した場合に適切な処理を行っています (返り値 -1, -2, -3, -4)。

可変長引数の処理

va_listva_startva_end を使用して可変長引数を適切に処理しています。これにより、柔軟な引数の受け渡しが可能となっています。


詳細解説

宣言・定義

#define MAX_SIZE 4096

各バッファの最大サイズを 4096 バイトに定義しています。これにより、一度に出力できる文字列長やタイムスタンプの長さに制限を設けています。

char tstr[MAX_SIZE];

フォーマットされた日時文字列(例: "2024/12/05 10:30:00")を格納するための文字配列です。

char buff[MAX_SIZE];

タイムスタンプとユーザーメッセージを結合した、最終的な出力形式の文字列全体を格納するための文字配列です。

char message[MAX_SIZE];

ユーザーから渡された可変長引数部分をフォーマットして格納するための文字配列です。

(その他の変数 time_t ct;, struct tm *lst;, va_list ap;, int ret; はそれぞれの型と役割に応じて宣言されています。)

処理の流れ

  1. 現在時刻の取得とフォーマット:
    • time(&ct); で現在のカレンダー時刻(エポックからの秒数)を取得します。
    • localtime(&ct); で取得したカレンダー時刻を、年月日時分秒などの要素に分解したローカル時刻の構造体 (struct tm) に変換します。
    • strftime(tstr, sizeof(tstr), "%Y/%m/%d %H:%M:%S", lst); で、ローカル時刻構造体を指定された書式("YYYY/MM/DD HH:MM:SS")の文字列に変換し、tstrに格納します。
  2. 可変長引数の処理開始:
    • va_start(ap, fmt); マクロを使用して、可変長引数リスト ap の処理を開始します。fmt は最後の固定引数(この関数の場合はフォーマット文字列)です。
  3. ユーザーメッセージのフォーマット:
    • ret = vsnprintf(message, sizeof(message), fmt, ap); で、ユーザーが渡したフォーマット文字列 fmt とそれに対応する可変長引数 ap を使用して、書式設定された文字列を message バッファに書き込みます。sizeof(message) でバッファサイズを指定し、オーバーフローを防ぎます。
    • エラーチェック:
      • ret < 0: vsnprintf がエンコードエラーなどで失敗した場合、-1を返して終了します。
      • ret >= sizeof(message): フォーマット後の文字列が message バッファに収まらなかった場合(切り捨てが発生した場合を含む)、-2を返して終了します。
  4. タイムスタンプとメッセージの結合:
    • ret = snprintf(buff, sizeof(buff), "| %s | %s", tstr, message); で、先にフォーマットした日時文字列 tstr とユーザーメッセージ message を、指定された書式 "| タイムスタンプ | メッセージ" で buff バッファに結合・格納します。
    • エラーチェック:
      • ret < 0: snprintf が失敗した場合、-3を返して終了します。
      • ret >= sizeof(buff): 結合後の文字列が buff バッファに収まらなかった場合、-4を返して終了します。
  5. 可変長引数の処理終了:
    • va_end(ap); マクロを使用して、可変長引数リスト ap の処理をクリーンアップします。これは va_start と対で必ず呼び出す必要があります。
  6. 標準出力への出力:
    • fprintf(stdout, "%s\n", buff); で、最終的にフォーマットされた文字列 buff を標準出力 (通常はコンソール) に出力します。末尾に改行 \n を追加しています。
  7. 正常終了:
    • すべて成功した場合、0 を返します。

主要なマクロ・関数解説

va_start(va_list ap, last_fixed_arg)

ヘッダ: <stdarg.h>

機能: 可変長引数リスト ap を初期化し、last_fixed_arg (関数に渡される最後の名前付き引数) の直後から引数を読み取れるように設定します。

va_end(va_list ap)

ヘッダ: <stdarg.h>

機能: 可変長引数の処理が終了したことを示し、va_list をクリーンアップします。va_start が呼ばれた後は、関数から抜ける前に必ず呼び出す必要があります。

vsnprintf(char *str, size_t size, const char *format, va_list ap)

ヘッダ: <stdio.h>

機能: va_list (ap) で渡された引数を format 文字列に従って書式化し、結果を文字配列 str に最大 size-1 文字まで書き込み、ヌル終端します。バッファオーバーフローを防ぎます。書き込もうとした文字数(ヌル文字含まず)を返します。エラー時は負数を返します。

snprintf(char *str, size_t size, const char *format, ...)

ヘッダ: <stdio.h>

機能: printf と同様に引数を書式化しますが、出力を画面ではなく文字配列 str に書き込みます。最大 size-1 文字まで書き込み、ヌル終端します。バッファオーバーフローを防ぎます。書き込もうとした文字数(ヌル文字含まず)を返します。エラー時は負数を返します。

用語: 実引数 (Argument) と 仮引数 (Parameter)

実引数 (Argument): 関数を呼び出す際に実際に渡される値や変数のことです。例: myFunction(10, "hello")10"hello"

仮引数 (Parameter): 関数定義の際に、受け取る値を格納するために宣言される変数のことです。例: void myFunction(int count, char* text)counttext