質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

Q&A

解決済

3回答

1873閲覧

C言語でポインタ型を返却する関数でmallocした参照を返す場合,局所変数を返す場合の挙動の違い

退会済みユーザー

退会済みユーザー

総合スコア0

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

1グッド

1クリップ

投稿2017/08/05 15:44

C言語のポインタを返す関数の挙動でいま一つピンとこない点があったので
お聞きします。

例えば,以下のようなコードがあった場合

C

1 2 3#include <stdio.h> 4#include <stdlib.h> 5#include <string.h> 6#include <malloc.h> 7 8int *a_function(int a, int b) { 9 int* c = (int * )malloc(sizeof(int)); 10 *c = a + b; 11 return c; 12} 13 14int *b_function(int a, int b) { 15 int c; 16 c = a + b; 17 return &c; 18} 19 20 21int main (int c, char* param[]) { 22 23 int aa = 1; 24 int bb = 2; 25 26 int *cc = NULL; 27 cc = a_function(aa, bb); 28 printf("%p", cc); 29 printf("%d", *cc); 30} 31 32

a_functionおよびb_functionがありますが,a_functionは正しく関数内でmallocされたメモリへのアドレスを返却してくれます。

しかしb_functionは関数内で静的に宣言されているint型変数の参照は返却してくれませんでした。(返却以前にコンパイルエラーですが)
このとき
オライリー詳説Cポインタ
https://www.oreilly.co.jp/books/9784873116563/
には関数内のスタックフレームは関数終了時にスタックからポップされるため関数内で宣言したアドレスが有効なものではなくなってしまうと記述がありました。

つまるところ,
(1)a_functionが正しく目的のアドレスへとアクセスできるのはmallocで確保したint型のポインタが参照するメモリ分は関数を抜けても確保されたまま
(呼び出し元でfreeする責任があるが)int型のポインタを返却しても呼び出し元でそこにアクセスできる....と認識しています。
しかし

(2)b_functionでは関数内で宣言したint型(4byte)分のメモリは確保しても関数終了とともに開放される....と認識していますが
関数内で宣言した上記の変数 int c の参照自体は存在して返却できるのではないでしょうか?

上記コードa_functionでコンパイルが通り b_functionがコンパイルエラーになる点がイマイチピンと来ません。
単純に関数内で確保したアドレスを返したければmallocで動的にヒープへとメモリ確保したものを返却すればよさそうなのですが
ちょっと喉に骨がひっかかったような感覚のため質問させてもらいました。

よろしくご教授お願いします。

Toshimichi👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

LouiS0616

2017/08/05 15:54

コンパイラは何をご利用でしょうか?私の環境(VS2015)ではコンパイルエラーは出ませんでした。
退会済みユーザー

退会済みユーザー

2017/08/05 16:00

!失礼しました。コンパイラはMNgw32のgccです。
guest

回答3

0

こんにちは。

(2)b_functionでは関数内で宣言したint型(4byte)分のメモリは確保しても関数終了とともに開放される....と認識していますが

関数内で宣言した上記の変数 int c の参照自体は存在して返却できるのではないでしょうか?

その通りです。時々、見かけますね。

b_functionがコンパイルエラーになる

コンパイラによると思います。
危険なコードですのでgccやclangは警告を出してくれますが、コンパイルには通り、実行可能です。

サンプル

投稿2017/08/05 16:03

Chironian

総合スコア23272

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

ベストアンサー

(2)b_functionでは関数内で宣言したint型(4byte)分のメモリは確保しても関数終了とともに開放される....と認識していますが
関数内で宣言した上記の変数 int c の参照自体は存在して返却できるのではないでしょうか?

返却できたとして何が起こるか、試すためにmain()を書き換えました。

C

1int main (int c, char* param[]) 2{ 3 int *aptr, *bptr; 4 5 bptr = b_function(3, 4); 6 printf("bptr = %p, *bptr = %d\n", bptr, *bptr); 7 aptr = a_function(1, 2); 8 printf("aptr = %p, *aptr = %d\n", aptr, *aptr); 9 printf("bptr = %p, *bptr = %d\n", bptr, *bptr); 10 return 0; 11}

私の手元に、これを試せるコンパイラがありました。Windows上の、やや古いコンパイラです。コンパイルして動作させた事例をお見せします。

C:\Users\rubato\Documents> cl t87156.c Microsoft(R) 32-bit C/C++ Standard Compiler Version 13.00.9466 for 80x86 Copyright (C) Microsoft Corporation 1984-2001. All rights reserved. t87156.c t87156.c(15) : warning C4172: ローカル変数またはテンポラリのアドレスを返します。 Microsoft (R) Incremental Linker Version 7.00.9466 Copyright (C) Microsoft Corporation. All rights reserved. /out:t87156.exe t87156.obj C:\Users\rubato\Documents> t87156 bptr = 0018FE88, *bptr = 7 aptr = 003B0F10, *aptr = 3 bptr = 0018FE88, *bptr = 2130567168

ご覧の通り、警告は出たものの、コンパイルは通り動作もした、しかし最初 "7" が正しく(?)表示されたけど、その後 2130567168 という値が表示されてしまいました。
int c として割り当てられたメモリ(この場合、0x18FE88番地)の値が上書きされてしまったのです(2130567168 == 0x7EFDE000 は何かのアドレスでしょう)。

ローカル変数のアドレスを返してはいけないのは、こうした現象が起こるからです。

int c に割り当てられたメモリは「解放」された後も存在し続けます。霧のように消えてなくなるわけでも、メモリがアクセスできなくなるわけでもなく、どこか別な場所に移動することももありません。従って、変数 c のアドレスもそのまま、仰る通り「参照自体は存在」し続けると言えなくありません。なので「喉に骨がひっかかったような感覚w」が残るのも、理解できなくありませんが、、、

メモリは、何らかの目的に合わせて・何らかの値を格納するもの・値を変化できるもの・です(ね?)。
では、メモリを**「解放する」とは**、どういうことでしょうか。ざっくり言えば、他のコードが、他の目的に使って構わなくなるという事ではないでしょうか。

ある時点で、もう値を残しておく必要がなくなった・・・ので、他の関数が・別の目的で使っても良いよ、という変数もあります。malloc() で取得したメモリをfree()で返却することが、まさにそうです。

関数内のローカル変数は、関数からリターンしたら、もう他の目的に使える、という前提でメモリを割り当てます。それがC言語の基本設計なのです。この、関数からリターンしたら解放する、ということを実現しているのがスタックです。関数のローカル変数はスタック領域(のメモリ)に割り当てられます。

オライリー詳説Cポインタ
関数内のスタックフレームは関数終了時にスタックからポップされるため
関数内で宣言したアドレスが有効なものではなくなってしまう

繰り返しますが、アドレスが有効ではなくなるとは、int c に割当られたメモリが消えてなくなる…とかではなく、他のコード(上の事例では、おそらくa_function()関数)が、全く異なる目的に使うようになった、もはや b_function() の int c ではなくなった…だから上書きされたのです。

※念の為:"7"と表示したprintf()関数も同じスタック・同じメモリを使って動作するので、その時点で int c を上書きしてしまい、最初から7と表示されない可能性もあります。ただ、上の事例では「たまたま・幸運にも」7という値が表示された、にすぎません。

b_functionは関数内で静的に宣言されているint型変数の参照は返却してくれません

ここの「静的に宣言されている」という言い回しは誤りです。なぜなら、C言語には static というキーワードがあるから。例えば、次のように宣言した変数 c であれば「静的に宣言されている」と言えます。

C

1int *b_function(int a, int b) 2{ 3 static int c; 4 c = a + b; 5 return &c; 6}

このように宣言された変数 c は、スタック領域ではないメモリ領域に割り当てられますので、関数からリターンした後も解放されることはありません。この使い方は定石とも言える正当なものであり、当然コンパイルも実行もできます。

(返却以前にコンパイルエラーですが)

お手元のコンパイラはエラーにしたわけですが、コンパイラによって扱いが異なるのですね。
実は私は最初、メモリが書き換えられる実行例をGCCでお見せできると思いました。試してみると、コンパイル時に警告は出たものの、コンパイルは通ったのですが、実行したら、何も表示せずセグメンテーション例外(!)で終了してしまいました。なんと、b_function()はNULLポインタを返していた、即ち0番地を返すコードが生成されていたのです。そこでWindows上のコンパイラを試した、という次第です。

エラーにするコンパイラ、コンパイルは通すけど実行時にエラーにするコンパイラ・・・NULLかどうか、チェックしてからアクセスしろってことでしょうが・・・それくらい、関数内部のローカル変数のアドレスを返すことは、C言語にとって尋常でない事だと言えます。

投稿2017/08/06 06:15

rubato6809

総合スコア1380

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

(2)b_functionでは関数内で宣言したint型(4byte)分のメモリは確保しても関数終了とともに開放される....と認識していますが

その通りです。

関数内で宣言した上記の変数 int c の参照自体は存在して返却できるのではないでしょうか?

存在しない変数のアドレス(参照)は無効です。「無効な参照が存在する」という事なら合っています。

b_functionがコンパイルエラー

エラーじゃなくて警告では?参照を返却は出来ますが、無効な値なので警告が出ます。無効な参照は返されても使い道が無いです。

投稿2017/08/05 23:10

otn

総合スコア84499

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

maisumakun

2017/08/06 00:54

「自動変数のアドレスを返り値にする」ことが、未定義の動作である「生存期間が終了したオブジェクトを指すポインタの値が使われる場合」に該当するのか少し判断がつきませんが、仮にそうだとすれば、未定義の動作である以上コンパイルエラーにしても規格には合致します。
otn

2017/08/06 01:32

返しただけでは、ポインタとして使われてはいないですね。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問