4409


Cで可変長引数のみを取る関数を作りたいのですが、どのようにすればいいのでしょうか?  
下の例のように、最初に可変長引数の数を示すcounterのような引数を取り、その数を元にforで値を得るものはよく見かけますが、これを
average(10, 20, 30) というふうに可変長引数のみを取り、結果を返すにはどうすればいいのでしょうか?

Macを使っているため、コンパイラはclangです。

[code]

    #include <stdarg.h>
    
    double average(int count, ...) {
        va_list ap;
        int j;
        double sum = 0;
    
        va_start(ap, count);
        for (j = 0; j < count; j++) {
            sum += va_arg(ap, double);
        }
    
        va_end(ap);
        return sum / count;
    }
    
    #average(10, 20, 30) -> 20と返ってくるようにしたい
    
[/code]

コードの引用元:
[Wikipedia:可変長引数](http://ja.wikipedia.org/wiki/%E5%8F%AF%E5%A4%89%E9%95%B7%E5%BC%95%E6%95%B0)


CC BY-SA 3.0
2015-01-11T11:59:22.650
4406
2015-01-13T19:01:40.760
2015-01-12T04:20:21.230
5246
5246
question
c
Cで可変長引数のみを取る関数を作る


標準Cの範囲内では、引数の数がいくつかを得る手段がありません。従って引数の数を示すcountを陽に渡さないのであれば、別の方法で引数の終わりを呼出側が指示する必要があります。引数の最後に特別な値を渡す、などです。

例えば、引数の終端をNaNで示すことにすれば、最低ひとつは引数があるはずなので：

[code]

    #include <stdarg.h>
    #include <math.h>  /* for isnan */
    
    double average(double val, ...) {
        va_list ap;
        int count = 1;
        double sum = val;
    
        if (isnan(val)) return val;
    
        va_start(ap, val);
        for (;;) {
            double v = va_arg(ap, double);
            if (isnan(v)) break;
            sum += v;
            count++;
        }
        va_end(ap);
        return sum / count;
    }
    
[/code]

呼出側ではこうします:

[code]

    #include <math.h>   /* for NAN */
    
    average(10.0, 20.0, 30.0, NAN);
    
[/code]

呼出側で終端マーカーを忘れると不正なメモリにアクセスしてSegmentation fault等になるでしょう。

残念ながら、マーカーを渡さずにただ average(10.0, 20.0, 30.0)
とする方法はありません。(別にグローバル変数を使う、等の荒技はありえますが。)

(修正: NaNのみを引数として呼ばれた場合に対応)


CC BY-SA 3.0
2015-01-11T12:21:48.450
4408
2015-01-11T14:06:39.343
2015-01-11T14:06:39.343
5258
5258
4406
answer


C99の[可変個引数マクロ](http://seclan.dll.jp/c99d/c99d02.htm#dt19990405)と[複合リテラル](http://seclan.dll.jp/c99d/c99d07.htm#dt19991101)を使えば強引に実現可能です。（おすすめできませんが）

[code]

    #include <stdarg.h>
    
    double average_impl(int count, ...) {
        va_list ap;
        va_start(ap, count);
        double sum = 0.0;
        for (int i = 0; i < count; i++) {
            double v = va_arg(ap, double);
            sum += v;
        }
        va_end(ap);
        return sum / count;
    }
    
    #define count_vsargs(...)  (sizeof((double[]){__VA_ARGS__}) / sizeof(double))
    #define average(...)  average_impl(count_vsargs(__VA_ARGS__), __VA_ARGS__)
    
[/code]

呼び出し側コード：

[code]

    average(10.0, 20.0, 30.0);
    // average_impl(3, 10.0, 20.0, 30.0)に展開される
    
[/code]

LiveDemo: <http://melpon.org/wandbox/permlink/zAxIsh9m7AWvMu9q>

* * *

追記：個人的におすすめできない理由としては、1)呼び出し時に整数リテラルを渡すと正常動作しない、2)マクロによる技巧に走った実装である、3)そもそもC99標準の機能自体がマイナーなため保守性に疑問符あたりです。

特に1)は深刻で、呼び出し側コードを`average(10.0, 20.0, 30.0);`から`average(10, 20,
30);`に変えると、前掲コードは正常動作する保証が無くなります（偶然に動く場合もあります）。これはC言語の可変長引数による大きな落とし穴の一つです。良くある不具合パターンは、下記`printf`関数の例でしょう。

[code]

    // NG: 期待通り動作しない！
    printf("%f\n", 10);
    // 書式指定"%f"でdouble型を要求するが、`10`はint型リテラルのため型不一致
    
    // OK: 期待通り動作する
    printf("%f\n", 10.0);
    
[/code]

@HiroshiYamamotoさん[実装](https://ja.stackoverflow.com/a/4415/49)では、コンパイラによる型変換が行われるため、前掲実装コードより少しだけ安全側に倒れます。（元質問に合わせs/`int`/`double`/gとしてコード引用します）

[code]

    #define average(...) ( \
      _average((double[]){__VA_ARGS__}, sizeof((double[]){__VA_ARGS__}) / sizeof(double)) )
    
    double _average(int double[], size_t size)
    {
      if (!size) return 0.0;
      double sum = 0.0;
      size_t i = 0;
      while (i < size) sum += nums[i++];
      return sum / size;
    }
    
[/code]

呼び出し側コード`average(10, 20, 30)`は、`_average((double[]){10, 20, 30},
～)`とマクロ展開され、1個目の引数では`(double[]){...}`＝「double型配列の複合リテラル」が明記されます。このため、可変長引数では起こり得た呼出元の型と`va_args`取得時の型不一致が起こりません。


CC BY-SA 3.0
2015-01-11T14:36:19.477
4409
2015-01-13T11:41:01.190
2017-04-13T12:52:38.920
-1
49
4406
answer


可変長マクロ (Variadic Macro) を使います。他のとあわせてのバリエーションということで:

[code]

    #include <stdarg.h>
    #include <stdio.h>
    
    #define average(...) ( \
      _average((int[]){__VA_ARGS__}, sizeof((int[]){__VA_ARGS__}) / sizeof(int)) )
    
    double _average( int nums[], size_t size)
    {
      if (!size) return 0.0;
      double sum = 0.0;
      int i = 0;
      while (i < size) sum += nums[i++];
      return sum / size;
    }
    
    int main(void) {
      printf( "average: %.2f\n", average( 10, 20, 30));
      return 0;
    }
    
[/code]

出力 ( [Online](http://ideone.com/t5uMnW) ):

[code]

    $ gcc -std=c99 -pedantic-errors var_arg.c ; ./a.out
    average: 20.00
    
[/code]

可変長マクロを使った方法で微妙な点は一度配列に落としているところなのですが、このサンプルコードのように、関数に対して定数だけを渡している場合は、コンパイラによる最適化の対象になるでしょうね。

### 補足

マクロ部分は gcc の
[GNUCコンパイラ拡張](https://gcc.gnu.org/onlinedocs/gcc-4.2.1/gcc/Variadic-
Macros.html) を使うことで、以下のようにできます。

[code]

    #define average(args...) ({\
      int _args[] = {args}; \
      _average(_args, sizeof(_args) / sizeof(int)); \
    })
    
[/code]

出力:

[code]

    $ gcc var_arg.c; ./a.out;
    average: 20.00
    
[/code]


CC BY-SA 3.0
2015-01-11T17:12:45.373
4415
2015-01-11T19:25:33.780
2015-01-11T19:25:33.780
4978
4978
4406
answer


可変長引数をとる関数については、こんなページがあります。  
参考ページ：  
[C言語で可変長引数をとる関数を、型安全に書く方法](http://blog.kazuhooku.com/2014/12/c.html)

> ...  
>  C言語の可変長引数は、型安全でない（まちがった型の引数を渡してもコンパイルエラーにならない）とされています。  
>  これは言語仕様の理解としては正しいのですが、特定の型の引数を任意の個数とる関数に限っては、マクロを使うことで型安全性を確保することができます。  
>  ...  
>  任意の個数のdoubleを引数にとり、その和を返す関数「sumf」を例にあげて説明します。  
>  ... # NUM を終端に利用したコードが提示されています。
>
> では、どのようにsumfを定義すれば良いのでしょう。答えを書いてしまうと、こんな感じです。  
>  ...  
>  ... # マクロをつかったコードが提示されています  
>  ...  
>  この定義では、可変長の引数群をマクロを用いてインラインで配列として初期化し、かつ、その要素数をsizeof演算子を用いて計算しています。  
>  そのため、C言語標準の可変長引数機構を使った場合の問題はいずれも発生しません。  
>
> 要素数が_sumf関数に引数countとして渡されるため、終端を表す特殊な値は必要になりませんし、また、実引数はdouble型の配列として呼出側で構築されるため、誤った型の引数を渡してしまうとコンパイルエラーになります。  
>  あるいは、たとえばint型の値を渡してしまった場合は、コンパイラによってdouble型に昇格することになるからです。

↑ でしめされたコードに従って 任意の個数を引数にとり、その平均値を返すものを書いてみました。

[code]

    // See http://blog.kazuhooku.com/2014/12/c.html
    
    #include <stdio.h>
    
    #define sumi(...)                                 \
      _sumi(                                          \
      (int[]){ __VA_ARGS__ },                         \
        sizeof((int[]){ __VA_ARGS__ }) / sizeof(int)  \
      )
    
    static double _sumi(int* list, size_t count)
    {
      double r = 0;
      size_t i;
    
      // データが 0 個なら、 0 を返す。
      if (count == 0) {
        return 0.0;
      }
    
      for (i = 0; i != count; ++i)
        r += list[i];
    
      return r / count;
    }
    
    int main(int argc, char** argv)
    {
      printf("%1.2f\n", sumi());           /* =>  0.00 */
      printf("%1.2f\n", sumi(1));          /* =>  1.00 */
      printf("%1.2f\n", sumi(10, 20));     /* => 15.00 */
      printf("%1.2f\n", sumi(10, 20, 30)); /* => 20.00 */
      return 0;
    }
    
[/code]

コンパイル、実行の結果：  
$ gcc average.c  
$ ./a.out  
0.00  
1.00  
15.00  
20.00


CC BY-SA 3.0
2015-01-11T17:31:29.977
4417
2015-01-11T17:31:29.977
2020-06-17T08:14:45.997
-1
2987
4406
answer


以下の条件(他にも多々ありますが)を前提にすれば、

  * 32bit CPU/OS
  * GCC のみ
  * function parameter は全て同じ型
  * function parameter のサイズは short int 以上

こんな書き方も出来ます。

[code]

    #include <stdio.h>
    
    double average(int arg1, ...) {
      int *ap = &arg1;
      int *last = (int *)__builtin_frame_address(1);
      int num = last - ap;
      double sum = 0;
    
      while (ap < last) {
        sum += *ap++;
      }
      return (sum / num);
    }
    
    int main(int argc, char *argv[]) {
      printf("average = %f\n", average(10, 20, 30));
      return 0;
    }
    
[/code]

コンパイルスイッチにも条件があって、以下の様になります。

[code]

    $ gcc -mpreferred-stack-boundary=2 -o average average.c
    
[/code]

※ 最適化オプション(-On)を指定すると、期待する結果は得られないでしょう。

しかし、stack frame や function frame に関する知識のある人なら、これが「イカサマ」であることに気が付くことでしょう。例えば、

[code]

    int main(int argc, char *argv[]) {
      printf("average = %f\n", average(10, 20, 30));
      printf("average = %f\n", average(1, 2));
      return 0;
    }
    
[/code]

などとすると、もはや使い物にならなくなります。興味のある方は以下の様にして assembler code を眺めてみると良いかもしれません。

[code]

    $ gcc -S -mpreferred-stack-boundary=2 average.c 
    
[/code]

この問題は `__cyg_profile_func_exit()`
などを使えば何とかできなくはないのですが、その他にも欠陥がありますので以上とします。ご参考、、というか小ネタとしてどうぞ。


CC BY-SA 3.0
2015-01-13T19:01:40.760
4518
2015-01-13T19:01:40.760
4406
answer