【応用編】C言語的可変引数関数:フォーマット付き文字列出力、オプション引数など


可変引数関数は、stdarg.h ヘッダーファイルで定義されているマクロを使用して実装されます。主なマクロは以下の通りです。

  • va_end(ap): 可変引数リストへのアクセスを終了します。
  • va_arg(ap, type): 可変引数リストから次の引数を type 型で取り出します。
  • va_start(ap, arg1): 可変引数リストへのアクセスを開始します。ap は可変引数リストへのポインタ、arg1 は最初の固定引数となります。

可変引数関数の例

以下の例は、可変個の整数を合計して返す sum 関数です。

#include <stdarg.h>

int sum(int n, ...) {
  va_list ap;
  int total = 0;

  va_start(ap, n);
  for (int i = 0; i < n; i++) {
    total += va_arg(ap, int);
  }
  va_end(ap);

  return total;
}

int main() {
  int result = sum(3, 10, 20, 30);
  printf("合計: %d\n", result);  // 合計: 60

  result = sum(5, 1, 2, 3, 4, 5);
  printf("合計: %d\n", result);  // 合計: 15
}

この例では、sum 関数は最初の引数 n で指定された個数の整数を va_arg マクロを使用して可変引数リストから取り出し、合計を返します。

可変引数関数の注意点

可変引数関数は便利ですが、以下の点に注意する必要があります。

  • 可変引数関数は、関数ポインタや再帰呼び出しと組み合わせることはできません。
  • 引数の型を正しく指定する必要があります。型が間違っていると、予期しない動作やプログラムクラッシュにつながる可能性があります。
  • 引数の個数を知る方法はありません。そのため、引数の個数を事前に知る必要がある場合は、別の方法で実装する必要があります。

可変引数関数の使用例

可変引数関数は、以下のような場合に役立ちます。

  • オプション引数の処理
  • 引数の個数が不定な関数呼び出し
  • フォーマット付き文字列出力 (printf など)


フォーマット付き文字列出力

この例は、printf 関数のようなフォーマット付き文字列出力機能を再現する my_printf 関数を実装します。

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

void my_printf(const char *format, ...) {
  va_list ap;
  va_start(ap, format);

  while (*format) {
    if (*format == '%') {
      switch (*++format) {
        case 'd':
          printf("%d", va_arg(ap, int));
          break;
        case 's':
          printf("%s", va_arg(ap, char *));
          break;
        case 'f':
          printf("%f", va_arg(ap, double));
          break;
        default:
          putchar(*format);
      }
    } else {
      putchar(*format);
    }
    format++;
  }

  va_end(ap);
}

int main() {
  my_printf("名前: %s, 年齢: %d, 身長: %fcm\n", "Taro", 20, 170.0);
  // 出力: 名前: Taro, 年齢: 20, 身長: 170.0cm

  my_printf("合計: %d + %d = %d\n", 10, 20, 30);
  // 出力: 合計: 10 + 20 = 30
}

このコードでは、my_printf 関数はフォーマット文字列 (format) と可変引数を受け取ります。フォーマット文字列を解析し、に応じて可変引数リストから値を取り出して出力します。

引数の個数が不定な関数呼び出し

この例は、引数の個数が不定な関数 sum を実装します。この関数は、渡されたすべての整数の合計を返します。

#include <stdarg.h>

int sum(int n, ...) {
  va_list ap;
  int total = 0;

  va_start(ap, n);
  for (int i = 0; i < n; i++) {
    total += va_arg(ap, int);
  }
  va_end(ap);

  return total;
}

int main() {
  int result = sum(3, 10, 20, 30);
  printf("合計: %d\n", result);  // 合計: 60

  result = sum(5, 1, 2, 3, 4, 5);
  printf("合計: %d\n", result);  // 合計: 15
}

この例は、オプション引数付きのファイル出力関数 print_file を実装します。

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

void print_file(const char *filename, const char *message, ...) {
  FILE *fp;

  fp = fopen(filename, "a");
  if (fp == NULL) {
    perror("ファイルを開けません");
    return;
  }

  fprintf(fp, "%s: ", message);

  va_list ap;
  va_start(ap, message);

  while ((char *)va_arg(ap, void *) != NULL) {
    fprintf(fp, "%s ", va_arg(ap, char *));
  }

  va_end(ap);

  fputc('\n', fp);
  fclose(fp);
}

int main() {
  print_file("log.txt", "エラーが発生しました。", "詳細: ", "関数Aでエラーが発生しました。", "引数: ", "x = 10, y = 20", NULL);

  print_file("log.txt", "情報メッセージ。", "現在の時刻: ", asctime(localtime(NULL)), NULL);
}


配列

  • 型は、配列の要素型で指定します。
  • 引数の個数は、配列の長さで管理します。
  • 事前に引数の個数と型が分かっている場合は、配列を使用することができます。
void print_numbers(int numbers[], int count) {
  for (int i = 0; i < count; i++) {
    printf("%d ", numbers[i]);
  }
  printf("\n");
}

int main() {
  int numbers[] = {10, 20, 30, 40, 50};
  print_numbers(numbers, 5);  // 出力: 10 20 30 40 50
}

構造体

  • メンバー変数で引数を定義し、それぞれに型を指定します。
  • 引数の個数や型が固定されている場合は、構造体を使用することができます。
typedef struct {
  int x;
  int y;
  double z;
} Point;

void print_point(Point p) {
  printf("座標: (%d, %d, %f)\n", p.x, p.y, p.z);
}

int main() {
  Point p = {10, 20, 3.14};
  print_point(p);  // 出力: 座標: (10, 20, 3.14)
}

標準ライブラリの関数

  • 例えば、printf 関数や vfprintf 関数などは、フォーマット文字列と可変個の引数を受け取って書式化された出力を生成します。
  • 標準ライブラリに用意されている関数の中には、可変引数のような機能を提供するものがあります。
#include <stdio.h>

int main() {
  printf("合計: %d + %d = %d\n", 10, 20, 30);  // 出力: 合計: 10 + 20 = 30
}

マクロ

  • 複雑な処理や柔軟性を求める場合に有効です。
  • マクロを使用して、可変引数のような機能を自作することもできます。

関数ポインタ

  • 高度なプログラミング技法になりますが、柔軟性と汎用性の高いコードを書くことができます。
  • 関数ポインタを使用して、引数の個数や型を動的に扱うことができます。

可変引数関数は便利な機能ですが、状況に応じて適切な代替方法を選択することが重要です。