April 16, 2007

mallocは関数の中ですべきか

C言語の行列ライブラリを訳あって書いていました。その中でふと思ったのが、今回の記事の表題にあるとおりで、結果を引数として指定されたポインタを通じて返す関数では、関数内でmallocするべきか否かという宗教じみた話です。

このことは特に、関数が場合によって異なるサイズの配列を返す場合で問題になると思います。
例えば、結果を格納する場所をポインタで引き受け、内部でmallocによって割り当てを行う関数は次のようになります。

void caller_func(){
    char input;
    char *outputs;
    int number_of_outputs;
    /* input に対して何か処理 */
    callee_func(&input, &outputs, &number_of_outputs);
    free(outputs);
}

void callee_func(char *input, char *outputs[], int *number_of_output){
    *number_of_outputs = (int)(random() * 100);
    *outputs = (char *)malloc(sizeof(char) * number_of_outputs);
    /* (*outputs)[i] に対する何か処理 */
}

mallocを関数の中で使わないとすれば、外で大きめのメモリを確保して渡すことになります。

void caller_func(){
    char input;
    char outputs[256];
    int number_of_outputs;
    /* input に対して何か処理 */
    callee_func(&input, outputs, &number_of_outputs);
}

void callee_func(char *input, char outputs[], int *number_of_outputs){
    *number_of_outputs = (int)(random() * 100);
    /* outputs[i] に対する何か処理 */
}

どちらがスマートでしょうか。入力と出力の関係が常に固定、あるいはそこまでいかなくとも予想が付くのなら、関数の外で大きめのバッファを用意するのがメモリリークの心配もなく良いと思います。しかし、文字列処理など一体最大の大きさがどの程度になるかわからないという場合は関数内でmallocという方法にも一利あると思います。

そこで師匠であるA氏に相談したところ、Cライブラリに沿うようにすれば、ということでした。これはmallocするもしないもどちらにも対応するようにする方法で、引数を見てNULLが入っていたらmallocでメモリを割り当てて、そうでない場合はmallocしないでバッファが外で確保されているとして処理する方法です。例題でいうとmallocを使う版において*outputsでスイッチにすることに相当し、コードで書くと次のようになります。

void callee_func(char *input, char *outputs[], int *number_of_output){
    *number_of_outputs = (int)(random() * 100);
    if(!(*output)){*outputs = (char *)malloc(sizeof(char) * number_of_outputs);}
    /* (*outputs)[i] に対する何か処理 */
}

なるほど、これなら使い分けられます。

23:32 fenrir が投稿 : 固定リンク | | このエントリーを含むはてなブックマーク | この記事をdel.icio.usでブックマーク | トラックバック
このエントリーのトラックバックURL: http://fenrir.naruoka.org/mt/mt-tb.cgi/560
コメント

私は関数が返す値を決して関数内でmalloc()しません。なぜなら、受け取った値をfree()すべきかどうか、必ず間違うからです。他の人は知りませんが、私は間違います。賭けてもいい。
例外はクラス内部の変数で、これはconstructorでnewし、destructorでdeleteしています。
今回のケースでは、ベクトル型をC++言語のクラスにするのが一般的な解じゃないかと思います。でも、いつも思うのですが式をきれいにするためにライブラリを根こそぎ書き直すのも無粋ですよね。こういう記述をするためのC++の仕掛けが奇怪だというのも、私が決してこの解を採用しない理由です。

Posted by: 酔漢 : April 17, 2007 09:31 AM

>酔漢さん
おっしゃるとおりだと思います。
しかしC++がNGといわれたので、仕方なくCで書いています(笑)。実はCでもBoehmGCという裏技を使えば、安全にメモリをやり取りできるんですが、標準ライブラリ以外は使っちゃダメという諸々の事情があったり…
C++は本当に奥が深い変態言語です。boost::spiritのソースを読むとその真髄が垣間見られるような気がします。

Posted by: fenrir : April 17, 2007 09:46 AM
コメントする









名前、アドレスを登録しますか?
(次回以降コメント入力が楽になります)
  • 匿名でのコメントは受け付けておりません。
  • 名前(ハンドル名可)とメールアドレスは必ず入力してください。
  • メールアドレスを表示されたくないときはURLも必ず記入してください。
  • コメント欄でHTMLタグは使用できません。
  • コメント本文に日本語(全角文字)がある程度多く含まれている必要があります。
  • コメント欄内のURLと思われる文字列は自動的にリンクに変換されます。