任意の個数のdoubleを引数にとり、その和を返す関数「
sumf
」を例にあげて説明します。C言語の可変長引数機構を使って
sumf
を定義すると、以下のようになります。#include <math.h> #include <stdarg.h> #include <stdio.h> static double sumf(double nfirst, ...) { double r = 0, n; va_list args; va_start(args, nfirst); for (n = nfirst; ! isnan(n); n = va_arg(args, double)) r += n; va_end(args); return r; } int main(int argc, char **argv) { printf("%f\n", sumf(NAN)); /* => 0 */ printf("%f\n", sumf(1., NAN)); /* => 1 */ printf("%f\n", sumf(1., 2.5, 3., NAN)); /* => 6.5 */ return 0; }が、この定義には「NANを終端に使っているがために、NANを引数として渡すことができない(=終端を表す値が必要になる)」「型安全でない」という2点の問題があります。後者については、たとえば、
sumf(1, 1, NAN)
のように、うっかりdouble
型以外の引数を渡してしまってもコンパイルエラーにならず、ただ結果がおかしくなったりコアダンプしたりすることになります注1。では、どのように
sumf
を定義すれば良いのでしょう。答えを書いてしまうと、こんな感じです。#include <stdio.h> #define sumf(...) \ _sumf( \ (double[]){ __VA_ARGS__ }, \ sizeof((double[]){ __VA_ARGS__ }) / sizeof(double) \ ) static double _sumf(double *list, size_t count) { double r = 0; size_t i; for (i = 0; i != count; ++i) r += list[i]; return r; } int main(int argc, char **argv) { printf("%f\n", sumf()); /* => 0 */注2 printf("%f\n", sumf(1.)); /* => 1 */ printf("%f\n", sumf(1., 2.5, 3)); /* => 6.5 */ return 0; }この定義では、可変長の引数群をマクロを用いてインラインで配列として初期化し、かつ、その要素数を
sizeof
演算子を用いて計算しています。そのため、C言語標準の可変長引数機構を使った場合の問題はいずれも発生しません。要素数が_sumf
関数に引数count
として渡されるため、終端を表す特殊な値は必要になりませんし、また、実引数はdouble
型の配列として呼出側で構築されるため、誤った型の引数を渡してしまうとコンパイルエラーになります。あるいは、たとえばint
型の値を渡してしまった場合は、コンパイラによってdouble
型に昇格することになるからです。私たちが開発しているHTTPサーバ「H2O」では、この手法を用いて、型安全な文字列結合関数
h2o_concat
を定義、使用しています。以上、H2Oで使っているC言語の小ネタ紹介でした。
※この記事はH2O Advent Calendar 2014の一部です。
注1: 手元の環境だと、
注2: 可変長マクロに対して0個の引数を渡すのはC99の規格には違反しますが、GCCやClangは問題なく処理します
sumf(1, 1, NAN)
の結果は1
となります注2: 可変長マクロに対して0個の引数を渡すのはC99の規格には違反しますが、GCCやClangは問題なく処理します
No comments:
Post a Comment