読者です 読者をやめる 読者になる 読者になる

Gobble up pudding

プログラミングの記事がメインのブログです。

MENU

関数内で宣言した配列変数をreturnしてはいけない

スポンサードリンク

f:id:fa11enprince:20150719065534j:plain
C言語では関数内で宣言した配列変数を素直にreturnする方法がありません。
知らないとこんなコードを書きがちですが、
この書き方では関数内で積まれたスタックがリターン時に解放されてしまうので、
返したアドレスが無効になってしまって、たまたま動くこともありますが、
最悪セグメンテーションフォールトになっちゃいます。
とあるパスの前のパスを返す
get_directory()というのを考えてみます。
例) 例えば渡されたパスが/home/user1/hoge.txtだとすると/home/user1/というのを返す場合を考えてみます。

例1 ダメな例

コンパイル

$ gcc -o string1.exe string1.c
retarr.c: 関数 ‘get_directory’ 内:
retarr.c:18:5: 警告: 関数が局所変数のアドレスを返します [-Wreturn-local-addr]
     return dir;
     ^

※今回はgccで説明していますが、Visual Studioでも基本的に同じです。
その場合はわざわざcl.exeを使わなくてもよいです。

実行結果

この例だとたぶんgccだとこれだけのコードだとうまくいくけど基本的に
まずいコードなので割愛します。やってはいけないです。

じゃあどうするか、方法はいくつかあります。

2. 引数をポインタでもらって、呼び出し側がそれを引数で受け取り、
   その引数でもらったものをreturnする。
3. 引数をポインタでもらって、呼び出し側がそれを引数で受け取る。returnはしない。
4. グローバル変数に返却値を宣言し、それをreturnする。ただし、マルチスレッドだとやばい。 
5. 返却値をstaticで宣言し、関数のスコープを抜けても解放されないようにする。
6. 関数内でmallocして関数のスコープを抜けても解放されないようにする。
   が、誰がいつfreeするのかという問題があり基本的にはNG。

論外な6以外の方法で、
それぞれの例を書きます。以下全部実行結果は同じです。
GCでもついてりゃ6が一番いい気がします。

例2 ポインタでもらう + return


当然ですが、return値は配列じゃなくてポインタで受け取らないとダメです。
でもprintfで出しているのは引数のほうですがね……。

コンパイル

$ gcc -o string2.exe string2.c

実行結果

$ ./string2.exe /home/hogeuser/ctest
path: /home/hogeuser/ctest
dir : /home/hogeuser/

例3 ポインタでもらう


コンパイル

$ gcc -o string3.exe string3.c

実行結果

$ ./string3.exe /home/hogeuser/ctest
path: /home/hogeuser/ctest
dir : /home/hogeuser/

例4 グローバル変数をreturn


ちょっとだけ補足。グローバル変数にstaticがついていますが、これはC独特のもので、
関数内やクラス内(C++)のstaticとは意味合いが少し違います。
スコープをファイル内に限定するという意味でstaticを付けています。
他の言語では無名のnamespaceに近い意味合い。
ただし、ポインタさえ使えばアクセスできてしまいますが。

コンパイル

$ gcc -o string4.exe string4.c

実行結果

$ ./string4.exe /home/hogeuser/ctest
path: /home/hogeuser/ctest
dir : /home/hogeuser/

例5 static変数をreturn


なんだかんだで、例2か例3が普通だと思います。
普通は口から入れて下から出す、というのが自然だと思うのですが、
Cはなにかと口から入れて口から出すというような変なものが多いです。
それってペンギンさんですよね。

コンパイル

$ gcc -o string5.exe string5.c

実行結果

$ ./string5.exe /home/hogeuser/ctest
path: /home/hogeuser/ctest
dir : /home/hogeuser/

例6 関数内でmalloc

場合によってはありだけど、基本的にこの方法は取らないほうがいいので省略。
なぜなら解放を呼び出し元のどこかでやってあげればならないからです。