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

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

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

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

スコープ

スコープとは、プログラム内で変数名など、参照可能な有効範囲のことを指します。

Q&A

解決済

7回答

6940閲覧

初期化していないローカル変数が関数実行終了時に破棄されないのはなぜ?

KIYZ

総合スコア17

C

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

スコープ

スコープとは、プログラム内で変数名など、参照可能な有効範囲のことを指します。

0グッド

0クリップ

投稿2018/07/24 17:14

編集2018/07/24 18:05

先週 C の勉強を始めたばかりの者です。
ローカル変数について学んでいる時に疑問を抱きました。

色んな入門書やネット上の記事では、「ローカル変数はそれが宣言された関数内の処理が全て終わった時点でメモリから消去される。」と説明されていますが、自分の環境でそれを実験してみると、変数とその中に代入した値が破棄されずに残るという現象が起こりました。
※ 試した実行環境は下記の二つです。

  1. Ubuntu 14.04.5 LTS (Cloud9) / gcc version 4.8.4
  2. Wandbox

参考にした入門書はこちらです。
苦しんで覚えるC言語 - 関数内で寿命が尽きる変数

###理解できない現象

C

1#include <stdio.h> 2 3int countfunc(void); 4 5int main(void) 6{ 7 countfunc(); 8 countfunc(); 9 countfunc(); 10 return 0; 11} 12 13int countfunc(void) { 14 int count; // 初期化していない 15 count++; 16 printf("%d\n", count); 17 return count; 18} 19 20// 実行結果: 21// 1 22// 2 23// 3

###疑問

  1. 上記プログラム実行時にはなぜ、 count をまるで静的ローカル変数として宣言したかのように、プログラム終了まで count の値が記憶されるのでしょうか。
  2. 上記プログラムの countfunc() 実行時の最初に、 まるでグローバル変数として宣言したかのように count が 0 に初期化されるのはなぜなのでしょうか。

他にもこの現象を理解する上で知っておくべき概念等がありましたらご教授頂けると幸いです。

よろしくお願い申し上げます。

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

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

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

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

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

guest

回答7

0

こんにちは。

色んな入門書やネット上の記事では、「ローカル変数はそれが宣言された関数内の処理が全て終わった時点でメモリから消去される。」と説明されています

そもそも、ローカル変数をメモリから「消去」するってどのような意味でしょうか?

メモリ自体はハードウェアとして存在します。それをハード的に破壊するという意味でないのは自明と思います。

では、0やその他何か決まった値を書いて「消去」したことにするのでしょうか? その値はなんでしょう?
実のところ決めることはできません。C言語は全ての値をプログラマが使えるように設計されていますので、何か決まった値で埋めて「消去」するという概念がありません。

となるとできることは、あるメモリに割り当てていたローカル変数の割当を解除することだけです。
その後、再度同じ関数を呼び出した結果、前回割り当てられていたメモリが同じローカル変数に割り当てられることもあるかも知れません。(一般的な実装をするとそうなることも少なくないです。)
その結果、以前の値が保持され「消去」されていないかのように見えるかも知れません。

しかし、この動作はあくまでも「たまたま」です。一度割当を解除されたメモリは別の変数やスタックとして使われる可能性があります。(実際そうなることは多いです。)
この場合、再度同じ関数が呼ばれて以前のローカル変数が割り当てられていたメモリが今回も同じローカル変数に割り当てられたとしても既に値は以前の値ではありません。その時、何が入っているか予測することは事実上不可能ですので、そのようなメモリの使い方をしては行けません。

投稿2018/07/24 19:14

Chironian

総合スコア23272

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

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

KIYZ

2018/07/26 17:12

>あるメモリに割り当てていたローカル変数の割当を解除すること >その後、再度同じ関数を呼び出した結果、前回割り当てられていたメモリが同じローカル変数に割り当てられることもあるかも >その結果、以前の値が保持され「消去」されていないかのように見える >何が入っているか予測することは事実上不可能ですので、そのようなメモリの使い方をしては行けません。 入門書やネットの記事で読んだ解説を完全に誤解していたようですが、上記ご説明でそれが解けました。 ご回答誠にありがとうございます。
guest

0

寿命が尽きた変数とは、管理を放棄した変数と言い換える事ができます
(C言語の場合のみ、C++の場合はデコンストラクタによって解放処理が行われる事もあります)

適当に変数を代入するだけのotherfunc関数を追加して

c

1#include <stdio.h> 2 3int countfunc(void); 4void otherfunc(void){ 5 int hoge = 100; 6} 7int main(void) 8{ 9 countfunc(); 10 countfunc(); 11 otherfunc(); 12 countfunc(); 13 return 0; 14} 15 16int countfunc(void) { 17 int count; // 初期化していない 18 count++; 19 printf("%d\n", count); 20 return count; 21}

を手元の環境で実行してみたところ

1 2 101

と、他の関数を呼ぶことで変数の値が変化しました。

他の関数のまったく別の変数によって値が変化する以上信用できる値ではありません。

投稿2018/07/24 21:27

編集2018/07/25 13:50
asm

総合スコア15147

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

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

pepperleaf

2018/07/25 11:41

同じ理由で、なんとなく動いている関数が昔あった。デバッグに苦労したが。
catsforepaw

2018/07/25 12:18

「デコンストラクタ」ではなくて「デストラクタ」ですね。
asm

2018/07/25 13:49

ありがとうございます 最近ファイナライザばかり使ってたので忘れていました
KIYZ

2018/07/26 17:24

とても分かりやすいサンプルプログラムのお陰で、なぜ信用してはいけない値なのかがよく理解できました。 ご回答誠にありがとうございます。
guest

0

c

1 int count; // 初期化していない 2 count++;

この時点で未定義動作です。コンパイラは何をしても許されます。「基本的にはゴミが入る」とかそういうの以前の問題です。

鼻から悪魔:不定値(indeterminate value)バージョン - Qiita

投稿2018/07/24 18:36

編集2018/07/24 18:42
yumetodo

総合スコア5850

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

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

KIYZ

2018/07/26 17:03

未定義動作について調べるきっかけとなりました。 ご回答誠にありがとうございます。
guest

0

「ローカル変数はそれが宣言された関数内の処理が全て終わった時点でメモリから消去される。」

は誤りです。
「消去」されません。「参照してはいけない」のです。
関数が終了したら、以降そのメモリは処理系が勝手に使うことがある(使わないこともある)、
だから、プログラマは使ってはいけない。
と言うことです。

投稿2018/07/25 01:50

nob.

総合スコア711

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

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

KIYZ

2018/07/26 17:54

完全に誤解していました。 ご指摘頂き誠にありがとうございます。
guest

0

仕様は理解された上で、実装についての質問だと思います。

終了した関数のautoローカル変数の値が参照できるのはたまたまメモリを誰も書き換えていないからです。

C

1int main(void) 2{ 3 countfunc(); 4 printf("Hello World!\n"); 5 countfunc(); 6 fclose(stdin); 7 countfunc(); 8 return 0; 9}

等、途中に関数呼び出しなどスタックを使う処理を挟むと結果がおそらく変わってくると思います。
実装依存のため、上記コードでどうなるかは分かりませんので、いろいろやってみてください。
もちろん、全ての処理系が「間に処理を挟まない限りローカルauto変数を保存する」訳でもないです。

初期値が0であったというのは、
・たまたま
・その処理系が常にローカル変数も0で初期化するようになっているとか
などが考えられます。
初回のcountfunc()の前に他の関数呼び出し等を入れてみて、変わるようなら「たまたま」ですね。

投稿2018/07/25 00:22

otn

総合スコア84489

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

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

KIYZ

2018/07/26 17:52

ご回答誠にありがとうございます。 >終了した関数のautoローカル変数の値が参照できるのはたまたまメモリを誰も書き換えていないから 入門書等の解説を間違って解釈していましたが、お陰様で誤解が解けました。 初回の countfunc() の前に他の関数を呼び出してみたところ、 count は 0 以外で初期化されるが、他の関数呼び出し後の count に割り当てられたメモリ領域はその後も書き換えられないという結果になりました。 #include <stdio.h> int countfunc(void); void otherfunc(void) { int hoge = 100; } int main(void) { countfunc(); countfunc(); countfunc(); otherfunc(); countfunc(); countfunc(); countfunc(); return 0; } int countfunc(void) { int count; // 初期化していない count++; printf("%d\n", count); return count; } 実行結果: 1 2 3 101 102 103 スタックについては何となくしか分かっていないので、この機会に学ぼうと思います。
guest

0

面白いですね。この挙動。結論としてはC/C++では初期化しておいたほうが良く、今回の場合ですと初期化しないといけません。
たぶん未定義動作ってやつでないかなーと思います。
直近で残っていた内部的に参照していたエリアを見てしまうのだと思います。
この挙動に依存したプログラムはバグだと思います。なので、この場合ですと0で初期化すべきです。

上記プログラムの実行結果はなぜ、 変数 count を静的ローカル変数として宣言したかのようになるのでしょうか。

なっていません。ただのローカル変数です。

C言語の規約にはなかったはずなので、0が入るのはたまたまか、実装依存です(MSVCなのかgccなのかとか)。
WandBoxとかで試すとg++(C++)かな?
Javaとかはルールが決まっていて、0になったかと思います。
ローカル変数で明示的に初期値を代入しない場合、基本的にはゴミが入ると思ってください。

#include <stdio.h> int countfunc(void); int main(void) { int i = countfunc(); printf("i: %d\n", i); int j = countfunc(); printf("k: %d\n", j); int k = countfunc(); printf("k: %d\n", k); return 0; } int countfunc(void) { int count = 10; // 初期化 count++; printf("%d\n", count); return count; }

これだと意図通りですね。

未定義動作についてリンク1
未定義動作についてリンク2

投稿2018/07/24 18:18

編集2018/07/25 00:40
fa11enprince

総合スコア45

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

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

fa11enprince

2018/07/24 18:58 編集

サーバーエラーが出て二重投稿になってしまいました…。削除しようと思ったら、teratailが消すそうです…。なんてこった。こっちは消さないでおいてください。
KIYZ

2018/07/26 16:46

>未定義動作 >直近で残っていた内部的に参照していたエリアを見てしまう >この挙動に依存したプログラムはバグ 上記の説明がとても分かりやすかったです。 ご回答誠にありがとうございます。
guest

0

ベストアンサー

「ローカル変数はそれが宣言された関数内の処理が全て終わった時点でメモリから消去される。」と説明されていますが

すでに他の回答にもあるように、その説明は間違いです(そのような説明をしている人います?)。

C言語における変数定義とは「メモリのこの場所使うね」という、単なる場所取りです(具体的な場所はコンパイラーによって適切に決められます)。そして、スコープを抜ける際は「もういらないから好きにしていいよ」と言って、特に何もせずに明け渡すだけです。メモリに書いた内容は、他の誰かが同じ場所に書き込まない限り残り続けます。

これを踏まえて、

  1. 上記プログラム実行時にはなぜ、 count をまるで静的ローカル変数として宣言したかのように、プログラム終了まで count の値が記憶されるのでしょうか。

前述のように、スコープを抜けてもメモリに書いた値は他の誰かがいじらなければそのまま残っています。そのため、再度countfunc関数を呼び出したときに、たまたま同じ場所が割り当てられ、しかも他の誰もいじっていなければ前回と同じ値になるので、記憶されたかのように見えてしまうのです。

上記プログラムの countfunc() 実行時の最初に、 まるでグローバル変数として宣言したかのように count が 0 に初期化されるのはなぜなのでしょうか。

main関数に入る前に、たまたま同じ場所を使っていた誰かが、たまたま0を書き込んでいたのだと思います。

ただし、必ずしも上記のような仕組みとは限りません。例えば、最適化を有効にするとメモリを使わずに計算することもあるので、その場合はまったく異なる挙動を示すことになります。

さらに言うと、未初期化のローカル変数を使用した結果は未定義なので、どのような結果をもたらすかは想定できません。実際、Visual Studioではデバッグビルドで警告(ただしエラー扱い)が出ました(親切設計!)。警告を無視して無理矢理コンパイルを通して実行させたらアサーションが発生しました(親切設計!!)。

投稿2018/07/25 12:15

catsforepaw

総合スコア5938

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

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

KIYZ

2018/07/26 18:10

>そのような説明をしている人います? 自分で勝手に誤解していました…。 >C言語における変数定義とは単なる場所取り >スコープを抜けてもメモリに書いた値は他の誰かがいじらなければそのまま残っています >再度countfunc関数を呼び出したときに、たまたま同じ場所が割り当てられ、しかも他の誰もいじっていなければ前回と同じ値になるので、記憶されたかのように見えてしまう >未初期化のローカル変数を使用した結果は未定義なので、どのような結果をもたらすかは想定できません 上記の大変分かりやすいご説明のお陰で質問で投稿したプログラムの実行結果についての理解がかなり深まりました。 心より感謝申し上げます。
rubato6809

2018/07/30 09:59

蛇足ながら、KIYZさんへ countfunc() は、呼び出されるたびにスタック上の(書き換えられずにそのまま残っている)ある特定の番地をcount変数に割り当てるので、静的変数のように見えるわけです。こうしたことはコンパイラが生成したアセンブリコードを読んでみるとかして、スタック領域の使い方がわかってくると不思議でもなんてもなくなります。 一方、サイトの実行例が毎回5369と表示され、現象が異なる件は、count変数がメモリではなく、CPUのレジスタに割り当てられている可能性を考えます(もちろん推測だけ)。レジスタはメモリよりも頻繁に使われる、即ち頻繁に値が変化します。count値として使う直前にcountfunc()の特定のコードを実行するので、結果として特定の値(5368)がセットされるのではないかと。これもコンパイラが生成するアセンブリコードを調べることができるならはっきりするでしょう。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.49%

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

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

質問する

関連した質問