【C言語】可変長引数を受け取る関数の作り方【stdarg.h(va_list/va_start/va_arg/va_end)】

<stdarg.h>ヘッダに可変長引数を扱うための機能が用意されています。

可変個数の実引数

引数の取得は以下のマクロを用いて手動で行う必要があります。

va_listイテレート用の情報を保持する型
va_start(ap, param)イテレートを開始
va_arg(ap, type)次の引数を取り出す
va_end(ap)イテレートを終了

使用例

#include <stdarg.h>

int sum(int count, ...) {
  va_list ap;
  va_start(ap, count);
  
  int sum = 0;
  for (int i = 0; i < count; i++ ) {
    sum += va_arg(ap, int);
  }
  va_end(ap);
  return sum;
}
int main() {
  int s = sum(3, 1, 2, 3);
  printf("%d", s);                    // 6
  printf("%d", sum((3), 1, 2, 3, 4)); // 6
  printf("%d", sum((4), 1, 2, 3, 4)); // 10
  printf("%d", sum((2), 1/* , 2 */)); // 無効なバッファを参照するため危険
}

可変長引数の個数

可変長引数の個数を取得することは出来ません。そのため、関数側には何らかの形で引数の個数を知らせる必要があります。今回のサンプルではint count引数の値で可変長引数のサイズを明示しました。

NULL終端の活用

NULL終端や番兵によって可変長引数の終わりを知らせるテクニックもあります。

bool strin(const char *target, const char *args, ...) {
  va_list ap;
  va_start(ap, args);
  for (const char *arg = args; arg != NULL; arg = va_arg(ap, const char *)) {
    if (!strcmp(target, arg)) {
      va_end(ap);
      return true;
    }
  }
  va_end(ap);
  return false;
}
if (strin("a", "A", "a", NULL)) puts("have");

NULLが出現するまで実引数の取り出し操作と比較処理が繰り返されます。

可変長引数をprintf関数に渡す方法

先頭引数をprintf関数に渡すだけでは不十分です。問題なく動いてしまう場合もありますが、最悪の場合にはプログラムがクラッシュします。

void pf(const char *format, ...) {
  // 危険
  // printf(format); // 警告:Format string is not a string literal (potentially insecure)
}

printf関数をラップしたい場合にはvprintf関数を使用します。

void pf(const char *format, ...) {
  va_list ap;
  va_start(ap, format);
  vprintf(format, ap);
  va_end(ap);
}
pf("%s %d", "Shop", 99); // "Shop 99"
広告
広告