2019年10月10日木曜日

可変長引数について

日が短くなってきた。
ランニングにはちょうどよい季節だ。

さて、可変長引数について調べたのでまとめようと思う。

可変長引数とは?


  • C言語の標準ライブラリ関数である printf や scanf のように任意の個数をとることができる引数のことを、可変長引数、もしくは動的引数という[1]。これらは決して不思議な力でライブラリのみに使用の許された魔法の機能なんかではなく、ユーザーも可変長引数を持つ関数を定義して使うことができる。
     
  • 宣言は、たとえば以下のようにやる
    1. void function(int argc, ...);
    ... が可変長の引数が入るということを示す。
    引数の並び順として、最初に数の固定された引数が入り、後に数の可変な引数が入る。
    数の固定された引数は二個でも三個でも良いが、必ず一つは用意しなければならない。
     
  • 可変長引数の利用では、以下の専用のマクロを用いることになっている。
    1. va_start
    2. va_copy
    3. va_arg
    4. va_end
    これらのマクロは stdarg.h に定義されており、ISO C99に準拠した実装となっている。vargs.h にも定義がなされているが、そりたは ANSI C89 で書かれたコードの互換性のためにあり、現在では非推奨となっている[2]。

    さて、MSDNの解説によると、/clr オプションを付けてコンパイルした場合、native と CLR の違いのために予期しない結果を生む場合があるとのこと。
    CLRとはなんぞや?と思い調べてみたが、どうやら .Net Framework の土台となっているモノらしい。
    .NET Frameword についても調べないといけないようなので、これについてはまた別の機会に調べたいと思う。


開発・実行環境

下記の環境でプログラムを書いて動作を確かめた。

プロセッサ: Intel(R) Core(TM) i5-5200U CPU @2.20GHz
メモリ: 8.00 GB
OS: Windows 8.1 (64bit)
compiler: Microsoft (R) C/C++ Optimizing Compiler Version 19.00.24215.1 for x86

コード

デモ関数

  1. void print_va_collect(int arg0, ...) {
  2. va_list arg_ptr;
  3. va_start(arg_ptr, arg0);
  4. printf("va 0:%d\n", va_arg(arg_ptr, int));
  5. printf("va 1:%f\n", va_arg(arg_ptr, double));
  6. printf("va 2:%f\n", va_arg(arg_ptr, double));
  7. printf("va 3:%c\n", va_arg(arg_ptr, char));
  8. printf("arg5:%s\n", va_arg(arg_ptr, const char*));
  9. va_end(arg_ptr);
  10. }

試しに書いてみた、可変長引数を使う関数である。
va_list型は引数リストのポインタであるらしい。
va_start では arg_ptr に可変長引数の一個前の引数のポインタを渡す。
(ポインタをセットするマクロであることは分かったが、 &変数名 ではなく 変数名 としているのは生理的に気持ちが悪い。規格がそうなっているのだから文句を言っても仕方がないのだが…)

呼び出す側

  1. print_va_collect(0, 128, 0.1F, 0.2L, 'a', "abc");

固定の第一引数は、今回は特に意味を持たせていないので適当に 0 を入れておく。
あとは、128 (int)、0.1F (float)、0.2L (double)、'a' (char)、"abc" (const char*) を可変引数に入れる。
注意点は、可変長引数に float 型を入れる際は double に変換することである。
以下のようなことはできない(やってみたけど、壊れた動き方をした)。
  1. va_arg(arg_ptr, float)

余談だが、標準変換では、int 型よりも小さい short 型などは int 型に変換され、float 型は double 型に暗黙に変換される[3]。
scanf 関数の書式指定子が float 型には %f、double 型には %lf を用いていることと混同してはならない。
printf 関数に float 型と double 型を入れたとして、その書式指定子はともに %f なのである。
つい最近まで、この違いが分かっていなかった。
初心者にありがちな落とし穴かもしれない。
閑話休題。

感想

printf のような任意書式で情報を提示する機能を可変長引数を用いて実装する機会は多い。
例えば、メッセージボックスやエディットコントロールに表示する場合など。
この可変長変数という機能を用いることで、いちいちその場で sprintf でバッファに文字列を格納するめんどくさい処理を関数化することができる。

付録

今回のコードの最新版はここに上げておく。


参考文献


  1. たとえば 動的引数
  2. va_arg, va_copy, va_end, va_start
  3. va_arg()

0 件のコメント:

コメントを投稿

コメント表示は承認制に設定しています