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

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

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

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

Q&A

解決済

8回答

688閲覧

配列の戻り値の考え方

kkkkentsu

総合スコア2

C

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

0グッド

0クリップ

投稿2021/05/09 01:56

C言語初心者です。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef char string[1024];

int* readA(void) {
int ages[4]; //仮に1000~1015番地
return ages;
}
int main(void) {
int* a = readA();
a[0] = 19;
printf("%d\n", a[0]);
return 0;
}
テキストではこのコードの問題点は関数が終了し、配列の寿命がついた後で、本来許されるはずのないメモリ領域にアクセスしてしまう不具合があるとのことなのですが、なせ不具合があるのかわかりません。
このコードでも警告やエラーも出ず、一応19と表示はされます。
例えば、a[0] = 19;の文をなくしたら、特に問題はないのでしょうか。

長文で分かりづらい文章になってしまい申し訳ございません。

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

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

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

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

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

guest

回答8

0

このコードでも警告やエラーも出ず

そうなんですか。。。私の手元のGCCだと

警告: 関数が局所変数のアドレスを返します [-Wreturn-local-addr] return ages;

という警告を出します。それでもコンパイルはできるので、強引に実行させるとセグメントフォールトしコアを吐いてしまいました。質問者の環境は恵まれていますね(皮肉ですw)。

質問者にお願いです。次のコードをコンパイル・実行して、結果を教えてください。

C

1#include <stdio.h> 2#include <stdlib.h> 3#include <string.h> 4typedef char string[1024]; 5 6int* readA(void) { 7 int ages[4]; //仮に1000~1015番地 8 return ages; 9} 10 11int* readB(void) { 12 int hello[4]; 13 hello[0] = 30; 14 return hello; 15} 16 17int main(void) { 18 int* a = readA(); 19 a[0] = 19; 20 printf("%p\n", readB()); 21 printf("%d\n", a[0]); 22 return 0; 23}

Bはアドレスが出ましたが、
Aは19ではなく、-858993460と出ました。

そうですか。それは私が想像した以上のことが起こったようです。でも、それも仕方ありません。19 を書き込んだメモリに、全く別の値(-858993460)が書き込まれたことを示しています。
ages[4] という配列は readA() 関数がリターンすると同時に無くなってしまう・・・メモリ自体は存在している(消えて無くならない)ので運が良ければ 19 という値は残りますが、その後、そのメモリは他の用途に使われてしまう・・・この場合は readB() もしくは printf() が動作する事によって書き変えられてしまったのです。それをテキストは「配列の寿命がついた後で、本来許されるはずのないメモリ領域にアクセスしてしまう」と書いています。


さらに追記。質問者の環境が無いので確かめられないが、次のように printf() 表示を繰り返すだけで不具合を示すかもしれない。

C

1int main(void) { 2 int* a = readA(); 3 a[0] = 19; 4 printf("%d\n", a[0]); // ここで19が表示できたとしても 5 printf("%d\n", a[0]); // もう値が変更されている可能性が… 6 return 0; 7}

投稿2021/05/09 02:24

編集2021/05/09 04:39
rubato6809

総合スコア1382

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

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

kkkkentsu

2021/05/09 02:32

addの値を返さなければいけません というエラーが出ています。
rubato6809

2021/05/09 02:35

すみません、コードに誤りがありました。修正してありますので、やり直してください。
kkkkentsu

2021/05/09 02:47

Bはアドレスが出ましたが、 Aは19ではなく、-858993460と出ました。
guest

0

例えば提示コードを少し改変すると分かりやすいかもしれません。
このコードでは a[0] = 19; と代入しているのに出力されるのは 100 になります。

ローカル変数はスタック上に領域が確保されますが、その性質上
関数を抜けた後にはローカル変数のアドレスは使用できません。
提示コードが動いていたのはたまたまです。
例示しているコードではreadAとfuncのローカル変数が同じメモリアドレスに割り当てられています。
つまり、readAの戻り値のアドレスのメモリ領域は、呼び出した関数内で変更される可能性があるため
main関数の中で操作してはいけません。
「C言語 関数フレーム」とかで検索すると色々と記事が出てくるので調べてみてください。
https://brain.cc.kogakuin.ac.jp/~kanamaru/lecture/MP/final/part06/node9.html

c

1#include <stdio.h> 2 3int* readA(void) { 4 int ages[4]; //仮に1000~1015番地 5 return ages; 6} 7 8void func(void) { 9 int a[100] = {0}; 10 printf("a = %p in func()\n", a); 11 a[0] = 100; 12 printf("%d in func()\n", a[0]); 13} 14 15int main(void) { 16 int* a = readA(); 17 printf("a = %p in main()\n", a); 18 a[0] = 19; 19 func(); 20 printf("%d in main()\n", a[0]); 21 return 0; 22}

出力

a = 0x7fff6020d7b0 in main() a = 0x7fff6020d7b0 in func() 100 in func() 100 in main()

投稿2021/05/09 02:10

編集2021/05/09 02:39
jamjam3

総合スコア165

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

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

jamjam3

2021/05/09 02:13

ちなみにこれはpaiza.ioで実行しています。 プログラムとして間違っている以上、環境によって動作結果は異なるので注意してください。 間違えて回答に投稿してしまいました、削除リクエスト送信ずみです
guest

0

概念としては、ローカル変数は、関数処理中などの有効範囲内においてのみ、領域を貸出されていると考えられます。
その為、有効範囲外では、一切の保証がされません。

例えるなら、コインロッカーでしょうか。
コインロッカーに荷物を預け、カギを掛けている間は、荷物は守ってもらえます。
ですが、料金を払い、カギを開けてからも荷物をそのままとし、時間をおいて戻った場合、荷物はどうなっているでしょうか?
そのまま残っているかもしれませんし、他の人が他の荷物を預けていたり、空になっていたり、するかもしれません。

ご提示いただいたコードは、上記のように、カギを開けた後、コインロッカーの場所を教えるから、そこの荷物を使ってくれ、と言っているようなものです。

質問者の環境では、たまたま、荷物が残っていたので、正しく動作して見えていますが、いつ、誤動作するかわからない状態です。ご注意下さい。

なお、配列でない場合は、変数に格納されている値を返しています。
上記のコインロッカーの例で言えば、荷物そのものを渡している状態です。

投稿2021/05/09 03:04

YT0014

総合スコア1750

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

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

0

開放された領域にアクセスするのは“未定義動作”です。
こちらを参考に:不定と未定義
「鼻から悪魔が飛び出しても仕様に反しない」・・・要は、“何が起きてもしらないよw”です。

投稿2021/05/09 02:48

編集2021/05/09 02:50
cateye

総合スコア6851

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

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

0

ローカル変数は、スコープから出た(ここでいうとreadA関数を抜けた)時点で、破棄される事になっています。(agesにはアクセス出来なくなる)
ただ、どう破棄されるかまでは規定されていません。
ただ、投稿者さんのコードのように、そのアドレスを覚えておけば、無理矢理アクセスすることが出来てしまいます。ただこの領域は既に「破棄」された領域なので、使ってはいけません。
関数呼び出し時のスタックの動きを勉強されたら良いと思います。破棄された領域へのアクセスがいかに危険な事か分かるようになります。

触りだけ書くと、関数呼び出しをすると、スタックとして確保されているメモリ領域に、呼び出し元アドレスを積んだあと、ローカル変数を積み上げます。
関数を抜ける時に、積み上げたスタックは全て破棄されます。
その後、別の関数を呼び出すと、スタックの先ほどと同じ領域を使って、また呼び出し元アドレスを積んだあと、ローカル変数を積み上げます。(概念的には積み上げる、ですが、実際はその領域を使うと宣言(他の人と使う所が被らないようにする)して書き換えるだけです。)
このように、「破棄」されたあとは、何に使われるのかがプログラマの手を離れてしまうので、そこにアクセスして書き変えるのは禁忌です。

よく言われるのが、最悪の場合、破棄されたアドレスに書き込みを行い、偶然何かの関数の呼び出し元アドレスが書かれた領域を書き換えてしまい、またその書き換えた内容が偶然ストレージ内を全削除する命令だったりする可能性も無くはないのです。

領域外アクセスとは、原則、本当に何が起こるか分からない危険な事です。
(環境によって、破棄のやり方に法則性があったりするので、領域外アクセスを意図的に悪用するウイルスとかもあります。)

投稿2021/05/12 15:37

編集2021/05/12 15:40
ttb

総合スコア74

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

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

0

例えば、

C

1int* readA(void) { 2 int ages[4]; //仮に1000~1015番地 3 return ages; 4} 5 6int* bad() 7{ 8 int ages[4]; //仮に1000~1015番地 9 ages[0] = 100; 10 return ages; 11} 12 13int main(void) { 14 int* a = readA(); 15 a[0] = 19; 16 bad(); 17 printf("%d\n", a[0]); 18 return 0; 19}

とすると、どうなるでしょうか?
処理系依存で本来は、やってはいけない事ですが、手元の環境(Visual Studio 2019)では、100が出力されました。
あ、コンパイル時に警告は出ました。
("warning C4172: ローカル変数またはテンポラリのアドレスを返します: ages")

投稿2021/05/09 02:21

pepperleaf

総合スコア6385

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

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

0

戻り値で返しているのが、『ローカル変数のアドレス』なのが問題です。

このコードでも警告やエラーも出ず、一応19と表示はされます。

では、
a[0] = 19;
のあとで、別の関数をコールしたらどうでしょう?
結果を表示しているprintf()では、破壊する前の値を積んでいるようなので表面化しなかったようですが。

投稿2021/05/09 02:05

setoppu

総合スコア311

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

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

0

ベストアンサー

テキストに書かれているとおりなので、それ以上説明のしようがないのですが、a[0] = 19;は「自分用に確保していないメモリ領域」にずかずかと書き込むので、実行時に何らかの例外が引き起こされる可能性もありますし、何も起こらず19と表示されたとしても、それはたまたま運が良かったに過ぎません。決してやってはいけないことです。

投稿2021/05/09 02:02

itagagaki

総合スコア8402

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

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

kkkkentsu

2021/05/09 02:29

a[0] = 19;を消したコードであれば問題はないでしょうか
itagagaki

2021/05/09 02:39

printf("%d\n", a[0]); も問題があります。a[0] という確保されていないメモリを参照しています。 とにかく a は許されないメモリを参照するポインタです。問題ありありです。
dodox86

2021/05/09 02:51

質問者さんはたぶん、まず、C言語における変数のスコープ(可視範囲、あるいは有効範囲)というものを学び直した方が良いかと思います。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.35%

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

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

質問する

関連した質問