ページ 11

配列について

Posted: 2008年9月12日(金) 01:33
by
こんばんは、早速ですが質問させて頂きたい事があります。

現在課題としてコマンドプロンプト上で動くリバーシを作っているのですが、その作業の最中に配列と構造体で引っかかってしまいました。
質問内容なのですが、
#include <stdio.h>

typedef struct{
		int flag;
	}STONE;	
	
	STONE black[8][8];	
	STONE white[8][8];	

int main(void){
	black[3][4].flag=1;
	black[4][3].flag=1;
	white[3][3].flag=1;
	white[4][4].flag=1;
	
	for(int s=0; s<8; s++){
		for(int j=0; j<16; j++){
			printf("   %d",black[j].flag);
		}
		printf("\n");
	}

	printf("\n");

	for(int s=0; s<8; s++){
		for(int j=0; j<16; j++){
			printf("   %d",white[j].flag);
		}
		printf("\n");
	}
return (0);
}


まず仮にこのようなプログラムがあるとします。
このプログラムを実行した結果が以下になります(目がチカチカしそうな内容ですいません...)
・結果
   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
   0   0   0   0   0   0   0   0   0   0   0   0   1   0   0   0
   0   0   0   0   1   0   0   0   0   0   0   1   0   0   0   0
   0   0   0   1   0   0   0   0   0   0   0   0   0   0   0   0
   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0

   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
   0   0   0   0   0   0   0   0   0   0   0   1   0   0   0   0
   0   0   0   1   0   0   0   0   0   0   0   0   1   0   0   0
   0   0   0   0   1   0   0   0   0   0   0   0   0   0   0   0
   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0


この結果を見て、まず疑問に思ったのが配列の要素数が[8][8]にも関わらず、[8][16]まで表示できているという点です。
今までの勝手な認識では、配列というのは宣言した要素数までしか使用出来ず、仮にその要素数を超えるような要素に値が代入・あるいは表示される場合はエラーか警告を返してくれるのだと思っていました。
しかしこの結果を見る限りそうでもないようですので、仕様(?)ということでしょうか……?

それと、
black[3][4].flag=1;
black[4][3].flag=1;
white[3][3].flag=1;
white[4][4].flag=1;
という具合に1を代入しているのですが、結果を見ると変な所にまで1が代入されてしまっています。
憶測ですがアドレスか何かの関係かな? と適当に思ってはみているのですが、よければ解説をお願いしたいです。

最後にもう一つだけ。。。
まず、以下のような構造体と配列があるとします。
1.構造体
typedef struct{
	int flag;
}STONE;		
	STONE black[8][8];	
2.配列
int black2[8][8];


配列の場合、初期化されていないので各要素にとんでもない値が代入されているようですが、
構造体の配列(?)であるblackは最初から全て0で初期化されているということを知りました。
ですが、仮に0で初期化したい場合、やはり初期状態で勝手に0が代入されていると言っても
やはり初期化の処理はしておいた方がいいのでしょうか?
こちらのサイトのゲームプログラミングの館などでは初期化を行っているので、
恐らくした方がいいとは思うのですが、一応確認をということで……。
また、構造体の配列の場合0で初期化されているのは仕様というかそんな感じなのでしょうか?

分かりにくい質問かもしれませんが、何かアドバイス頂ければ幸いです。

コンパイラ:BorlandC++ Compiler
開発環境:CPad for Borland C++Compiler
OS:Windows XP

Re:配列(あるいは構造体)について

Posted: 2008年9月12日(金) 01:58
by 管理人
それは
STONE black[8][8];
の次に連続したアドレス上に
STONE white[8][8];
が確保されたためでしょう。
int a[10];
で用意した配列に
a[20]=10;
とか用意していない要素に代入してもコンパイルエラーでは教えてくれません。
こういうのをオーバーフローといってバグの原因の一つとして問題とされています。
[ ]の中の数字は単純にそのsizeof(int)バイト目をさしますから、
そこが確保された領域かどうかわかりません。

オーバーフローは非常にやっかいで、例えば
int a[10];
int b=1;
で用意した場合、a[10]=2;とすれば、
bがaに連続したアドレス上に確保された場合b=2;をしたことと同じ意味になりますから
bに2を代入した覚えが無いのに、いつの間にか2が入ってるって事もあります。

#define MAX 10

みたいに定義を用いて配列要素を指定したり、for文の上限を指定したりするのは
変更が楽だというほかにこのようなバグの発生をふせぐ目的もあると思います。

なので、今回[8][16]まで表示できたのではなく、[16][8]まで表示できたという方が感覚的に正しいかもしれません。
また、2次元配列は連続したアドレス上に確保されますから、
こんな事にもなります。
#include <stdio.h>

char str[2][4];

int main( void ){
	str[0][0]='y';
	str[0][1]='e';
	str[1][0]='s';
	str[1][1]='!';
	int i,j;
	for(j=0;j<8;j++){
		putchar(str[0][j]);
	}
}

実行結果

ye  s!

本来2と4で2重ループでわけないといけないはずですが、これで表示できてしまいます。
また、グローバル変数は自動的に中身が0で初期化されますが、
ローカル変数はされませんからこんな事に。

#include <stdio.h>


int main( void ){
	char str[2][4];
	str[0][0]='y';
	str[0][1]='e';
	str[1][0]='s';
	str[1][1]='!';
	int i,j;
	for(j=0;j<8;j++){
		putchar(str[0][j]);
	}
}

実行結果

yeフフs!フフ
 
 

Re:配列(あるいは構造体)について

Posted: 2008年9月12日(金) 02:05
by 管理人
上の「フフ」という部分は、全角じゃなく、半角です。
文字化け対策の為、掲示板の仕様で自動的に全角に置換されたようです。

また、今回はアドレスと構造体の話が出てきたので、
この辺も押さえておくと良いでしょう。

「構造体 アライメント」でググる。
http://www.google.co.jp/search?hl=ja&q= ... 0%E3%81%82
今、short intが2バイト、intが4バイトだとします。
このshort intとintのセットの構造体は何バイトになるでしょう?
2+4だから6バイトになると思いますが、実は8バイトです。
実際に確かめてみましょう。

#include <stdio.h>

typedef struct{
  short int a;
  int b;
}ch_t;

int main( void ){
	printf("short int = %d\n",sizeof(short int));
	printf("int = %d\n",sizeof(int));
	printf("ch_t = %d\n",sizeof(ch_t));
}

実行結果

short int = 2
int = 4
ch_t = 8

Re:配列について

Posted: 2008年9月12日(金) 09:50
by
返信ありがとうございます。

なるほど、これがオーバーフローですか。
用意してもいない要素に代入されるとなかなかどこで不具合が起きているのか発見出来なそうですね。
現在作っているリバーシで、途中から変な所に値が代入されるためこのような質問をしましたが、オーバーフローが原因だと分かったので改めてfor文(特に入れ子の部分)辺りと定義の活用方法を考えてみようと思います。

「構造体 アライメント」
と調べて上から2つほどサイトを覗いてみました。
これは全く知りませんでしたね……そもそもint型やdouble型の大きさというのも今まであまり意識せずに使ってきました。このパディングという空間は、必要ではあるのでしょうが、たかが数バイトといえど無駄にメモリを消費してしまっている……とも思いました。ホントにわずか数バイト、それこそ今日市場に出回っているパソコンのメモリ容量なら意識しなくてもいいくらい小さな数字だとは思いますが。

リバーシ作成を通してまだまだCの基礎がなってないと痛感しました。
当初の疑問は解消したので一応解決マークを付けておきます。