• ベストアンサー

配列の疑問。

もうすぐ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回以上でもエラーがでることもなく実行できるんですよね。 これはなぜでしょうか? 私の配列の考え方がまちがっているのでしょうか?

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

  • ベストアンサー
  • shige_70
  • ベストアンサー率17% (168/946)
回答No.4

#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()の結果も検査しましょう) なお、本当にはみ出してないか心配になる気持ちはよく分かりますが、こちらもやはり歯止めはありませんので、注意深くプログラムを書かなければいけないのは配列と同じです。どうしても心配ならデバッガを使って観察しながら確認しましょうね。

その他の回答 (4)

  • shige_70
  • ベストアンサー率17% (168/946)
回答No.5

#1,#4です。 #4に書いた内容に間違いがありました。  if ( i > len ) { これは  if ( i >= len ) { が正解です。 失礼いたしました。

usui323
質問者

お礼

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

回答No.3

No.2の方が言われているように、配列宣言を大きくオーバーするとエラーがでるはずです。 エラーが出ないのは「たまたまうまくいった」だけですね(^^; なので、配列宣言はNo.1の方の挙げられている2つが一般的だと思います。 静的と動的って言います。 大きな配列を用いるときは動的に確保します。 でも、100個,1000個,10000個ぐらいなら静的でも問題ないです。 こういうときは普通 #define MAX 10000 int wa[MAX]; という風に、あらかじめ大きめに宣言をしておいて用います。 もしdefineが分からなかったら、ちょっと調べてみて下さいね~。C関連のどの本にもサイトにも載ってると思います。

usui323
質問者

お礼

回答ありがとうございました。 defineははじめてみました。

  • tatsu99
  • ベストアンサー率52% (391/751)
回答No.2

この場合、配列の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は、プログラムが異常終了するでしょう。

usui323
質問者

お礼

回答ありがとうございました。 ここら辺のところはきっちり理解しないといけませんね。

  • shige_70
  • ベストアンサー率17% (168/946)
回答No.1

確かにエラーになりませんが、誤動作し、最悪の場合システムダウンの原因にすらなり得ます。 これはメモリリークといいます。C言語やアセンブラに特有のもので、他の言語では普通起こりません(起こらないように作ってある)。 どういうことかといいますと、Cの場合、配列の添字に範囲を限定する歯止めを設けてないんです。10個分の配列を定義しても、メモリは10個分しかとられないのに、歯止めがないので11個目以降も普通に使えてしまいます。ついでにいうと、実はマイナスも使えます(a[-1]など)。 ところが、11個目以降は実際は配列のために用意した場所ではなく、別のものが入っています。それは、別の変数だったり、プログラムの領域だったり、もしかしたらOSが使っているかも知れません。 そんなところを使っていくということは、知らないうちに別の変数を書き換えてしまったり、プログラムを壊してしまったり。。。 プログラムにこういう問題があると、ハッカーの侵入口にすらなることもあるくらいです。 ですから、配列は定義した範囲をはみ出さないよう、十分注意してプログラムを作らなければならないのです。 さて、どのくらい使うかわからない時はどうするか? 方法はふたつあります。 ひとつは、単純に、なるべく大きめにとっておいて、そのサイズを越えるデータは受け付けないようにしてしまう方法です。これは、簡単ですが、どうしても制限ができてしまいますし、制限を越えることが少なくなるように十分大きく配列をとるとメモリをたくさん使ってしまいます。 ふたつめの方法は、ポインタとmalloc(),realloc()ライブラリ関数を使う方法です。配列の代わりにある程度の大きさのメモリをmalloc()確保して、そこの場所をポインタ変数に入れて覚えておきます。もちろん大きさも変数に入れてとっておきます。そして、途中で足りなくなるようだったら、realloc()で広げることができます。もちろん、余るようなら減らすこともできます。 この方法だと、無駄なく効率よくメモリを使えますし、たくさん必要になってもメモリの許す限りいくらでも使えます。 一般的にはこちらの方法を使うケースが多いと思います。 まあ、これはちょっと難しいかも知れないので、今回は理屈のみの説明にしておきますね。

usui323
質問者

お礼

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ずつ増やしていくというふうに したつもりなのですが、これは確保した領域をはみだしてないですよね? エラーにならないのでいまいち自信がもてないんですよね(^^;)

関連するQ&A

  • 答えがおかしい

    配列と関数を使って教科ごとの平均点を求めるC言語のプログラムを 作りました。しかし、結果がおかしくいろいろ考えてみたのですが、 よくわからないので質問に出すことにしました。 作ったものは以下です。どうすればよいでしょうか? #include <stdio.h> #define NUMBER 3 /*--- 要素数noの配列の平均値を求める ---*/ double ave_of(int vb[], int no) { int i, sum, ave; for (i=1;i<no; i++) sum+=vb[i]; ave=(double)sum/NUMBER; return(ave); } int main(void) { int i; int a[NUMBER]; int b[NUMBER]; int c[NUMBER]; double ave_a, ave_b, ave_c; printf("%d人の点数を入力してください。\n",NUMBER); for(i=0;i<NUMBER; i++){ printf("[%d番]理科:",i+1); scanf("%d",&a[i]); printf("   数学:"); scanf("%d",&b[i]); printf("   社会:"); scanf("%d",&c[i]); } ave_a=ave_of(a, NUMBER); ave_b=ave_of(b, NUMBER); ave_c=ave_of(c, NUMBER); printf("【理科】平均点:%.1f\n", ave_a); printf("【数学】平均点:%.1f\n", ave_b); printf("【社会】平均点:%.1f\n", ave_c); return(0); } 結果は例えば45.2 というような感じで出したいです。

  • 配列にしたいのですが

    int i,j=0,work=0; int D[]={376,251,240,115}; int A[]={1,2,4,7,13,24,44,81,149,274}; do{ for(i=9;i>=0;i--){ work=D[j]-A[i]; if(work>=0){ D[j]=work; printf("%2d",i); } } printf("\n"); j++; }while(j<=3); これにより、出力 9 7 4 3 0 8 7 4 3 0 8 7 3 1 0 7 5 3 1 0 を得たのですが、これを int e[4][5]={{9,7,4,3,0},{8,7,4,3,0},{8,7,3,1,0},{7,5,3,1,0}}; となるように配列したいのですが、上記のプログラムをどのようにすればいいのでしょうか。

  • プログラム作成が出来ません

    プログラム作成の課題でエラーが出て進めません (1)~(3)までだけなら作成できるのですがどうしても(4) を表示させることができません^^; (1)3~99までの奇数を7*7の2次元配列に読み込み表示 (2)(1)の配列の行と列を入れ替えたものを表示 (3)全要素の平均 (4)要素の中から9の倍数を選び出し表示 添削よろしくお願いします。 #include <stdio.h> main() { int a[7][7]; int i,j,ave,sum; int c; for(i=0; i<7; ++i) { for(j=0; j<7; ++j) { a[i][j] =3+(j*2)+(i*14); } } for(i=0; i<7; ++i) { for(j=0; j<7; ++j) { for(c=0; c<49; ++c) { if((3+(j*2)+(i*14))%9==0) { c=3+(j*2)+(i*14); } } } } sum=0; for(i=0; i<7; ++i) { for(j=0; j<7; ++j) { sum=sum+a[i][j]; } } ave=sum/7/7; for(i=0; i<7; ++i) { for(j=0; j<7; ++j) { printf( "%2d ", a[i][j] ); } printf( "\n" ); } printf("\n"); for(i=0; i<7; ++i) { for(j=0; j<7; ++j) { printf( "%2d ", a[j][i] ); } printf( "\n" ); } printf("\n"); printf("average=%d\n",ave); printf("\n"); for(c=0; c<49; ++c) { printf("%d",c); } } 長々と失礼しました・・・

  • 配列の使い方

    今C言語入門という本で勉強中です。 配列の基本という初歩の部分ですが、教科書通り以下のように記述しているのに、うまく動きません。 環境はMacのXcode ver5で、新しいプロジェクトにてOSXのApplicationのComand Line ToolにてC言語を選択しています。 #include <stdio.h> int main(int argc, const char * argv[]) { // insert code here... int array[5]; int i; int ans; for ( i = 0 ; i <5 ; i++){ printf("number : "); scanf(" %d\n" , &array[i]); } ans = 0; for( i=0; i<4; i++){ printf("%d + " , array[i]); ans += array[i]; } ans += array[4]; printf("%d = %d\n" , array[4] , ans); return 0; } コンソール結果 number : 1 2 number : 3 number : 4 number : 5 number : 6 1 + 2 + 3 + 4 + 5 = 15 Program ended with exit code: 0 数字を1,2,3,4,5,6の順番で入力しました。 おかしな点一つ目:2の入力の際「number:」が出力されない。 おかしな点二つ目:入力項目が一つ多い(array[4]まで、5までの入力のつもりだった)

  • 配列プログラムのバグ

    入力した正の整数を読み込み、小さい順に並べ替える配列のプログラムを作りたいのですが、以下のプログラムで結果がくるってしまいます。 色々な数値を入力してみたところ、入力した数字のうち、どれかが0に なってしまうようなのですが、いまいち理解できません。 どこを修正すればいいのでしょうか? #include<stdio.h> int main(void){ int a[255],b,i=0,j,k; while(i<255){ printf("正整数:\n"); scanf("%d",&a[i]); if(a[i]==0) break; else if(a[i]<0){ printf("正の整数ではありません"); return 0; } i++; } for(k=0;k<=i;k++){ for(j=0;j<=i;j++){ if(a[j]>a[j+1]){ b=a[j]; a[j]=a[j+1]; a[j+1]=b; } } } printf("入力された整数は小さい順に"); for(j=0;j<=i;j++){ printf("%d,",a[j]); } printf("です。"); return 0; }

  • 配列のプログラミングを作ったのですが「

    問題:10問の計算問題を解いて、以下のような成績表を表示するプログラムを作成せよ 例) 第一問 ○ 第二問 × 第三問 × ・・・ 自分が作った解答)#include<stdio.h> #include<stdlib.h> void main() { int a,b,ans,i; int seikai=0; int huseikai=0; int kaitou[10]; srand((unsigned)time(NULL)); printf("暗算\n"); for(i=0;i<10;i++); { printf("%問\n",i+1); a=rand()%10; b=rand()%10; printf("%d+%dは?\n",a,b); scanf_s("%d",&ans); if(ans==a+b) kaitou[i+1]=seikai; else kaitou[i+1]=huseikai; } for(i=0;i<10;i++) { printf("第%d問",i+1); if(kaitou[i+1]==seikai) printf("○\n",seikai); else printf("×\n",huseikai); } } のように作成したのですが、コンパイルは成功しますが思い通りの解答になりません。 どこが間違っているのか指摘してください。

  • 外積のプログラムについて質問があります

    ベクトルの外積のプログラムについて質問があります 環境はリナックスです 問題は2つの実ベクトルa,bをキーボードから入力しその外積を求めるプログラムです i<jである組み合わせに対してai*bj-aj*biを並べたものです 次元nについてはキーボードから入力します wedge内で外積を求める計算をします #include <stdio.h> int *wedge(int *a, int *b, int *c); int main() { int n; int a[100]; int b[100]; int i; int j; printf(" nの値を入力" ); scanf("%d", &n); for(i = 0; i < n; i++){ scanf("%d",&a[i]); } for(i = 0; i < n; i++){ scanf("%d",&b[i]); } for(i = 0; i < n; i++){ printf("a[%d]=%d\n",i, a[i]); } for(i = 0; i < n; i++){ printf("b[%d]=%d\n",i, b[i]); } } int *wedge(int *a, int *b, int *c) { int i; int j; *c = a[i] * b[j] - a[j] * b[i]; if ( i < j ){ *c = a[i] * b[j] - a[j] * b[i]; } else { *c = 0 ; } printf("*c = %d",a[i] * b[j] - a[j] * b[i]); } このようなプログラムを作ったのですが外積を表示させることができません。 修正したプログラムをおしえていただけないでしょうか

  • 配列の問題

    配列の問題です。 n個の要素を持つ一次元配列の値(変数値)をまったく逆に入れ替えるプログラムを作りたいのですが、この場合どのようにして逆を表現すればよいのかわかりません。 (nの値は読み込み、配列は奇数個でも偶数個でも使えるプログラムでなければなりません) 参考書を見ながら作ってみたのですが…だめでした。 プログラム初心者です。アドバイスお願いします。 int main(void) { int i,n; int vc[n]; printf("n個の要素を持つ一次元配列をつくる\n"); printf("nの値を入力してください\n"); scanf("%d",&n); for (i=0;i<n+1;i++) vc[i]=i+1; for (i=0;i<5;i++) printf("vc[%d]=%d\n",i,vc[i]); printf("この配列を逆に入れ替えると\n); return 0; }

  • 2次元配列を使ったC言語の九九表を作りたいんですが、方法がわかりません。

    C言語で、九九の表を作っているのですが2次配列を使わないでの方法なら出来るんですが、 2次配列を使うと出来なくなってします。 下記のように途中まで組んだのですが、どうしてもエラーがでてしまいます。 #include <stdio.h> int main(void) { int i,j,a[9][9]; printf(" "); for(i=1; i<=9; i++) printf("%3d", i); printf("\n"); for(i=0; i<9; i++){ for(j=0; j<9; j++) a[i][j]= {1,2,3,4,5,6,7,8,9},{2,4,6,8,10,12,14,16,18},{3,6,9,12,15,18,21,24,27},{4,8,12,16,20,24,28,32,36},{5,10,15,20,25,30,35,40,45,6,12,18,24,30,36,42,48,54,7,14,21,28,35,42,49,56,63},{8,16,24,32,40,48,56,64,72},{9,18,27,36,45,54,63,72,81} }; for(i=0; i<9; i++){ printf("%3d", i+1); for(j=0; j<9; j++) printf("%3d",a[9][9]); printf("\n"); } return 0; } とやったのですが…以下に書く部分が間違っているようで。 #include <stdio.h> int main(void) { int i,j,a[9][9]; printf(" "); for(i=1; i<=9; i++) printf("%3d", i); printf("\n"); for(i=0; i<9; i++){ for(j=0; j<9; j++) a[i][j]=□ } for(i=0; i<9; i++){ printf("%3d", i+1); for(j=0; j<9; j++) printf("%3d", □); printf("\n"); } return 0; } 色々調べたり、少しずつ変えながら試しているのですが、できません。 どなたかわかるかたいらっしゃいますか。間違いがわかりません… 配列を使用しなくても出来ることは、わかるのですが、配列を使うバージョンでもできるようになりたいんです。 私がしようとおもっているのは、81個分の値を先に計算し、9×9の2次元配列に格納し、次に81個の配列要素の値を出力したいのですが、 間違いと方法がわかる方いらっしゃいませんか。

  • 2次元配列を引数とする関数について

    2次元配列を引数とする関数について 私は今、2次元配列を引数とする関数の表を作るという課題に取り組んでいます。 条件として、int a[数字][数字]={{1,2,3...}}という配列の宣言と同時の初期化は使わず、 関数内で表の値を代入し、値を表示する関数を作り、事実上二つの関数を作るというものです。 私は以下のようなプログラムを作り、動かしましたが、[数字][数字]=********のような本来 あるべき実行結果とは異なる数字の羅列が出てきてしまいました。 ↓ #include <stdio.h> void func(int a[][6]); void fund(int b[4][6]); void main(void) { int a[4][6]; fund(a); func(a); } void func(int a[][6]) { int i,j,b[4][6]; fund(b); for(i=0;i<4;i++)        { for(j=0;j<6;j++) { printf("a[%d][%d]=%d\n",i,j,a[i][j]); printf("\n"); } } } void fund(int b[4][6]) { int i,j; for(i=0;i<4;i++) { for(j=0;j<6;j++)          { scanf("b[%d]*[%d]=%d\n",&i,j,b[i][j]); } } } 本来の実行結果 1 2 3 4 5 6 2 4 6 8 10 12 3 6 9 12 15 18 4 8 12 16 20 24 紙にも書いて何回も見直しましたが、どこがおかしいのかわかりませんでした。 どうすれば良いのでしょうか? 何か良いアドバイスをよろしくお願いします。