printf みたいな関数を作る

SPECIAL

printf のような、可変個の引数をとって書式文字列による文字列成型を行う方法を調べてみました。


はじめに

たとえば C++ で、独自の文字列クラスを作ったりして、書式付文字列を取り扱う Format 関数を作成したいとします。

これを実装するために、全てを自力で行おうとすると非常に困難なのがなんとなく想像できます。しかも独自の書式になってしまうと使うときに何かと戸惑ってしまいそう…。

 

その他にも、書式付テキストの場合、とりうる引数の数が状況によって違うはずです。

このような関数を作るというのはあまり馴染みがないことなので難しそうですが、標準の printf では実現しているので、自分でもそういった関数が書けるはず…。

 

そんなことで、標準の printf のような書式機能は使えないのかという感じで調べてみました。

 

可変個の引数をもつ関数を作る

C 言語では、引数の個数が不定の場合に用いる記号がちゃんと用意されていました。

それはピリオドを3つ並べた記号で、これを関数の引数部分の2番目以降に記載することで、それ以降は好きなだけ引数を渡せる関数になります。

 

ヘッダ

void TestFunction(int a, ...);

実装

void TestFunction(int a, ...)

{

}

と、ざっと書くとこんな感じです。見た目にもあきらかですね。

なお、可変個の引数を持たす場合、必ずひとつは固定した引数である必要があるそうです。また、当然かもしれませんけど、可変個であることをしめす記号は最後に書きます。

 

可変個の引数の扱い方

さて、問題はどうやって、その個数もわからない引数を取り扱うのでしょうか。

それを実現するものが、stdarg.h にちゃんとまとめられています。このヘッダを組み込むことで、可変個引数を扱うために必要なものがそろいます。

 

これを組み込むことによって、次のものが用意されるそうです。

va_list 可変長引数を操作するために必要な構造体
va_start (ap, v) 可変長引数を操作する上で必要なデータを取得するマクロ
va_arg (list, mode) 可変長引数から、順に引数を取り出すマクロ
va_end (list) 可変長引数の操作を終了するマクロ

 

これらを使って可変長引数を操作します。

まずは va_list 型の変数を用意したら、続いて va_start マクロを呼び出して、可変長引数を操作する準備をします。そして va_arg マクロで値を順次取り出して、最後に va_end マクロで締めくくる。

こんな流れです。

 

たいした例にはなりませんけど、渡された可変長引数の合計(総和)を求める関数を作ったとすると、次のような感じになります。

#include <stdarg.h>

 

// 総和を求める関数(値は int 型を想定)

// n は、渡す引数の数、それ以降は計算する値です。

int sum(int n, ...)

{

int result = 0;

va_list ap;

 

// 可変個引数の利用準備

// -- 1… va_list 構造体 ap

// -- 2 … 可変個引数の直前にある引数

va_start(ap, n);

 

// 順に取り出して加算する

for (int i = 0; i < n; i++)

{

    // 第2引数で [int] 型として取得するようにする

result += va_arg(ap, int);

}

 

// 可変個引数の利用おわり

va_end(ap);

 

// 総和の返却

return result;

}

 

渡された可変長部分の引数から引数の個数を知ることはできないようなので注意が必要です。上記の例のように引数の個数も渡してもらうなどして、個数を知る必要があります。

printf などはきっと、書式付文字列の中の置き換え文字の個数と文字列の末端などから、引数の個数を知っているのでしょう…。

 

書式付文字列の処理を既存のものに任せる

いくら可変個引数を利用できるとはいっても、上記の段階ではとても printf に引数を渡して処理してもらうということはできません。

書式付文字列の処理を、受け取った可変個引数の値を使って行いたい場合、vsprintf という関数を使用します。

 

int vsprintf(char* buffer, const char* format, va_list arg_ptr);

この vsprintf は sprintf と非常に良く似ていますが、第3引数に、可変個の引数ではなくて、va_list 構造体の値を渡すようになっています。

これを使用することで、簡単に書式付文字列の処理を実装することができます。

 

char* FormatFunc(char* buffer, const char* format, ...)

{

va_list ap;

 

va_start(ap, format);

vsprintf(buffer, format, ap);

va_end(ap);

 

return buffer;

}

たとえばこのような感じで、可変個の引数を素直に vsprintf に渡して、sprintf と同等の機能を持った FormatFunc という関数を簡単に作成することができます。