- ベストアンサー
構造体メンバの個数
気になって質問させていただいたのですが、構造体のメンバが例えば10個とかあったりしたら、何かプログラムに異常が起きたりするものでしょうか? 現在作っているプログラム中で、本来入っているはずの数値(文字列)が、微妙にでたらめなものに変わってしまいます。(実際に起きたのが、int aの値は10なのに、46543とか。char name[20] = "ジタン" が、"ジタnp"とか) たまに、こういったことがプログラムを組んでいると起こるので、何が起こっているのか分かる方、御教授お願いします。
- みんなの回答 (26)
- 専門家の回答
質問者が選んだベストアンサー
後編: // 味方の攻撃番号を取得 static int turnInput( int check[] ) { int turn; for ( ; ; ){ printf( "誰が攻撃しますか(1.人物A 2.人物B 3.人物C 4.人物D)--->" ); scanf( "%d", &turn ); // 1~4以外ならプログラムの終了 if ( (turn < 1) || (turn > MAX_MEMBER) ){ exit( 1 ); // 強制終了 } // 0~3に変換 turn--; // 戦闘不能のチェック if ( Member[turn].hp <= 0 ){ printf( "戦闘不能です。\n" ); continue; } // 攻撃済みのチェック if ( check[turn] ){ printf( "/*-----%sのターンは終了しています-----*/\n", Member[turn].name ); continue; } // 攻撃番号を返す check[ turn ] = 1; // 攻撃済みフラグのセット printf( "\n" ); return turn; } } // 味方の攻撃ルーチン extern int attackMember( int members ) { int check[ MAX_MEMBER ] = { 0 }; int loop; dispStatus(); for ( loop = 1 ; loop <= members ; loop++ ){ printf( "味方の攻撃ターン(%d/%d)\n", loop, members ); switch ( turnInput(check) ){ case 0: chaAB( &Member[0], &Enemy[0] ); printf( "/*-----------------人物Aのターン終了------------------*/\n\n" ); break; case 1: chaAB( &Member[1], &Enemy[0] ); printf( "/*-----------------人物Bのターン終了------------------*/\n\n" ); break; case 2: chaCD( &Member[2], &Enemy[0] ); printf( "/*-----------------人物Cのターン終了------------------*/\n\n" ); break; case 3: chaCD( &Member[3], &Enemy[0] ); printf( "/*-----------------人物Dのターン終了------------------*/\n\n" ); break; default: loop = members; // 抜ける設定 break; } } return checkEnemy(); // 1:生きている 0:死す } // 敵の攻撃ルーチン extern int attackEnemy( void ) { int i, n1, s1, s2, s3, s4; dispStatus(); printf( "敵の攻撃ターン\n" ); for ( i = 0 ; i < MAX_MEMBER ; i++ ){ status *p = &Member[i]; srand( (unsigned)time(NULL) + rand() ); n1 = RAND( 1000, 1000 ); // ダメージ量(1000~1999) s1 = RAND( 100, 1 ); // [毒]状態(1-100) s2 = RAND( 100, 1 ); // [沈黙]状態(1-100) s3 = RAND( 100, 1 ); // [暗闇]状態(1-100) s4 = RAND( 100, 1 ); // [混乱]状態(1-100) if ( (p->hp -= n1) < 0 ){ p->hp = 0; } p->st[ 0 ] |= s1 % 2; // [毒]状態の設定 p->st[ 1 ] |= s2 % 2; // [沈黙]状態の設定 p->st[ 2 ] |= s3 % 2; // [暗闇]状態の設定 p->st[ 3 ] |= s4 % 2; // [混乱]状態の設定 printf( "%sの攻撃--->%sに%dのダメージを与えた。\n", Enemy[0].name, p->name, n1 ); } printf( "\n" ); return checkMember(); // 1:生きている 0:全滅 } // 人物A/Bの攻撃(勇者,戦士) extern void chaAB( status *member, status *enemy ) { int n1; // 初期化 srand( (unsigned)time(NULL) + rand() ); n1 = RAND( 1000, 1000 ); // ダメージ量(1000~1999) if ( (enemy->hp -= n1) < 0 ){ enemy->hp = 0; } printf( "%sの攻撃--->%sに%dのダメージを与えた。\n\n", member->name, enemy->name, n1 ); } // 人物C/Dの攻撃(魔法使い) extern void chaCD( status *member, status *enemy ) { int n1, select; // 初期化 srand( (unsigned)time(NULL) + rand() ); do { printf( "1.魔法1 2.魔法2--->" ); scanf( "%d", &select ); switch ( select ){ case 1: if ( member->mp < 20 ){ printf( "MPが足りません。\n" ); select = -1; break; } n1 = RAND( 1000, 1000 ); // ダメージ量(1000~1999) if ( (enemy->hp -= n1) < 0 ){ enemy->hp = 0; } member->mp -= 20; printf( "%sの魔法攻撃--->%sに%dのダメージを与えた。\n\n", member->name, enemy->name, n1 ); break; case 2: n1 = RAND( 50, 50 ); // MP吸収量(50~99) if ( enemy->mp < n1 ){ n1 = enemy->mp; } member->mp += n1; enemy->mp -= n1; printf( "%sの魔法吸収--->%sから%dのMPを吸収した。\n\n", member->name, enemy->name, n1 ); break; default: select = -1; break; } } while ( select == -1 ); } 以上。
その他の回答 (25)
- Oh-Orange
- ベストアンサー率63% (854/1345)
★似たものを作ってみました。 >実は、No16さん以降、ソースを追加したり修正しているうちに、文字化けやバグが無くなりました(汗) ↑ これ本当に直った?不思議ですね。たまたま文字化けしなくなった気が…。 ・私も『戦闘シミュレーション』を作ってみました。→sikimori さんのソースを元に。 main() 関数を複数のサブ関数に分割してみました。今後の参考にしてみて下さい。 ※1つのソースで作成しました。→前編、後編で貼り付けておく。 前編: #include <time.h> #include <stdio.h> #include <stdlib.h> #include <windows.h> // 構造体 typedef struct Status { char name[ 20 ]; // キャラクタ名 int hp; // HP量 int mp; // MP量 int st[ 4 ]; // 特殊状態(毒,沈黙,暗闇,混乱) } status; // 記号定数 #define MAX_ENEMY (sizeof(Enemy) / sizeof(status)) #define MAX_MEMBER (sizeof(Member) / sizeof(status)) // マクロ関数 #define RAND(max,ofs) ((rand() % (max)) + (ofs)) // 関数のプロトタイプ宣言 extern void dispStatus( void ); extern int checkEnemy( void ); extern int checkMember( void ); extern int checkGameOver( int *members ); extern int attackMember( int members ); extern int attackEnemy( void ); extern void chaAB( status *member, status *enemy ); extern void chaCD( status *member, status *enemy ); // 敵キャラのデータ extern status Enemy[ 1 ] = { "human", 25000, 9999, 0, 0, 0, 0, }; // 味方キャラのデータ extern status Member[ 4 ] = { "人物A", 6000, 100, 0, 0, 0, 0, "人物B", 4000, 300, 0, 0, 0, 0, "人物C", 4000, 250, 0, 0, 0, 0, "人物D", 6500, 200, 0, 0, 0, 0, }; // 戦闘シミュレーション int main( void ) { // 生き残り人数 int members; // 初期化 srand( (unsigned)time(NULL) ); printf( "/*-------------戦闘開始!!-------------*/\n\n" ); // 最初の1回だけ。味方or敵(0:先手,1:後手) if ( RAND(2,0) == 0 ){ // 味方が先手 while ( !checkGameOver(&members) ){ if ( attackMember(members) ){ // 味方の攻撃 if ( attackEnemy() ){ // 敵の攻撃 Sleep( 2000 ); system( "cls" ); } } } } else{ // 敵が先手 while ( !checkGameOver(&members) ){ if ( attackEnemy() ){ // 敵の攻撃 if ( attackMember(members) ){ // 味方の攻撃 Sleep( 2000 ); system( "cls" ); } } } } return 0; } // 敵,味方ステータスの表示 static void dispStatusOne( status *data ) { static const char *msg[] = { "毒", "沈黙", "暗闇", "混乱", }; int i, flag = 0; // 名前,HP,MPの表示 printf( "%-10s: HP = %6d MP = %4d 状態 :", data->name, data->hp, data->mp ); // 状態の表示 if ( data->hp == 0 ){ printf( "死亡\n" ); return; } for ( i = 0 ; i < 4 ; i++ ){ if ( data->st[i] ){ if ( flag ){ printf( " " ); } printf( msg[i] ); // 毒,沈黙,暗闇,混乱の表示 flag = 1; } } if ( flag == 0 ){ printf( "なし" ); } printf( "\n" ); } // 敵,味方ステータスの表示 extern void dispStatus( void ) { int i; printf( "/*-----------味方のステータス---------*/\n" ); for ( i = 0 ; i < MAX_MEMBER ; i++ ){ dispStatusOne( Member + i ); } printf( "/*-------------敵のステータス---------*/\n" ); for ( i = 0 ; i < MAX_ENEMY ; i++ ){ dispStatusOne( Enemy + i ); } printf( "/*------------------------------------*/\n" ); printf( "\n" ); } // 敵のHPをチェック extern int checkEnemy( void ) { return (Enemy[0].hp > 0) ? 1 : 0; // 1:敵生きている 0:敵死す } //味方のHPをチェック extern int checkMember( void ) { int i; for ( i = 0 ; i < MAX_MEMBER ; i++ ){ if ( Member[i].hp > 0 ){ return 1; } } return 0; // 全滅 } // ゲームオーバーのチェック extern int checkGameOver( int *members ) { int i, count = 0; if ( checkEnemy() == 0 ){ dispStatus(); printf( "%sを倒しました!!\n", Enemy[0].name ); return 1; // 勝利! } if ( checkMember() == 0 ){ dispStatus(); printf( "/*----------ゲームオーバーです----------*/\n" ); return 1; // 全滅! } for ( i = 0 ; i < MAX_MEMBER ; i++ ){ if ( Member[i].hp > 0 ){ count++; } } *members = count; return 0; }
- mikaemi
- ベストアンサー率50% (33/65)
enum human_t { Ahuman = 0, Bhuman, ..., Zhuman, humanNum }; か^^
- mikaemi
- ベストアンサー率50% (33/65)
ちらっと眺めただけで、プログラムを読んでないし、デバッグと直接関係ないですが。。茶々入れてるだけかと思われるならゴメンナサイ^^; 数字をそのまま使うより、 enum human_t { Ahuman, Bhuman, ... }; なんか定義して、列挙にしておいたほうがわかりやすいんじゃないですか? 配列の添え字として使う場合は、 enum human_t { Ahuman = 0, Bhuman, ..., Zhuman, humanNum = Zhuman }; などとして、0 を明示しておいたり、長さを別に定義しておいたり。。 あと、C++コンパイラが使えて、C99の拡張機能を使っていないなら、C++コンパイラにかけてみるのもプログラムチェックに役立つと思いますよ(文字サイズなどちょっとした違いが CとC++にはありますが)。
- Oh-Orange
- ベストアンサー率63% (854/1345)
★訂正。 ・check[] 配列のチェックは if ( check[turn-1] ){ printf( "/*-----%sのターンは終了しています-----*/\n", dt[turn-1].name ); } else switch ( turn ){ case 1: chaAB( &dt[0], &dt[4] ); printf( … ); break; case 2: chaAB( &dt[1], &dt[4] ); printf( … ); break; case 3: chaCD( &dt[2], &dt[4] ); printf( … ); break; case 4: chaCD( &dt[3], &dt[4] ); printf( … ); break; default:break; } でした。 for文で4回ifチェックしなくても良かった。 ・以上。
- Oh-Orange
- ベストアンサー率63% (854/1345)
★いろいろと怪しい箇所(疑問)が見つかった。 ・(1)for ( j = 0 ; j < 4 ; j++ ) if( dt[j].hp == 0 ) flg2--;//死んだキャラがいたらflg2--。 この処理は flg2=4 してから (hp == 0) なら flg2-- よりも flg2=0 してから (hp > 0) なら flg2++ の方が分かりやすくないか。 for ( flg2 = j = 0 ; j < 4 ; j++ ) if ( dt[j].hp > 0 ) flg2++; (2)変数 i と check[ i++ ] = turn; の部分がきっとずれるかも。 ここのロジックが変なので chaA()~chaD() 関数が正しく呼ばれていないと思う。 よって選択した人物とは違う人物関数が処理される。 そして『MPが足りない』となる。 (3)human() 関数で人物A~人物Dの4人に対して同じダメージで攻撃しています。 この動作で良いのですか? (4)chaA()、chaB() が構造体データ以外はすべて同じなら1つの関数に出来ます。 chaC()、chaD() も構造体データ以外はすべて同じなら1つの関数に出来ます。 処理する構造体データの部分だけが違うのなら次の2つに出来ます。 void chaAB( status *member, status *enemy ); void chaCD( status *member, status *enemy ); このような引数にすれば switch ( turn ){ case 1: chaAB( &dt[0], &dt[4] ); printf( … ); break; case 2: chaAB( &dt[1], &dt[4] ); printf( … ); break; case 3: chaCD( &dt[2], &dt[4] ); printf( … ); break; case 4: chaCD( &dt[3], &dt[4] ); printf( … ); break; default:break; } と出来ます。 (5)human() 関数で戦闘するたびに毒、沈黙、暗闇、混乱の状態がセット/リセットします。 普通は一度『毒』状態になったら『毒消し』しない限りはずっと『毒』状態になる気が…。 なので代入ではなくてビットORすれば良い。 (p+i)->st_ab[0] |= s1 % 2; (p+i)->st_ab[1] |= s2 % 2; (p+i)->st_ab[2] |= s3 % 2; (p+i)->st_ab[3] |= s4 % 2; とします。→『|=』を使う。 ・上記の5つ以外にも気になる点がゴロゴロあります。 データは 人物A~人物Dまでを status member[4]; として定義して humanという敵だけを status enemy[1]; として定義した方が分かりやすいですよ。 また構造体データ member、enemy をグローバル変数にして main() 関数の処理を 複数のサブ関数に分けたほうが見やすくなります。 これによりバグになりにくくなる。 ・下に main() 関数と分けたほうが良さそうな関数のプロトタイプ宣言を載せておきます。 作り変えるときの参考にして下さい。 // 記号定数 #define MAX_ENEMY (sizeof(Enemy) / sizeof(status)) #define MAX_MEMBER (sizeof(Member) / sizeof(status)) // マクロ関数 #define RAND(max,ofs) ((rand() % (max)) + (ofs)) // 関数のプロトタイプ宣言 extern void dispStatus( void ); // ステータスの表示 extern int attackEnemy( void ); // 敵の攻撃(戻り値:checkEnemy()の値) extern int attackMember( int members ); // 味方の攻撃(戻り値:checkMember()の値) extern int checkEnemy( void ); // 敵のHP=0をチェック(1=生きている,0=倒れた) extern int checkMember( void ); // 味方4人のHP=0をチェック(1=戦える,0=全滅) extern int checkGameOver( int *members ); // ゲームオーバーをチェック(1=GameOver,0=戦闘続行) extern void chaA( status *member, status *enemy ); // 人物Aの攻撃 extern void chaB( status *member, status *enemy ); // 人物Bの攻撃 extern void chaC( status *member, status *enemy ); // 人物Cの攻撃 extern void chaD( status *member, status *enemy ); // 人物Dの攻撃 // 敵キャラのデータ extern status Enemy[ 1 ] = { "human", 100000, 9999, 0, 0, 0, 0, }; // 味方キャラのデータ extern status Member[ 4 ] = { "人物A", 6000, 100, 0, 0, 0, 0, "人物B", 4000, 300, 0, 0, 0, 0, "人物C", 4000, 250, 0, 0, 0, 0, "人物D", 6500, 200, 0, 0, 0, 0, }; // 戦闘シミュレーション int main( void ) { // 生き残り人数 int members; // 初期化 srand( (unsigned)time(NULL) ); printf( "/*-------------戦闘開始!!-------------*/\n" ); // 最初の1回だけ。味方or敵(0:先手,1:後手) if ( RAND(2,0) == 0 ){ // 味方が先手 while ( !checkGameOver(&members) ){ dispStatus(); // ステータスの表示 if ( attackMember(members) ){ // 味方の攻撃 if ( attackEnemy() ){ // 敵の攻撃 Sleep( 2000 ); system( "cls" ); } } } } else{ // 敵が先手 while ( !checkGameOver(&members) ){ dispStatus(); // ステータスの表示 if ( attackEnemy() ){ // 敵の攻撃 if ( attackMember(members) ){ // 味方の攻撃 Sleep( 2000 ); system( "cls" ); } } } } return 0; } 最後に: ・今回の原因は check[] 配列のロジックが正しくないようです。 『check[ i++ ] = turn;』ではなくて『check[ turn - 1 ] = 1;』にした方が良いでしょう。 それからチェックは for ( flg = j = 0 ; j < 4 ; j++ ){ if ( check[j] ){ printf( "/*-----%sのターンは終了しています-----*/\n", dt[turn-1].name ); flg = 1; break; } } とします。flg は変なところで初期化しないようにして下さい。見づらいよ。 ・check[] 配列のロジックを直せばおかしな構造体データを操作しなくなります。 これにより >(1)dtを初期化しているのに、魔法を使おうとすると、mpが足りないと出る >(2)お互いにダメージを与えているのに敵だけhpが減らない の2つが解決するはずです。 ・以上。サブ関数に分けて作り直した方がデバッグするより早い気がします。
お礼
まず疑問点について私がいうのも変なのですが、回答します。 (1)これは先に思いついた方のやり方を書いてました。 (2)このプログラムで、状態異常をまだ付けていないソースがあったのですが、その時実行した時には何もバクや文字化けなく動きました。 (3)一応全体攻撃として考えているので^^ (4)確かにchaAとchaB、chaCとchaDは殆ど同じ処理内容ですが、まだ追加処理を入れるつもりだったので、一人一人別々に作っていたのですm(_ _)m (5)状態異常のときの処理は、まだ手付かずなので・・・。ごめんなさい。 ビットOR、これは使ったことがなかったので参考にさせていただきます。 実は、No16さん以降、ソースを追加したり修正しているうちに、文字化けやバグが無くなりました(汗)
- mikaemi
- ベストアンサー率50% (33/65)
部分部分ばらばらに貼り付けずに、全部一緒に貼り付けちゃえばいいのに。。そんなに長くなさそうだし。 そのほうが、見てくれる方々も見やすいし、まぎれがなくていいでしょ^^
- Oh-Orange
- ベストアンサー率63% (854/1345)
★アドバイス >基本的に文字化けしたり、値か変わったりするのは、ダメージを受けたあとになっているようです。 >本題ですが、nameのNULL文字以降調べようとしたのですが、調べ方が分からなくて;; ↑ 次のダンプ関数を作成して下さい。 そしてダンプ表示したい場所で呼び出して下さい。 // 構造体の16進ダンプ表示 void printDump( status data[], size_t max, const char msg[] ) { FILE *fp; if ( (fp = fopen("dump16.txt","a")) != NULL ){ fprintf( fp, "---- ↓[%s] ----\n", msg ); for ( size_t n = 0 ; n < max ; n++ ){ unsigned char *p = (unsigned char *)&data[n]; unsigned char *end = (unsigned char *)&data[n + 1]; fprintf( fp, "\n★status dt[%d]:%s\n", n, data[n].name ); for ( int y = 0x00 ; p < end ; y += 0x10 ){ fprintf( fp, "%08X: ", y ); for ( int x = 0 ; (p < end) && (x < 0x10) ; x++ ){ if ( x == 0x07 ){ fprintf( fp, "%02X-", *p++ ); } else{ fprintf( fp, "%02X ", *p++ ); } } fprintf( fp, "\n" ); } } fprintf( fp, "---- ↑[%s] ----\n\n", msg ); fclose( fp ); } } // 使い方 printDump( dt, 5, "test(1)" ); printDump( dt, 5, "test(2)" ); ←ダンプ時のメッセージを指定可能 その他: ・上記の printDump() 関数をソースに組み込んで下さい。 インデント部が全角空白文字なのでエディタでタブ文字に一括変換して下さい。 それから dump16.txt というファイルに追加書き込みされていきます。 メモ帳などで開いて確認して下さい。 ・あと else 部分のソースもすべて貼り付けてくれますか。 その他 human()、chaA()、chaB()、chaC()、chaD() 関数のソースもすべて貼り付けて欲しいです。 ANo.19 の回答への補足、ANo.19 の回答へのお礼、 ANo.18 の回答への補足、ANo.18 の回答へのお礼 の順に else 部分、human()、chaA()、chaB()、chaC()、chaD() 関数を貼り付けて。 ・以上。
- zwi
- ベストアンサー率56% (730/1282)
あっ、何度も申し訳ない! 単なる書き間違いでした。C言語で書いてますよ。 x0xfじゃなくて0x0fです。 すごい打ち間違いだな自分。
- zwi
- ベストアンサー率56% (730/1282)
あっ、さらに修正です。たびたび申し訳ない!。 for( int di=0 ; di<5 ; di++ ) { unsigned char *pDump = (unsigned char*)&dt[di]; printf( "--- dump dt[%d] struct ---",di ); for( int dj=0 ; dj<sizeof(status) ; dj++,pDump++ ) { if( (dj&x0xf)==0 ) printf("\n%04X",dj); printf( "%02X ", *pDump ); } printf("\n"); } printf( "--- dump dt end ---\n" );
補足
これはC++で書かれていますか?残念ながら私Cで書いてまして・・・。 取り合えず、ファイル名を「.cpp」に変えてやってみましたが、 エラー E2451 main.cpp 25: 未定義のシンボル x0xf(関数 main() ) っていうのが発生しました(汗)
- zwi
- ベストアンサー率56% (730/1282)
ダンプは、それでOKです。俗にダンプというとメモリ内容を16進数で表示させたものをいいます。 でも、ごめんなさい、ちょっと見づらかったですね(^^ゞ 今後のことを考えて修正しました。 for( int di=0 ; di<5 ; di++ ) { unsigned char *pDump = (unsigned char*)&dt[di]; printf( "--- dump dt[%d] struct ---\n",di ); for( int dj=0 ; dj<sizeof(status) ; dj++,pDump++ ) { printf( "%02X ", *pDump ); if( (dj&x0xf)==0xf ) printf("\n"); } printf("\n"); } printf( "--- dump dt end ---\n" ); これを正常な場合と異常な場合の2パターンお願いします。
お礼
わざわざ考えていただいてありがとうございますm(_ _)m これを参考に、改良できる部分はしていきたいと思います。 いきなり直ったので、出来る限り原因追求していきます。