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

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

ただいまの
回答率

90.53%

  • C

    3657questions

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

  • スコープ

    12questions

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

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

解決済

回答 7

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 379

KIYZ

score 10

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

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

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

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

理解できない現象

#include <stdio.h>

int countfunc(void);

int main(void)
{
    countfunc();
    countfunc();
    countfunc();
    return 0;
}

int countfunc(void) {
    int count; // 初期化していない
    count++;
    printf("%d\n", count);
    return count;
}

// 実行結果:
// 1
// 2
// 3

疑問

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

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

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

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

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 7

+8

こんにちは。

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

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

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

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

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

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

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/07/27 02:12

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

    入門書やネットの記事で読んだ解説を完全に誤解していたようですが、上記ご説明でそれが解けました。
    ご回答誠にありがとうございます。

    キャンセル

+6

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

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

#include <stdio.h>

int countfunc(void);
void otherfunc(void){
    int hoge = 100;
}
int main(void)
{
    countfunc();
    countfunc();
    otherfunc();
    countfunc();
    return 0;
}

int countfunc(void) {
    int count; // 初期化していない
    count++;
    printf("%d\n", count);
    return count;
}

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

1
2
101

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

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

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/07/25 20:41

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

    キャンセル

  • 2018/07/25 21:18

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

    キャンセル

  • 2018/07/25 22:49

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

    キャンセル

  • 2018/07/27 02:24

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

    キャンセル

+4

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

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

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

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/07/27 02:03

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

    キャンセル

+3

面白いですね。この挙動。結論としては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/25 03:23 編集

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

    キャンセル

  • 2018/07/27 01:46

    >未定義動作
    >直近で残っていた内部的に参照していたエリアを見てしまう
    >この挙動に依存したプログラムはバグ

    上記の説明がとても分かりやすかったです。
    ご回答誠にありがとうございます。

    キャンセル

+3

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

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

int main(void)
{
    countfunc();
    printf("Hello World!\n");
    countfunc();
    fclose(stdin);
    countfunc();
    return 0;
}


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

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

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/07/27 02: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


    スタックについては何となくしか分かっていないので、この機会に学ぼうと思います。

    キャンセル

+3

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

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

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/07/27 02:54

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

    キャンセル

checkベストアンサー

+1

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

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

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

これを踏まえて、

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

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

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

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

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

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

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/07/27 03:10

    >そのような説明をしている人います?
    自分で勝手に誤解していました…。

    >C言語における変数定義とは単なる場所取り
    >スコープを抜けてもメモリに書いた値は他の誰かがいじらなければそのまま残っています
    >再度countfunc関数を呼び出したときに、たまたま同じ場所が割り当てられ、しかも他の誰もいじっていなければ前回と同じ値になるので、記憶されたかのように見えてしまう
    >未初期化のローカル変数を使用した結果は未定義なので、どのような結果をもたらすかは想定できません

    上記の大変分かりやすいご説明のお陰で質問で投稿したプログラムの実行結果についての理解がかなり深まりました。
    心より感謝申し上げます。

    キャンセル

  • 2018/07/30 18:59

    蛇足ながら、KIYZさんへ
    countfunc() は、呼び出されるたびにスタック上の(書き換えられずにそのまま残っている)ある特定の番地をcount変数に割り当てるので、静的変数のように見えるわけです。こうしたことはコンパイラが生成したアセンブリコードを読んでみるとかして、スタック領域の使い方がわかってくると不思議でもなんてもなくなります。

    一方、サイトの実行例が毎回5369と表示され、現象が異なる件は、count変数がメモリではなく、CPUのレジスタに割り当てられている可能性を考えます(もちろん推測だけ)。レジスタはメモリよりも頻繁に使われる、即ち頻繁に値が変化します。count値として使う直前にcountfunc()の特定のコードを実行するので、結果として特定の値(5368)がセットされるのではないかと。これもコンパイラが生成するアセンブリコードを調べることができるならはっきりするでしょう。

    キャンセル

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

  • ただいまの回答率 90.53%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る

  • C

    3657questions

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

  • スコープ

    12questions

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