ショボ短歌会

もうすぐC言語のテストがあるので適当に自分で問題を作って
プログラムを作る練習をしていたのですが配列の所でちょっと疑問に思いました。

問題
ひとつずつ数字を入力していき、それまでの数字の合計と平均を求めるプログラム。
0を入力するとプログラム終了(配列、ポインタ、関数を用いること)

#include <stdio.h>

   int wa(int *a,int b);

main()
{
   int a[10],ans,i=0,c;
   double ave=0,j;

   while(1)
   {
     scanf("%d",&a[i]);
     c=a[i];
     i++;

     if(c==0){
        exit(1);
     }

     ans=wa(&a[0],i);
     printf("合計%d\n",ans);

     j=i;
     ans=wa(&a[0],i);
     ave=ans/j;
     printf("平均%lf\n",ave);

     printf("計算回数%d回\n",i);
   }
}
int wa(int *a,int b)
{
   int ans=0,i;

   for(i=0;i<b;i++){
     ans+=*(a+i);
   }
   return ans;
}

このようなプログラムで一応自分の期待通りには動いてくれたのですが、
こういう「0」を入力しない限り終わらないプログラムのときに配列を利用すると
どれぐらい領域を取っておくかがわからないんですよ。

今回はa[10]としてますが、結局10しか確保してないから10回しか入力できないかな?
っと思って実行してみますが普通に10回以上でもエラーがでることもなく実行できるんですよね。

これはなぜでしょうか?
私の配列の考え方がまちがっているのでしょうか?

A 回答 (5件)

#1です。



お礼にお書きになったプログラムですが、流れ的にはそんな感じでいいと思います。

ただし、実はrealloc()はかなり重い処理なので、頻繁に呼び出すのはおすすめしません。
ですので、たとえば256個ずつ増やしていく、というように、ある程度まとまった形で拡げていくのがベターです。

ですから、最初のメモリ確保は

#define BLOCKSIZE 256
 int len = BLOCKSIZE ;
 a = (int *)malloc( sizeof(int) * len ) ;
 if(a==NULL){
  printf("メモリ確保失敗\n");
 }

とやったうえで、whileの最後は

 if ( i > len ) {
  len += BLOCKSIZE ;
  a = (int *)realloc( a, sizeof(int) * len ) ;
  if(a==NULL){
   printf("メモリ再確保失敗\n");
  }
 }

というようにすればよいでしょう。
(realloc()の結果も検査しましょう)

なお、本当にはみ出してないか心配になる気持ちはよく分かりますが、こちらもやはり歯止めはありませんので、注意深くプログラムを書かなければいけないのは配列と同じです。どうしても心配ならデバッガを使って観察しながら確認しましょうね。
    • good
    • 0

#1,#4です。



#4に書いた内容に間違いがありました。

 if ( i > len ) {

これは

 if ( i >= len ) {

が正解です。

失礼いたしました。
    • good
    • 0
この回答へのお礼

なるほど。まとめて確保したらいいんですね。
親切に書いていただきありがとうございました。
よくわかりました。

回答ありがとうございました。

お礼日時:2003/08/15 12:21

No.2の方が言われているように、配列宣言を大きくオーバーするとエラーがでるはずです。


エラーが出ないのは「たまたまうまくいった」だけですね(^^;

なので、配列宣言はNo.1の方の挙げられている2つが一般的だと思います。
静的と動的って言います。
大きな配列を用いるときは動的に確保します。
でも、100個,1000個,10000個ぐらいなら静的でも問題ないです。
こういうときは普通

#define MAX 10000

int wa[MAX];

という風に、あらかじめ大きめに宣言をしておいて用います。
もしdefineが分からなかったら、ちょっと調べてみて下さいね~。C関連のどの本にもサイトにも載ってると思います。
    • good
    • 0
この回答へのお礼

回答ありがとうございました。

defineははじめてみました。

お礼日時:2003/08/15 12:15

この場合、配列のa[10]へデータをセットしたときに、配列を突き破りますが、たまたま、エラーが発生しないだけです。

a[10]は、自動変数といって、スタック領域にに確保されますが、a[10]に相当する場所(アドレス)に、他の変数が、割り当てられている場合は、その変数に、値がセットされることになります。一般的に、a[N]として、変数を宣言した場合、a[N-1]までが、正しく動作する範囲になります。a[10]を越えた場合は、見かけ上正しく動作する場合もありますが、だめなときもあります。
例として、以下のプログラムを実行してください。
例1
int a[10];
int i;
for (i=0;i<11;i++){
a[i] = i;
}
例2
int a[10];
int i;
for (i=0;i<1000000000;i++){
a[i] = i;
}
例1は、たぶん見かけ上、正常に終了するでしょう。
例2は、プログラムが異常終了するでしょう。
    • good
    • 0
この回答へのお礼

回答ありがとうございました。

ここら辺のところはきっちり理解しないといけませんね。

お礼日時:2003/08/15 03:10

確かにエラーになりませんが、誤動作し、最悪の場合システムダウンの原因にすらなり得ます。



これはメモリリークといいます。C言語やアセンブラに特有のもので、他の言語では普通起こりません(起こらないように作ってある)。

どういうことかといいますと、Cの場合、配列の添字に範囲を限定する歯止めを設けてないんです。10個分の配列を定義しても、メモリは10個分しかとられないのに、歯止めがないので11個目以降も普通に使えてしまいます。ついでにいうと、実はマイナスも使えます(a[-1]など)。
ところが、11個目以降は実際は配列のために用意した場所ではなく、別のものが入っています。それは、別の変数だったり、プログラムの領域だったり、もしかしたらOSが使っているかも知れません。
そんなところを使っていくということは、知らないうちに別の変数を書き換えてしまったり、プログラムを壊してしまったり。。。
プログラムにこういう問題があると、ハッカーの侵入口にすらなることもあるくらいです。

ですから、配列は定義した範囲をはみ出さないよう、十分注意してプログラムを作らなければならないのです。


さて、どのくらい使うかわからない時はどうするか?
方法はふたつあります。

ひとつは、単純に、なるべく大きめにとっておいて、そのサイズを越えるデータは受け付けないようにしてしまう方法です。これは、簡単ですが、どうしても制限ができてしまいますし、制限を越えることが少なくなるように十分大きく配列をとるとメモリをたくさん使ってしまいます。

ふたつめの方法は、ポインタとmalloc(),realloc()ライブラリ関数を使う方法です。配列の代わりにある程度の大きさのメモリをmalloc()確保して、そこの場所をポインタ変数に入れて覚えておきます。もちろん大きさも変数に入れてとっておきます。そして、途中で足りなくなるようだったら、realloc()で広げることができます。もちろん、余るようなら減らすこともできます。
この方法だと、無駄なく効率よくメモリを使えますし、たくさん必要になってもメモリの許す限りいくらでも使えます。
一般的にはこちらの方法を使うケースが多いと思います。
まあ、これはちょっと難しいかも知れないので、今回は理屈のみの説明にしておきますね。
    • good
    • 0
この回答へのお礼

malloc()とrealloc()を使って考えてみました。

結局これも10以上になったとしてもエラーが出ないで動くのでこのプログラムが正しいかどうか
ちょっと疑問な気もしますがこんな感じでしょうか?


mainの変数宣言の下の行に

a=(int*)malloc(sizeof(int)*10);
if(a==NULL){
   printf("メモリ確保失敗\n");
}


while(1)の最後の行に

if(i>9){
   a=(int*)realloc(a,sizeof(int)*(i+1));
}

while(1)を抜けたあとに

free(a);

を追加して、元々10個とってあった領域が足りなくなったら1ずつ増やしていくというふうに
したつもりなのですが、これは確保した領域をはみだしてないですよね?

エラーにならないのでいまいち自信がもてないんですよね(^^;)

お礼日時:2003/08/15 03:09

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!