August 18, 2007

C言語の学習で、関数での文字列の扱いははまりどころじゃね?

それともヲイラがバカなだけ?

もちろん独学なわけだが、Cではまったのは、他の関数から文字列を返り値として受け取るときの方法かな。あ、ちなみにここで言う文字列とは、文字の配列と同義なり。

つまり、C言語では、関数が文字列を返すことはできないのですよ!全てはこれに尽きる。文字列の先頭アドレスは返すことができますけどね。これには本当にはまりました。。。文字列という型がないのが、他の普段使っている言語と違うところでしたね。ってか、当然と言えば、当然なのですけど、頭の中では「文字列を返す」というスキーマができてしまっているので、当時はchar型の配列の先頭アドレスを返すといったことがイメージできませんでした。

つまり、こんなことをしちゃうわけ。

  1. char *getMyString()
  2. {
  3.   char myString[] = "hogehoge";
  4.   return myString;
  5. }
  6. int main()
  7. {
  8.   char *hoge;
  9.  
  10.   hoge = getMyString();
  11.   printf("%s\n", hoge);
  12.   return 0;
  13. }

もちろん上のは動かない。

まぁ、
初級C言語Q&A(2) - Q 【文字列を戻す関数】

C言語講座:Bug4.html
を見ながらなんとかそのレベルを脱出しましたけどさ。しかし、このレベルを脱出するのに2年くらいかかった。まぁ、たまーにしか勉強していなかったけどさ。やっぱり近くにわかった人がいて聞けたら2年もかからなかっただろうなー。つーか、時間かかり杉。。。orz

つまり、自動変数で文字列を確保したところで、その関数の返り値は、文字の配列の先頭アドレスなので、ダメなんですよ。そして、その解決方法としてstaticを付ければいいって書いてあるけど、それは嫌なのよ。なんつーか、その方法は生理的に受け付けん。

ということで、残されたのは次の二つとなる。

  • 関数の呼び出し元で文字の配列を予め確保しておく(静的に確保しても良いし、mallocで確保しても良い)。そして、そのサイズと一緒に引数にしてにその関数を呼び出す。
  • 関数の中でmallocする。あとでfreeをする。

つーわけで上の動かないサンプルを動くようにするには、こうしてみる。ちなみにここで、mallocで失敗したときのことは考えていない。 (追記:Kさんの指摘の通りstrcpyはサイズは積極的にサンプルでも使わない方が良さそうですね。ここは、hogehogeという8文字ということで許してちょんまげ。)

  1. char *getMyString()
  2. {
  3.   char *myString;
  4.   myString = malloc(8 + 1);
  5.   strcpy(myString, "hogehoge");
  6.   return myString;
  7. }
  8. int main()
  9. {
  10.   char *hoge;
  11.  
  12.   hoge = getMyString();
  13.   printf("%s\n", hoge);
  14.   free(hoge);
  15.   return 0;
  16. }

まぁ、いろいろ考えた結果、自分に合ったスタイルで書くのが望ましいと考えていたところ、構造体をクラスのように使うのが私にとっては一番使いやすいと思うようになった。一気に飛んだな。つまり、C 言語によるオブジェクト記述法 COOL 4-2.再コンパイル不要インターフェイスのように。動的バインディング・インタフェースについては、私の理解範囲を超えましたので、よくわかりません。つーか、ifdefの嵐はどうも好きになれん。

それでもmallocやらfreeは処理コストが高いので、静的に確保した方が処理コストを抑えることができるみたいなんだけど、その静的に確保する際のサイズをどうやって決めたらいいか、よくわからないのが今のレベル。。。1024とか、4096とかって数字としてはキリがいいのがわかるんだけど、いつ1024を採用して、いつ4096を採用するなんてことが決められない。。。やっぱり動的の方がいいのよ。うーん。まだまだ先が長いなー。

えと、追記。

なぜ、staticが嫌かと言うと。こんなときに困ってしまうから。つーか、そもそもこんな使い方すんなってことかもしれん。。。

  1. #include <stdio .h>
  2. #include <stdlib .h>
  3. #include <assert .h>
  4.  
  5. #define BUFLEN 16
  6.  
  7. char *getMyString(char *string, int length)
  8. {
  9.   assert(BUFLEN> length);
  10.   static char myString[BUFLEN];
  11.   strncpy(myString, string, length + 1);
  12.   return myString;
  13. }
  14. int main()
  15. {
  16.   char *hoge, *foo;
  17.  
  18.   hoge = getMyString("hogehoge", 8);
  19.   foo = getMyString("foobar", 6);
  20.   printf("hoge: %s\n", hoge);
  21.   printf("foo: %s\n", foo);
  22. }

もちろん結果は。l

  1. hoge: foobar
  2. foo: foobar

となるので、hogeの領域の文字列hogehogeがなくなってしまうからだ。まぁ、当然だけど、静的ローカル変数はリエントラントでないのが、嫌というわけ。まぁ、もっとちゃんとしたサンプルコードを書けば、誤解はなかったかも。

最初のは static char にしたら動くよ。
次のは strcpy じゃなくて strncpy とか strlcpy 使ったらいいよ。
今時 strcpy 使うと怒られるよ。で
この場合は strdup がいいと思うけど。

Comment by K — August 18, 2007

Kさん

コメントどうもです。
static にしたら動くのは知っています。上のポストにも書きましたが、
「その解決方法としてstaticを付ければいいって書いてあるけど、それは嫌なのよ。なんつーか、その方法は生理的に受け付けん。」
ということなのです。

確かに今どきstrcpyはないかもですね。今回に限っては、hogehogeという文字列が決まっているので許してちょんまげ。

って、Kさんって。。。。私の知っている人じゃないですかw

Comment by shin — August 18, 2007

あ、なんとなく違和感わかりました。
ぼくは static に加えて const が付くようなのを想像してました。

Comment by K — August 18, 2007

呼び出し元で配列のサイズの上限が分かってればの話ですが、呼び出し元で配列を用意してやって、そこに結果をつめてもらうようにすれば良いのでは?

Comment by BigFatCat — August 20, 2007

BigFatCatさん

コメントどうもです。

そうですね。それも一つの手段ですね。
私が上で書いた
「関数の呼び出し元で文字の配列を予め確保しておく(静的に確保しても良いし、mallocで確保しても良い)。そして、そのサイズと一緒に引数にしてにその関数を呼び出す。」
の静的に確保するというのが、その方法を意味しているつもりでしたが、説明がわかりにくくてすいません。

Comment by shin — August 20, 2007

mallocとfreeはできるだけ見えるところ、同じ関数内で行うようにしたほうが、開放し忘れとかなくていいですよ。

Comment by ooi — August 21, 2007

ooiさん

コメントありがとうございます。

確かに開放忘れをしないようにしないといけないですね。

でも、今は、文字列のみならず、使いやすいバッファを構造体で持っておいて、それをmallocする関数(コンストラクタ)とfreeする関数(デストラクタ)のように使おうと思っています。また、その上にリストやハッシュの構造体を持って、それもmallocするコンストラクタとfreeするデストラクタを持っておいて、それ以外はmallocしない予定です。ポリシーがしっかりしていれば、開放忘れヶがなくなると思いますので、もう少し勉強してみます。
Cのデータ構造は奥が深いですね。。

Comment by shin — August 21, 2007

Leave a comment

Bloglines feedburner