C言語で可変引数関数を使いこなす!va_argマクロのしくみとサンプルコード


可変引数関数の基本

可変引数関数は、関数宣言において ... を使用して宣言されます。これは、関数に 可変引数リスト があることを示します。可変引数リストには、任意の数の引数を含むことができます。

void my_function(int arg1, ...);

上記のように宣言された my_function 関数は、最初の引数 arg1 は固定で、それ以降の引数は可変です。

可変引数関数を定義するには、以下のマクロを使用する必要があります。

  • va_end: 可変引数リストへのアクセスを終了するために使用します。
  • va_arg: 可変引数リストから引数を取得するために使用します。
  • va_start: 可変引数リストへのアクセスを開始するために使用します。

va_arg マクロの使い方

va_arg マクロは、以下の構文で使用されます。

type va_arg(va_list ap, type);
  • type: 取得する引数の型です。
  • ap: va_start で初期化された va_list 型の引数です。

va_arg マクロは、可変引数リストから type 型の値を 1 つ返し、ap を次の引数へ進めます。

以下は、va_arg マクロを使用して可変引数の合計を計算する関数の例です。

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

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

  return sum;
}

この関数は、最初の引数 n で指定された数の整数引数を受け取り、それらの合計を返します。

  • 可変引数関数の引数の型は、関数内で一致する必要があります。
  • 関数内で va_arg マクロを呼び出した後、必ず va_end マクロを呼び出して、可変引数リストへのアクセスを終了する必要があります。
  • 可変引数リストにアクセスする前に、必ず va_start マクロを呼び出す必要があります。
  • va_arg マクロは、可変引数リストを 一度に 1 つ しか処理できません。

可変引数関数は、引数の個数が不定な場合に柔軟な関数を作成できる便利な機能です。va_arg マクロを使用して、可変引数リストから引数にアクセスすることができます。



例 1:可変引数の合計を計算する関数

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

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

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

  return sum;
}

int main() {
  int result = sum_of_variadic_args(4, 1, 2, 3, 4);
  printf("The sum of 1, 2, 3, and 4 is: %d\n", result);

  return 0;
}

このプログラムを実行すると、以下の出力が得られます。

The sum of 1, 2, 3, and 4 is: 10

この例では、va_arg マクロを使用して、可変個の文字列を連結する関数を作成します。

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

char *concat_strings(int n, ...) {
  va_list ap;
  char *buffer = NULL;
  size_t size = 0;

  va_start(ap, n);
  for (int i = 0; i < n; i++) {
    char *str = va_arg(ap, char *);
    size += strlen(str) + 1;
  }
  va_end(ap);

  buffer = malloc(size);
  if (buffer == NULL) {
    return NULL;
  }

  char *ptr = buffer;
  va_start(ap, n);
  for (int i = 0; i < n; i++) {
    char *str = va_arg(ap, char *);
    strcpy(ptr, str);
    ptr += strlen(str) + 1;
  }
  va_end(ap);

  *ptr = '\0';
  return buffer;
}

int main() {
  char *result = concat_strings(3, "Hello", ", ", "World!");
  printf("The concatenated string is: %s\n", result);
  free(result);

  return 0;
}
The concatenated string is: Hello, World!


可変引数テンプレート(C++11以降)

C++11 以降では、可変引数テンプレートを使用して、可変引数関数をより安全かつ効率的に記述することができます。可変引数テンプレートは、コンパイラが型情報を推論できるため、va_arg マクロよりも型安全性の高いコードを作成できます。

#include <iostream>

template <typename... Args>
int sum(Args... args) {
  int total = 0;
  for (auto arg : args) {
    total += arg;
  }
  return total;
}

int main() {
  int result = sum(1, 2, 3, 4, 5);
  std::cout << "The sum is: " << result << std::endl;

  return 0;
}

配列引数

可変引数の代わりに、配列引数を使用して関数を定義することができます。この方法では、引数の個数が事前にわかっている場合に有効です。

int sum_array(int numbers[], int size) {
  int total = 0;
  for (int i = 0; i < size; i++) {
    total += numbers[i];
  }
  return total;
}

int main() {
  int numbers[] = {1, 2, 3, 4, 5};
  int size = sizeof(numbers) / sizeof(numbers[0]);
  int result = sum_array(numbers, size);
  std::cout << "The sum is: " << result << std::endl;

  return 0;
}

構造体と関数ポインタ

より複雑な可変引数関数の場合は、構造体と関数ポインタを使用して実装することができます。この方法は、可変引数の型が異なる場合に役立ちます。

typedef struct {
  int count;
  void *(*types[])(va_list);
} va_list_info_t;

int sum_variadic(va_list_info_t *info, va_list ap) {
  int total = 0;
  for (int i = 0; i < info->count; i++) {
    total += ((int *(*)(va_list))info->types[i])(ap);
  }
  return total;
}

int main() {
  va_list_info_t info = {3, {&va_arg<int>, &va_arg<int>, &va_arg<int>}};
  va_list ap;
  va_start(ap, info);
  int result = sum_variadic(&info, ap);
  va_end(ap);
  std::cout << "The sum is: " << result << std::endl;

  return 0;
}

専用ライブラリ

C言語には、va_arg の代替となる機能を提供するいくつかのライブラリがあります。これらのライブラリは、より安全で使いやすいコードを提供する場合がありますが、追加の依存関係が必要になる場合があります。

va_arg は、C言語における可変引数関数を実装するための標準的な方法ですが、状況に応じて適切な代替方法を選択することが重要です。可変引数テンプレート(C++11以降)、配列引数、構造体と関数ポインタ、専用ライブラリなどを検討することができます。