構造体の配列とメモリ

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
meron

構造体の配列とメモリ

#1

投稿記事 by meron » 16年前

初めまして、メロンと申します。
大学で一年ほどC言語を勉強し、今は趣味でC++とDXライブラリでシューティングゲームを作っています。

質問なのですが、構造体の配列(要素数が100や1000など非常に多いもの)を宣言するときは
「malloc関数」を使ったほうがよろしいのでしょうか?

現在、とある構造体の配列の数値をいじると、別の構造体の配列の数値がおかしなことになって、正常な動作が見込めない状況です。
ソースコードを見直しても、ループ文で、宣言した配列の要素以上の場所にアクセスしていたり、といったミスはありませんでした。
それどころか、処理中の配列とはまったく別の配列の要素が同時に、かつ勝手に置き換わり、それでエラーを起こしていることが分かりました。

おそらく、宣言時に確保したメモリに関する問題だと思うのですが、このような事態はよくあることなのでしょうか?

あるとすれば、一般的にどのような解決方法があるのでしょうか?

色々と探し回って「malloc関数」を使えばうまく行くのではないかと思うのですが、
もし、「malloc関数」を使う場合、構造体の配列をどのように確保すればよいのかがよく分かりません。

ソースコードを載せようとは思ったのですが、問題となる配列をまったくいじらなければ正常に動作しますし、
逆に問題となる配列の要素を一つでもいじると即座に他の配列の数値が変化してしまうので、
やはり、メモリとその確保に関する問題だと思います。
短いサンプルプログラムを作ってみたのですが、それだと当然のように正常に動作します。

結論として、疑問は、

1、先述のようなエラーはよくあることなのか? あるとすればどのような解決法があるのか?

2、「malloc関数」で構造体の配列を宣言するにはどのようにしたらいいのか?

3、メモリの確保の問題でなければ、どのような問題であると考えられるか?

の三つになります。

同じような質問が過去にあったならば、こちらの無礼をお詫びいたします。
けれども、この問題のために製作が完全にストップしている状況です。
なんとかお力をお貸しいただければ幸いです。

たかぎ

Re:構造体の配列とメモリ

#2

投稿記事 by たかぎ » 16年前

ソースを貼ってください。

meron

Re:構造体の配列とメモリ

#3

投稿記事 by meron » 16年前

プログラミングに関して質問するのが初めてなもので、無礼があったことをお詫びいたします。

短いサンプルプログラムのほうですか?
それだとエラーが起きませんし、元のソースコードをすべて貼ることはその量から考えて、物理的に不可能かと思います。

実際に、一週間ほどソースコードを見直しましたが、そこに問題は見受けられませんでした。
変数や構造体の宣言に関しては、こちらの「竜神録プログラミングの館」で解説されている通りに作成してあります。

それでもソースコードを載せなければならないとしたら、申し訳ありませんが、どのような範囲で、どのような処理のソースコードが必要なのかを明示していただけると助かります。

ゲームとしてはほぼ完成している以上、そのソースコードのすべてはかなりの量になっていると思います。
少なくとも、コピー、ペーストで貼り付けられません。

その量をすべてお渡しできる他の手段があれば、恐縮ですが、その方法を教えていただけないでしょうか?

yu

Re:構造体の配列とメモリ

#4

投稿記事 by yu » 16年前

アップローダーを使ってみては如何でしょうか?

検索すればたくさんでてきますよ。

SooA

Re:構造体の配列とメモリ

#5

投稿記事 by SooA » 16年前

プロジェクトを丸ごとコピーして
下記ファイルに該当するものだけを残し、
中間ファイルやゲームデータは全て削除、
その状態のものに圧縮をかければ
かなり作りこんだ内容でも数百 kb 程度になります。

VC++2008の場合
・プロジェクト名.sln
・プロジェクト名.vcproj
・各ソースファイル( c, cpp, h )

BCC の場合はソースのみでもかまいません。

toyo

Re:構造体の配列とメモリ

#6

投稿記事 by toyo » 16年前

1、先述のようなエラーはよくあることなのか? あるとすればどのような解決法があるのか?
勝手に書き換わることは絶対にないです。

2、「malloc関数」で構造体の配列を宣言するにはどのようにしたらいいのか?
構造体のポインタ = malloc(構造体のサイズ * 配列の個数); // C++ならmallocの戻り値を構造体のポインタ型にキャスト

3、メモリの確保の問題でなければ、どのような問題であると考えられるか?
ソース見ないとわからないですね。(見てもわからないかもしれませんが)。

思ったとおりに動かないのをバグといってこれはよくあることです。
解決法はデバッガを駆使して原因を調べます。

Mist

Re:構造体の配列とメモリ

#7

投稿記事 by Mist » 16年前

症状から察するにメモリ破壊が原因だと思います。
配列をmallocで確保すればメモリマッピングが変わるので現象が起きなくなる可能性はありますが、別のエリアが破壊される可能性もあるわけで根本的な解決にはならないと思います。

メモリ破壊はありがちなミスではあるけども「こうすれば解決する」という「一般的な解決策がない問題」ですので、一部のソースだけ見ても解決はしないでしょうね。
ましてや、meronさんのプログラム構成が全く分からないこちらからこの部分を見せてほしいというのは不可能です。
プロジェクト全体をアップされるのが一番早いかと思います。

meron

Re:構造体の配列とメモリ

#8

投稿記事 by meron » 16年前

有力な情報をありがとうございます。

アップローダーの件は、もう一度ソースコードを見直し、他の原因が考えられないことを確認してから検討してみたいと思います。

しばらく(1~2日か、あるいは更に一週間程度)、時間を頂けますか?

質問をしたのは自分である上に勝手なお願いをすることが皆さんにとってご迷惑になることは承知しておりますが、
とにかく他の原因が考えられないか、再調査してみようと思います。
その上で自分の手には負えないと思ったら、素直にアップロードして皆さんのご協力をたまわりたいと思います。

また、アップロードするにも、ソースコード製作に関して最低限度のコメントしか入れてないので、大変見にくいものとなっております。
その調整を含めて、もう一度、ソースコードを見直す時間を頂きたいのです。

non

Re:構造体の配列とメモリ

#9

投稿記事 by non » 16年前

>質問なのですが、構造体の配列(要素数が100や1000など非常に多いもの)を宣言するときは
>「malloc関数」を使ったほうがよろしいのでしょうか?

要素数が固定なら、mallocを使わない方が簡単です。まして、要素数の最大がが100や1000など少ない場合は
最大数の配列を用意した方が簡単と思います。ただし、構造体の1配列がむちゃくちゃ大きいなら別ですが。

おそらく、配列の要素数を超えた場所に書き込んでいると思います。
INDEX値(配列の添え字の計算式)を見直してみましょう。

たかぎ

Re:構造体の配列とメモリ

#10

投稿記事 by たかぎ » 16年前

いっそのこと、単純な配列ではなくstd::vectorにしてはどうでしょうか?
そして、[/url]演算子ではなくatメンバ関数を使えば、範囲を超えてアクセスしようとすると例外が送出されます。
パフォーマンスは落ちますが、動かないよりはましでしょう。

たかぎ

Re:構造体の配列とメモリ

#11

投稿記事 by たかぎ » 16年前

ちょっと言葉足らずでした。
std::vectorとatメンバ関数に書き換えれば動くようになるという意味ではありません。
おかしな箇所を特定しやすいというだけです。

meron

Re:構造体の配列とメモリ

#12

投稿記事 by meron » 16年前

>>non氏

解答ありがとうございます。

つまり、
int i;
int num[100];

for(i = 0 ; i <= 100 ; i++){
    num = 0;
}


このようなことですよね?
これだと、num[0]~num[99]だけでなく、存在しないnum[100]に代入してしまいますから。

その可能性を考慮してループ文を中心に見ているのですが、今のところ見つかりません。


>>たかぎ氏

なるほど、検出するためにそういう方法も考えられるわけですか!
盲点というか、まったく知りませんでした……検討してみます。


一応、問題となる構造体とその配列の宣言と、挿入するとエラーになるソースコードを載せておきます。

/* 変数の宣言、各処理においてexternで呼び出します */

#define MAX_BURST_NUM 2000	//同時に爆発を起こせる最大数
#define MAX_SHOT_NUM 20 //画面上に表示する弾幕の最大数

typedef struct{	//爆発構造体
	int flag,counter,pattern,img;
	double x,y,range,size,power;
} Burst_t;

Burst_t burst[MAX_BURST_NUM];

typedef struct{	//レーザーの子構造体
	int flag;
	double x,y,range;
} Box_ele_t;

typedef struct{	//レーザー(疑似長方形)判定用の構造体
	int flag,counter,pow_time,end_time,img,color,state;
	double start_height,base_angle,base_height,base_range,base_x,base_y;
	Box_ele_t child[200];
} Box_t ;

Box_t e_lazer[MAX_SHOT_NUM];


--------- 以下、処理に加えるとエラーになる文 ------------

/* t は敵のショットが始まってからのカウンタを示します */

	if(t == 60){
		for(int m=0;m < MAX_SHOT_NUM;m++){
			e_lazer[m].flag = 1;
			e_lazer[m].base_angle = Boss_atan();
			e_lazer[m].base_x = boss.x;
			e_lazer[m].base_y = boss.y;
			e_lazer[m].pow_time = 60;
			e_lazer[m].start_height = 20;
			e_lazer[m].base_height = 100;
			e_lazer[m].base_range = 50;
			e_lazer[m].counter = 0;
		}
	}


この構造体に関する処理は別のところでも行っており、現在、それにバグがないか調べています。

これを実行すると、アクセスしたのは e_lazer[/url] のメンバーなのに、爆発構造体配列の値が変わってしまいます。

呼びだしている関数は「double Boss_atan(void)」のみで、これはボスキャラから見た自機との角度を返します。
この関数は、math.hをincludeして、以下のような処理を行っています。

double Boss_atan(void){	//ボスと自機の角度
	return(atan2(Player_1.y -  boss.y,Player_1.x - boss.x));
}


長文を失礼しました。
ちゃんと字下げできたかどうか不安です。

仕組みは、Box_ele_t の構造体配列で指定したrangeを半径とする円をボスの射撃方向に一定距離を挟んで固定配置し、それと自機とのヒットチェックをすることで擬似的に長方形を表現するというものです。

これから別の用事があるので今日はもう返事はできませんが、なんとか早いうちにすべてのソースコードのアップロードができるよう尽力いたします。

ここまでで何かミスがあれば、自分の愚かさを呪うほかありません……。

たかぎ

Re:構造体の配列とメモリ

#13

投稿記事 by たかぎ » 16年前

> なるほど、検出するためにそういう方法も考えられるわけですか!

不具合の検出に限らず、配列を動的に割付けるのであれば、mallocやnewを使うよりstd::vectorを使う方が得策です。
生兵法でmallocやnewを使うと、きっとメモリリークや二重解放で悩む羽目になります。

non

Re:構造体の配列とメモリ

#14

投稿記事 by non » 16年前

誤りはこの範囲にはないようです。
Player_1やbossの構造体は不明ですが。たぶん、影響はないでしょう。

あのif文のブロックを加えたら他の配列を破壊するのですね。
とりあえず、
中のfor文をm=0だけ行われるようにして、破壊されますでしょうか?
破壊されるのであれば、
代入文を1行ずつ実行し、どれで破壊されているか調べてください。

meron

Re:構造体の配列とメモリ

#15

投稿記事 by meron » 16年前

返事が遅れて申し訳ありません。

>不具合の検出に限らず、配列を動的に割付けるのであれば、mallocやnewを使うよりstd::vectorを使う方が得策です。
>生兵法でmallocやnewを使うと、きっとメモリリークや二重解放で悩む羽目になります。

そうなのですか!
参考にさせていただきます!


>代入文を1行ずつ実行し、どれで破壊されているか調べてください。

e_lazer[8].flag = 1; の代入を行ったときからエラーで止まります。
しかも、そのエラーは、爆発用構造体とは違う、別の変数の値が置き換わったためのものでした。

「game.exe の 0x00578c47 でハンドルされていない例外が発生しました: 0xC0000005: 場所 0x01ee5ee0 を読み込み中にアクセス違反が発生しました。」

デバッグすると上記の文章が表示されます。
確認してみると、値の変化により、関数ポインタの設定していない領域を読み込んだようです。

あと、e_lazer[/url]に関する他の処理(前述の代入文・ループ文を除く別の処理)をソースコードから除いて実行したところ、やはり同様のバグでプログラムの動作が停止しました。

toyo

Re:構造体の配列とメモリ

#16

投稿記事 by toyo » 16年前

その構造体配列が自動変数(関数内でstaticなしで宣言している)であればstaticをつけて試してみては

meron

Re:構造体の配列とメモリ

#17

投稿記事 by meron » 16年前

構造体配列及び、メインで使う変数はほぼすべて「竜神録プログラミングの館」等の解説通り、ヘッダファイルで宣言し、それをextern宣言したヘッダファイルをincludeして使ってます。

これでは駄目でしょうか…?

non

Re:構造体の配列とメモリ

#18

投稿記事 by non » 16年前

>これでは駄目でしょうか…?
駄目なことはありません。
toyoさんがおっしゃているのは、たぶんメモリがどこに確保されるかによって、破壊される変数が
変われば、場所を特定することの手助けにならないかということではないでしょうか?

meron

Re:構造体の配列とメモリ

#19

投稿記事 by meron » 16年前

なるほど……。

でも、具体的に何をすればよろしいのかが分かりません。

破壊される変数はばらばらです…。
異常が見つかったのは今のところ、burst[174]~burst[175]、及びburst[1675]とその周辺、LoadGraphで読み込んだ背景を保存しているimg_back(これは、int img_back[2]; で宣言)です。
こんなにも破壊される箇所がばらばらになるのも、そのメモリ破壊というバグの症状の一つなのでしょうか?

Mist

Re:構造体の配列とメモリ

#20

投稿記事 by Mist » 16年前

> そのメモリ破壊というバグの症状の一つなのでしょうか?

そうですね、それもかなり重症とみるべきです。
起こしてしまった場合の調査はプログラムが大きければ大きいほど困難を極めます。

ループ処理でのバッファオーバランで破壊している場合は配列の途中だけ破壊ということはないように思いますので、ループ箇所を調べているだけでは見つけられないのではないでしょうか。
配列にアクセスする際に何らかの計算でインデックスを求めているようなことをしている場合、計算間違いで計算結果がとんでもない値になっていたりする場合も原因として考えられます。
(計算に使用する変数の初期化が不十分で不定値で計算しているとか)
可能性は挙げればきりがないので、プログラム全体を見ない限り正確な原因はわかりません。


開発環境が書かれていませんけど、VC系であれば調査の仕方を書きます。(見つけられるとは限らないけど)

1.プロジェクト→(プロジェクト名)のプロパティを選択
2.構成プロパティ→リンカ→デバッグを選択
3.マップファイルの作成を「はい(/MAP)」に変更
4.マップファイル名に適当なファイル名を入力

この状態でビルドするとメモリマップファイル(4で入力したファイル名)が作成されます。
メモリマップファイルにはプログラム実行時のメモリ配置アドレスが出力されます。
ファイル内を破壊される変数名で検索し、その変数よりも上に配置されている配列変数の使用箇所を重点的に調べてみてください。

meron

Re:構造体の配列とメモリ

#21

投稿記事 by meron » 16年前

開発環境で、どのように書けばいいのかよく分からないのですが……。
一応、「Windows Vista Home Premium」、「Visual Studio 2005」で製作しています。

0004:0023fed0 ?e_lazer@@3PAUBox_t@@A 00abaed0 Main.obj
0004:00260ff8 ?burst@@3PAUBurst_t@@A 00adbff8 Main.obj

Mist氏の助言を元にメモリマップファイルというものを作成して調べたところ、こんな文章が出てきました。
左の「0023fed0」「00260ff8」っていうのが、メモリ上のアドレスということですか?
つまり、そのアドレスが同じ場所を使っていたりしないかどうかを探せばよいのでしょうか?
こんな機能初めて使ったので、よく分かりません。
とりあえず、上記の項目の周辺を載せておきます。

0004:0023fea8 ?img_status@@3PAHA 00abaea8 Main.obj
0004:0023fecc ?novel_flag@@3HA 00abaecc Main.obj
0004:0023fed0 ?e_lazer@@3PAUBox_t@@A 00abaed0 Main.obj
0004:0025fd70 ?back_blend@@3HA 00adad70 Main.obj
0004:0025fd74 ?area@@3UArea@@A 00adad74 Main.obj
0004:0025fda8 ?cnt_slow@@3NA 00adada8 Main.obj
0004:0025fdb0 ?game_mode@@3HA 00adadb0 Main.obj
0004:0025fdb4 ?bgm_sel@@3HA 00adadb4 Main.obj
0004:0025fdb8 ?f_homing@@3PAUHoming_shot_t@@A 00adadb8 Main.obj
0004:00260ff8 ?burst@@3PAUBurst_t@@A 00adbff8 Main.obj
0004:0027c578 ?img_lazer@@3PAY01HA 00af7578 Main.obj
0004:0027c588 ?counter@@3HA 00af7588 Main.obj

ソースコード全文はもう少し待ってほしいところです。

Mist

Re:構造体の配列とメモリ

#22

投稿記事 by Mist » 16年前

> 左の「0023fed0」「00260ff8」っていうのが、メモリ上のアドレスということですか?
そう考えてもらって差し支えありません。

> つまり、そのアドレスが同じ場所を使っていたりしないかどうかを探せばよいのでしょうか?
違います。
アドレスを決定するのはコンパイラの仕事なので同じになることはありません。

> burst[174]~burst[175]

この辺が破壊されているということはメモリアドレスでは

0x00260ff8 + sizeof(Burst_t) * 174 = 0x00263608 // sizeof(Burst_t)が56byteの場合

のあたりが破壊されていることになります。
で、ここに破壊された時に書き込まれているデータ内容から破壊しているやつを探すということです。

例えばe_lazerへのライトアクセスでburstを破壊していると仮定した場合、

(0x00263608 - 0x0023fed0) / sizeof(Box_t) = 25.6 // sizeof(Box_t)が5676byteの場合

e_lazer[25]でchildの118か119あたりにライトアクセスした場合にburst[174]を破壊することになるとわかります。
(構造体サイズは手計算なので間違ってるかも(^^;、MAX_BURST_NUMが2000、MAX_SHOT_NUMが23ぐらいで定義されているならほぼあってると思うけど)

まぁ、闇君にソース眺めるよりはいくらか絞り込みが出来るというものです。

meron

Re:構造体の配列とメモリ

#23

投稿記事 by meron » 16年前

なるほど……。
私にとってはなんだか難しい話ですが、手探りでなんとか調べてみようと思います。

たかぎ

Re:構造体の配列とメモリ

#24

投稿記事 by たかぎ » 16年前

破壊されるところが分かっているのであれば、適当に何箇所かにブレークポイントを貼って実行すれば、大まかに破壊が起きている場所が絞り込めるはずです。
その上で、破壊される直前のブレークポイントからステップ実行すれば、直接の原因は比較的簡単に特定できます。
ただし、実時間のタイミングに依存するような場合は、ステップ実行では再現できないので、もう少し面倒になります。

いずれにせよ、ソース全文の掲載が一番近道であることは間違いありません。

meron

Re:構造体の配列とメモリ

#25

投稿記事 by meron » 16年前

ソースコードを「どっとうpろだ.org」にアップロードいたしました。

番号は「23152」、コメント「お願いします。」、容量は「228kb」、ファイル名は「game.zip」
ダウンロードパスは「meron」です。

汚い上に無駄に長く、コメントも僅かなものですが、どうかよろしくお願いします。

何か必要なことがあれば仰ってください。
実現可能な範囲であれば、最大限、その要望に応えるつもりです。

ただ、画像や音声などのファイルを抜かしているので、そのままでは実行してもエラーで中止すると思うのですが、それでもよろしいのでしょうか?

たかぎ

Re:構造体の配列とメモリ

#26

投稿記事 by たかぎ » 16年前

> ソースコードを「どっとうpろだ.org」にアップロードいたしました。

リンクぐらいは貼りましょう。
普通は、わざわざ探してまで調べませんよ。

meron

Re:構造体の配列とメモリ

#27

投稿記事 by meron » 16年前

こちらの非常識と無礼をお詫びいたします。
申し訳ありません。

こちらです。

→ http://www.dotup.org/uploda/www.dotup.org23152.zip.html

SooA

Re:構造体の配列とメモリ

#28

投稿記事 by SooA » 16年前

その後の記事も読ませてもらいましたが、
今回の meronさんのケースだとソースだけよりも
データをそろえて実際に動作確認できる状況であるほうが
望ましく問題解決が早くなると思います。

私が余計なこと書いちゃいましたね。
(ココにUPできるサイズにする方法を書いたので。)
できれば動作に必要な他データの補間もお願いします。

meron

Re:構造体の配列とメモリ

#29

投稿記事 by meron » 16年前

返事が遅れて申し訳ないです。

プレイに必要な画像や音声といったデータも同封しました。
パスは同じく「meron」です。
e_lazerに関する処理を行っている場所や、その他伝えたいことなどの情報は同封の「read me」テキストに記載しておきました。

よろしくお願いします。

→ http://www.dotup.org/uploda/www.dotup.org23672.zip.html

前回アップロードした、ソースコードだけのものはしばらく残しておきます。
ただ、実行する際の不具合をなくすために、今回のものには一部に変更を施してありますので、ご注意ください。

……ところで、直接アドレスを載せてしまってもよろしいのでしょうか?
もう後の祭りなのですが、掲示板というものを使う機会がめったにないもので、分からないのです。
今回、ここを利用させていただいたのも、ゲームの製作初期、こちらのサイトを利用させて頂いたからという理由なので……。

SooA

Re:構造体の配列とメモリ

#30

投稿記事 by SooA » 16年前

typedef struct{	//レーザー判定
	// フラグ、カウンタ、ヒット開始、ヒット終了、画像、色、状態
	int flag,counter,pow_time,end_time,img,color,state;
	// ヒット開始距離、角度、ヒット終了距離、ヒット幅、基準x、基準y
	double start_height,base_angle,base_height,base_range,base_x,base_y;
	Box_ele_t child[500]; [color=#bbffbb>// ← GlobalValue.h では 200 になっている[/color]
	Lphy_t lphy;
} Box_t ;

Extern.h と GlobalValue.h で
Box_ele_t child[/url]; の確保している数が違っています。

龍神録5章くらいを参考にファイルを一つにまとめると
こういったバグの回避になると思いますよ。

meron

Re:構造体の配列とメモリ

#31

投稿記事 by meron » 16年前

な、なんと……!!

コピーペーストだから写し間違いはないと思ってextern.hはまったく眼中にありませんでした。
おそらく、その後に微調整したとき、extern.hに反映させるのを忘れてしまったのでしょう。

小さなミスが大きなバグに繋がるとは何度も大学で聞かされていたのですが、実際に自分がそうなると身にしみて理解できますね……。

こちらでも正常な動作を確認しました。


> 龍神録5章くらいを参考にファイルを一つにまとめると
> こういったバグの回避になると思いますよ。

アドバイス、ありがとうございます!


皆様のご協力に深く感謝いたします。

私一人では、extern.hに対する先入観を捨て切れず、きっと諦めて投げ出していたことでしょう。
半月以上かけても発見できなかったのですから、それは確実であると言えます。
こんな小さなミスに半月も翻弄されていたとは、全身から力が抜けるようであります。

このゲームは、趣味とはいえ、私が生まれて初めてプログラミングしたゲームであります。
この先、何があったとしてもこのゲームを製作したこと、このゲームを製作する上でお世話になった方々のこと、そして親切にもバグの原因解明のため協力してくださった皆様のことは、決して忘れません。

本当にありがとうございました。

そして、もしかしたら、これから先、またこの掲示板と皆様のお世話になる機会があるかもしれません。
そのときはまた、暖かく迎えてくだされば幸いです。

それでは、今回のところはこれで失礼しようと思います。

皆様、本当にありがとうございました。

閉鎖

“C言語何でも質問掲示板” へ戻る