みんなの「教えて(疑問・質問)」にみんなで「答える」Q&Aコミュニティ

こんにちはゲストさん。会員登録(無料)して質問・回答してみよう!

解決済みの質問

C言語の質問です。

下記のコードでコンパイルすると成功しますが、実行時にエラーになります。

#include <stdio.h>

int series(void);

int main(void)
{
int i;

for(i=0; i<10; i++) printf("%d ", series());

return 0;
}

/* これは正しくない */
int series(void)
{
int total;

total = (total + 1423) % 1422;
return total;
}

解説には『ローカル変数の値はその関数が呼び出されている間だけ保持されます。
このプログラムはseries()関数を使ってある数列を作ろうとしていますが、
数列のそれぞれの数値を計算するのに1つ前の数値を使おうとしています。
しかし、変数totalの値はseries()の各関数呼び出しをまたがって保持される
ことはないため、意図したとおりには動いてくれません。』とあります。

ローカル変数がその関数が呼び出されている間だけ保持されるのはわかりますが、
そのあとの解説の意味がわかりません。
どなたか詳しく解説していただけないでしょうか?お願いします!!

投稿日時 - 2007-11-08 13:35:06

QNo.3500033

すぐに回答ほしいです

質問者が選んだベストアンサー

>修正案は載ってないです・・・

totalをグローバル変数 (またはstatic) に取ると、初期値0が保証され、一旦リターンしても変数の値が維持されますので、(恐らく)意図したとおりに動きます。

ローカル変数にとった場合は、リターンしたら値の保証がなくなるので、他のコード次第で動いたり動かなかったりする不安定なプログラムになります。

series()を連続的に呼び出すと、たまたま以前の値が残っていて正常に動くのですが、間に他の関数を挟むと破壊されて異常な動作をするなどです。(一見動いてしまったりするところがCのデバッグの難しいところです)

投稿日時 - 2007-11-09 16:49:05

お礼

毎度毎度の回答ありがとうございます!!
totalをグローバル変数にして、series()関数内のtotalの宣言を取ったら、

1 2 3 4 5 6 7 8 9 10

という実行結果になりました!
また、totalをstaticにするとやはり上記のような数列になりました!!
本文の『ある数列』とはこの数列のことを言うんでしょうか?
回答参考にさせてもらいます。
ほんとうに回答ありがとうございました。

投稿日時 - 2007-11-09 18:28:07

ANo.11

このQ&Aは役に立ちましたか?

2人が「このQ&Aが役に立った」と投票しています

回答(11)

ANo.10

>分かりやすい説明で、納得できました。
>でもtotalの初期値が不定なので、
>コード自体に間違いがないか
>今使っている本の出版社に問い合わせてみようと思います。

やめてください。全然判ってないと思います。「このプログラムは間違いである」と説明に書いてあるんですよね?

totalをグローバルに取ると初期値が保証されます。

修正案も載ってると思うので、ちゃんと最後まで見たほうが良いのでは?

投稿日時 - 2007-11-09 10:33:22

お礼

本書には『次のプログラムはうまく動きません』とあります。
コメントには『これは正しくない』と記述されているし、
ちょっと早とちりしすぎました。問い合わせるのはやめます。
修正案は載ってないです・・・
2回も回答ありがとうございました。

投稿日時 - 2007-11-09 15:16:21

ANo.9

C言語は機械語と密接に関係します。
わからなければ流して下さい。

Cの関数はスタックという機構を使用します。
(ESPはIntel系CPUのスタックポインタです。)
「関数を実行する」って事は、関数のプログラムにジャンプすることです。
ジャンプしたままだと戻ってこれないので、戻る場所を(2)の様にスタックに積んでからジャンプします。
関数内部で使用するローカル変数の領域を(3)の様にスタックに確保します。
total以外にもローカル変数がある場合、それらの分も確保されます。
また、確保される順番はコンパイラ次第です。
関数の処理が完了したら、ローカル変数を破棄し、戻りアドレスを読み出し、そこにジャンプ(リターン)します。
関数が完了すると(4)の様にESPがSTART地点に帰還してます。

通常Release版では、ローカル変数の確保及び破棄では、ESPが動くだけで内容を変更することはありません。
(VC++のDebug版は確保時に 0xcdcdcdcdにされます。)
ならば、totalの値が維持されて、想定道理に動作するように思われますが。
series関数を実行した後、printf関数が実行されます。
printfも関数ですので、スタックを使用します。
ESPがSTARTに戻っているので、series関数が使用していた領域を、printf関数が自由に使用します。
この動作によって、スタックの中身は不定になります。
次回series関数が実行された時、ローカル変数にはprintfのゴミが入っていることになります。

関数内で関数を実行した場合、(3)の状態からさらに、戻りアドレスを積んで、ローカル変数を・・・となります。
スタックが許す限り、関数を深く実行出来ます。
再帰実行が可能なのはこの機構のお陰です。

ローカル変数が配列などで、範囲を超えて値を書いた場合、戻りアドレスが破壊されます。
(戻りアドレスは近くにあるので、うっかりで直ぐ破壊されます;)
この場合、プログラムが在らぬところに吹っ飛んで、例外発生や暴走を起こします。
この障害を利用したアタックもありますが・・・

1.初期状態
|      |
|------|START <- ESP

2.サブルーティンを実行
|      |
|------|<- ESP
|      |
|戻りアドレス|
|------|START

3.サブルーティン内でローカル変数の確保
|      |
|------|<- ESP
|ローカル変数|
|TOTAL |
|------|
|      |
|戻りアドレス|
|------|START

4.関数リターン後
|      |
|------|
|ローカル変数|
|のゴミ   |
|------|
|戻りアドレス|
|のゴミ   |
|------|START <- ESP

可変長フォントだとガタガタです。頭の中で補完して下さい。^^;

投稿日時 - 2007-11-09 01:01:33

お礼

回答ありがとうございますっ!!
???・・・なんだか分かったような分からないような??ですが、
参考にさせてもらいます。
回答者の多い中、回答ありがとうございました。

投稿日時 - 2007-11-09 09:54:24

ANo.8

いろいろな方が回答されているようですので、内容的にはその通りです。

ただし、補足事項ですが、VCの場合は、変数が宣言された時点で、
デバッグ環境の場合のみ、初期値が割り当てられます。
int の場合は、0xccccccccです。
char a[4];とかやると、0xcc 0xcc 0xcc 0xccがわりあてられます。
要するに確保された変数のメモリに1バイトあたり0xccが割り当てられるようになっています。
よって、文字列などは、ウォッチで変数のところに"へへへへへ"(実際は半角)が表示されます。
以上、豆知識でした^^;

投稿日時 - 2007-11-08 19:18:29

お礼

sayaama さん、回答者が多い中、回答ありがとうございます!!
アドバイス、参考にさせてもらいます!
まだ、使っている本では配列の内容には入っていないんですが、
配列を勉強したら試してみようと思います!
回答ほんとうにありがとうございました!

投稿日時 - 2007-11-08 19:34:18

ANo.7

>Visual C++でコンパイルしているのですが、実行するとエラーで

このエラーはコンパイルオプションによって、消せるはずです。
(VCは使ってないので、具体的な操作は、わかりません。)


>ローカル変数の説明以前に、コードが間違っていると思います。
>出版社に問い合わせてみようと思います。

本に付属のBorland C++Compiler(Ver5.5かな?)で、
デフォルトの設定でコンパイルすれば、恐らくエラーになりません。

たぶん、本の中にも処理系に関する注意書きがあるのでは?

処理系や、コンパイルオプションによってはエラーは出ないので、
間違いとは言い難いですね。

どちらかと言うと、エラーが出ない方が一般的かも。(^^;

投稿日時 - 2007-11-08 17:55:20

お礼

2度の回答ありがとうございます!!
本には『このプログラムは、コンパイラによってはコンパイルすると、
totalの取り扱いに関する警告が出ます』とありました。

Borland C++ Compiler 5.5 でコンパイルしたらエラーになりませんでした!

処理系やコンパイルオプションによってはエラーが出ないんですね!
なんでもかんでもコードの間違いと思っていた私…恥ずかしいです…
勉強になりました。
回答ありがとうございました。

投稿日時 - 2007-11-08 18:39:15

ANo.6

みなさん指摘しているseries関数内のtotalが不定値ですね。
これって何かに載ってたコードでしょうか。。。?
初心者に説明する為に意図的なものを感じなくもないですが。。。

>ローカル変数がその関数が呼び出されている間だけ
>保持されるのはわかりますがそのあとの解説の意味がわかりません
つまり、totalという変数はseries関数が呼び出される度に、
新しく作られる変数のため、いくらtotalに加算して、
series関数を複数回呼出してもtotalは呼び出しの度に
新しくなるため、値を蓄積できないという事を言っています。

関数内では静的に宣言された変数と引数以外の変数は、
内容は全て不定であり、関数が終了すると全て
破棄されます(引数も含む)。
これさえ判っていれば、後の説明は、
今回提示された意図しない動きをする
関数に対してのものなので、特に気にする必要は
無いと思います。

#ローカル変数のスコープに対する説明にしては
#あまり良い例では無いように思いますね。。。

投稿日時 - 2007-11-08 15:01:47

お礼

回答ありがとうございます!!!
このコードは翔泳社発行の「独習C 第3版」に載っている
コードなんですけど、皆さんのおかげで、納得できました。
totalが不定なのは間違いか、問い合わせてみようと思います。
本当に丁寧な回答ありがとうございました。

投稿日時 - 2007-11-08 15:50:11

ANo.5

まず、ローカル変数の「total」が初期化されていない為、
値が不定値です。

サンプルソースですと、series()関数を10回呼び出しており、
その関数の中で、
total = (total + 1423) % 1422;
となっているので、例えば、totalの初期値が0だとした場合、
同じ変数名:totalを使って計算しているので、
1回目:1 = ( 0 + 1423 ) % 1422
2回目:2 = ( 1 + 1423 ) % 1422
3回目:3 = ( 2 + 1423 ) % 1422




10回目:10 = ( 9 + 1423 ) % 1422
という風に、前回計算した値を保持していると思われがちですが、

実際には、ローカル変数なので、
1回目:1 = ( 0 + 1423 ) % 1422
2回目:1 = ( 0 + 1423 ) % 1422
3回目:1 = ( 0 + 1423 ) % 1422




10回目:1 = ( 0 + 1423 ) % 1422

で、同じ値になるので注意しましょうってことだと思います。

ちなみに、static変数にすると前回の値を保持できます。
int total; を static int total = 0;
として実行数すると、1 2 3 4 5 6 7 8 9 10 と表示される
と思います。

投稿日時 - 2007-11-08 14:36:37

お礼

回答ありがとうございます。
分かりやすい説明で、納得できました。
でもtotalの初期値が不定なので、
コード自体に間違いがないか
今使っている本の出版社に問い合わせてみようと思います。
回答ありがとうございました。

投稿日時 - 2007-11-08 15:32:22

ANo.4

>下記のコードでコンパイルすると成功しますが、実行時にエラーになります。

エラーの内容を書いてください。見ただけではなんのエラーかわかりませんでした。

>ローカル変数がその関数が呼び出されている間だけ保持されるのはわかりますが、そのあとの解説の意味がわかりません。

何がわからないんでしょうか? これ以上丁寧な説明はしようがないと思うのですけど。それと、上のエラーの件とこの話は何か関係有りますか?

#補足欄はメールが来ないので、補足はお礼欄にお願いします。

投稿日時 - 2007-11-08 14:33:07

お礼

回答ありがとうございます!!!
Visual C++でコンパイルしているんですが、実行時に
「Microsoft Visual C++ Debug Library」ダイアログが出て、そこには

Line: 19
Run-Time Check Failure #3
The variable 'total' is being used without being defined.

と書いてありました。
できれば、コメント以下のコードがなぜ正しくないのか、詳しく教えていただけたらうれしいです。

投稿日時 - 2007-11-08 15:40:39

ANo.3

>意図したとおりには動いてくれません

この「意図」がわかり難いですね。
どういう意図で書いたかは本人にしか分かりませんから。(^^;

推測になりますが、意図している動作は下記だと思います。

int main(void)
{
int i;
int total;

for(i=0; i<10; i++)
{
total = (total + 1423) % 1422;
printf("%d ", total);
}

return 0;
}


>ある数列を作ろうとしていますが、

これも分かり難いですね。
下記の様に書いても結果は同じだと思います。

total = (total + 1) % 1422;

結局は、ある値(0~1421)からスタートし、1ずつ増加。
1421の次は0に戻る。と言う数列になると思います。

初期値を代入していないので、最初の値は不定です。


>ローカル変数がその関数が呼び出されている間だけ保持されるのはわかりますが、

ローカル変数の説明のようなので、
この点が理解できていれば問題ないと思います。

質問のサンプルはあまり良い例だとは思えません。(^^;

投稿日時 - 2007-11-08 14:31:57

お礼

回答ありがとうございます!!
私もtotalに初期値が代入されてなく、不定なので、
ローカル変数の説明以前に、コードが間違っていると思います。

出版社に問い合わせてみようと思います。
回答ありがとうございました。

投稿日時 - 2007-11-08 15:24:56

ANo.2

series関数で使っている変数totalは、
 ・series関数の内部で定義している
 ・static属性が付いていない
という状態です。したがって、変数totalの寿命は
series関数の内部のみに限られます。
つまり、
 ・series関数が呼び出されるたびに、変数totalのための領域が割り当てられる
 ・series関数の実行が終了すると、変数totalのための領域は消えてしまう
ということになります。

「前回の変数totalの値」を使おうと思っても、
それはどこかに消えてしまっていますので、使えません。

投稿日時 - 2007-11-08 14:27:23

お礼

回答ありがとうございます!!
asuncion さんの回答で、なんとなく分かった感じがします。
また、質問が載っていたら、よろしくお願いします。
回答ありがとうございました。

投稿日時 - 2007-11-08 15:15:46

ANo.1

>下記のコードでコンパイルすると成功しますが、実行時にエラーになります。
エラーにはなりませんよね。意図通り動かないだけで。

series()については、何をする関数か知りませんが、素直に読むと
初期値を必要とする関数で、その初期値に前回のseriesの結果を
必要とする。
しかし、前回のseriesの結果は、ローカル変数なので保持されていない。

何がわからないのでしょうか?
seriesの初回実行時に使う初期値はなんなのだろうとは思いますが。

投稿日時 - 2007-11-08 14:09:37

お礼

さっそくの回答ありがとうございます!!
Visual C++でコンパイルしているのですが、実行するとエラーで
「Microsoft Visual C++ Debug Library」ダイアログが出て、そこには

Line: 19
Run-Time Check Failure #3
The variable 'total' is being used without being defined.

とかいてあるんです。
このエラーと本の解説は関係あるのか分からないんですが、
コメント以降のコードがなぜ正しくないのかわからないんです。
未熟者な私ですが、回答よろしくお願いします。

投稿日時 - 2007-11-08 15:02:56