[C]可変長引数の扱い。Cではどうするか?

可変長引数とオーバーロード その1ではJavaで可変長引数を使う場合はメソッド呼び出しの都度、配列を生成するため、パフォーマンスに影響があるということを書いた。

でも、本質的にはJavaでは可変長引数も配列引数も同様の扱いだった。
なので、以下のように配列を可変長引数に渡すことができた。
int[] n = { 1, 2, 3 };
int s = sum(n);

int sum(int ... n) {
    ...
}

Cでは可変長引数と配列引数が異なるものなので、配列を可変長引数に渡すことはできない。
配列が自分自身の長さを持っていない、というのもあるけど。
だとすると、Javaでの可変長配列は呼び出し側の配列を生成する手間をなくすという利便性とパフォーマンスがAPIデザインの決め手となるけど、Cではそれ以外にも考えることがありそうだね。

CはJavaのようにすべての型を表すObject型がないため、異なる型のオブジェクトを配列に格納することができない。
だから、APIのデザインとして、一連の異なる型の値を渡したい場合には可変長引数が候補として挙げられる。
ただし、Cでは実行時に型はわからないため、printfの書式のようにどのようなオブジェクトを渡しているかの情報が必要となる。

あらかじめ決まっている同じ型を渡すのであれば、配列と配列サイズを渡せば、済むはずだよね。

もう一つ考えられるのはvoidポインタの配列を渡すということだけど、GCを前提としていない場合、オブジェクトの実体をどうやって管理するかを考えなければいけない。

とりあえず、Cで以下のような関数を型安全に提供することはできないということになる。
/* 第2引数以降の数値の和を返す */
int sum(size_t n, ...)

ここで考えられるのは
  • 型安全性が低下する。
  • 引数分スタックを消費する可能性がある。
そうするとCで可変長引数を使うのは異なる型を不定数渡す場合のみということになる。。。

むしろJavaのように可変長引数と配列を簡単に変換できないことによる問題としては可変長引数の関数をラッピングしたい時に可変長引数として受け取った値を消費してから、再度可変長引数関数を呼び出すのが大変だということだろうか。
Cで可変長引数のAPIを提供する場合はvprintfのようにva_listを受け付ける関数も同時に提供したほうがいいかもしれない。

2 件のコメント:

  1. 異なる型を不定数分渡そうとすると、呼び出し側から指示を受けなきゃいけないんですよね(printfの文字列のように)。そこの機能の染み出し感がいまいちなんだよなぁ。低レイヤーの言語だから、しゃーないっちゃしゃーないのかもしれませんが。

    返信削除
    返信
    1. ログ出力のラッパーなんかも可変引数マクロでなんとかなるからね。
      その時点で型とか意識してないってゆーか。

      削除