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ページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
回答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総合スコア1382
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2021/05/09 02:32
2021/05/09 02:35
2021/05/09 02:47
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総合スコア165
0
概念としては、ローカル変数は、関数処理中などの有効範囲内においてのみ、領域を貸出されていると考えられます。
その為、有効範囲外では、一切の保証がされません。
例えるなら、コインロッカーでしょうか。
コインロッカーに荷物を預け、カギを掛けている間は、荷物は守ってもらえます。
ですが、料金を払い、カギを開けてからも荷物をそのままとし、時間をおいて戻った場合、荷物はどうなっているでしょうか?
そのまま残っているかもしれませんし、他の人が他の荷物を預けていたり、空になっていたり、するかもしれません。
ご提示いただいたコードは、上記のように、カギを開けた後、コインロッカーの場所を教えるから、そこの荷物を使ってくれ、と言っているようなものです。
質問者の環境では、たまたま、荷物が残っていたので、正しく動作して見えていますが、いつ、誤動作するかわからない状態です。ご注意下さい。
なお、配列でない場合は、変数に格納されている値を返しています。
上記のコインロッカーの例で言えば、荷物そのものを渡している状態です。
投稿2021/05/09 03:04
総合スコア1750
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
開放された領域にアクセスするのは“未定義動作”です。
こちらを参考に:不定と未定義
「鼻から悪魔が飛び出しても仕様に反しない」・・・要は、“何が起きてもしらないよw”です。
投稿2021/05/09 02:48
編集2021/05/09 02:50総合スコア6851
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
ローカル変数は、スコープから出た(ここでいうとreadA関数を抜けた)時点で、破棄される事になっています。(agesにはアクセス出来なくなる)
ただ、どう破棄されるかまでは規定されていません。
ただ、投稿者さんのコードのように、そのアドレスを覚えておけば、無理矢理アクセスすることが出来てしまいます。ただこの領域は既に「破棄」された領域なので、使ってはいけません。
関数呼び出し時のスタックの動きを勉強されたら良いと思います。破棄された領域へのアクセスがいかに危険な事か分かるようになります。
触りだけ書くと、関数呼び出しをすると、スタックとして確保されているメモリ領域に、呼び出し元アドレスを積んだあと、ローカル変数を積み上げます。
関数を抜ける時に、積み上げたスタックは全て破棄されます。
その後、別の関数を呼び出すと、スタックの先ほどと同じ領域を使って、また呼び出し元アドレスを積んだあと、ローカル変数を積み上げます。(概念的には積み上げる、ですが、実際はその領域を使うと宣言(他の人と使う所が被らないようにする)して書き換えるだけです。)
このように、「破棄」されたあとは、何に使われるのかがプログラマの手を離れてしまうので、そこにアクセスして書き変えるのは禁忌です。
よく言われるのが、最悪の場合、破棄されたアドレスに書き込みを行い、偶然何かの関数の呼び出し元アドレスが書かれた領域を書き換えてしまい、またその書き換えた内容が偶然ストレージ内を全削除する命令だったりする可能性も無くはないのです。
領域外アクセスとは、原則、本当に何が起こるか分からない危険な事です。
(環境によって、破棄のやり方に法則性があったりするので、領域外アクセスを意図的に悪用するウイルスとかもあります。)
投稿2021/05/12 15:37
編集2021/05/12 15:40総合スコア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
総合スコア6385
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
ベストアンサー
テキストに書かれているとおりなので、それ以上説明のしようがないのですが、a[0] = 19;
は「自分用に確保していないメモリ領域」にずかずかと書き込むので、実行時に何らかの例外が引き起こされる可能性もありますし、何も起こらず19と表示されたとしても、それはたまたま運が良かったに過ぎません。決してやってはいけないことです。
投稿2021/05/09 02:02
総合スコア8402
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。