二次元配列で要素に代入すると別の要素にも代入されてしまいました

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

二次元配列で要素に代入すると別の要素にも代入されてしまいました

#1

投稿記事 by ヒナリ3x » 8年前

初めて質問するので、テンプレを流用します。

[1] 質問文
 [1.1] 自分が今行いたい事は何か

二次元配列を使ってマス目を作る。
マス目の上下左右の両端を1にする。

 [1.2] どのように取り組んだか(プログラムコードがある場合記載)

コード:

 
#include"Dxlib.h"


void MasuSyokika();//マスに壁(1)の枠を作る
void MasuHyouzi();//表示

int masu[15][7];
int masu_syokika_flag=0;//マスの外側を壁(1)にしたか

int WINAPI WinMain(HINSTANCE hI,HINSTANCE hp,LPSTR lpC,int nC)
{
ChangeWindowMode(TRUE);			//ウィンドウズモードで起動
if(DxLib_Init()==-1)return(-1);		//DXライブラリ初期化
while(ProcessMessage()==0&&CheckHitKey(KEY_INPUT_ESCAPE)==0)
	{
	MasuSyokika();
	break;
	}
DxLib_End();				//Dxライブラリ終了
return(0);				//終了
}

void MasuSyokika()
	{
	int a=0;
	int b=0;
	while(ProcessMessage()==0&&CheckHitKey(KEY_INPUT_ESCAPE)==0&&masu_syokika_flag==0)            
		{		
		masu[0][a]=1;
		masu[15][a]=1;
		masu[b][0]=1;
		masu[b][7]=1;
		if(a<8)a++;
		if(b<16)b++;
		printfDx("a=%d,b=%d¥n¥n",a,b);
		MasuHyouzi();
		
		if(a==8&&b==16)
			{
			masu_syokika_flag=1;
			printfDx("a=%d,b=%d¥n終了¥n¥n",a,b);
			WaitKey();
			break;
			}
		}
	}

void MasuHyouzi()
	{
	int a=0;
	int b=0;
	while(ProcessMessage()==0&&CheckHitKey(KEY_INPUT_ESCAPE)==0)
		{
		printfDx("%2d",masu[b][a]);
		a++;
		if(a==8)
			{
			printfDx("¥n");
			b++;
			a=0;
			if(b==16)
				{
				WaitKey();
				clsDx();
				ClearDrawScreen();
				b=0;
				break;
				}
			}
		}
	
	}
 
 [1.3] どのようなエラーやトラブルで困っているか(エラーメッセージが解る場合は記載)

予期しない要素に1が代入される。
配列の範囲外にも0と1が発生している。

 [1.4] 今何がわからないのか、知りたいのか

別の要素にも代入される仕組みが知りたい。
配列について理解を深めたい。

ついでにこのバグを修正したい。

[2] 環境  
 [2.1] OS : Windows, Linux等々

Windows

 [2.2] コンパイラ名 : VC++ 2008EE, Borand C++, gcc等々

Borand C++5.5.1

[3] その他
 ・どの程度C言語を理解しているか

14歳からはじめるc言語プログラミングを流し読み。
c言語の切り札を過去問手前まで読み、多分理解。
cの絵本の第2版を読み、多分理解。
苦しんで覚えるc言語を読んでいる途中で今回のプログラムを思い立つ。

 ・ライブラリを使っている場合は何を使っているか

DXライブラリ


説明不足あれば教えて下さるとありがたいです。

アバター
みけCAT
記事: 6734
登録日時: 14年前
住所: 千葉県
連絡を取る:

Re: 二次元配列で要素に代入すると別の要素にも代入されてしまいました

#2

投稿記事 by みけCAT » 8年前

C言語では、配列の宣言で使う数値は要素数であり、配列の添字は0から宣言した数未満の整数が有効な範囲です。
従って、masu[15][a]=1;やmasu[7]=1;は範囲外への書き込みであり、未定義動作となります。
配列masuの要素数を十分多くしましょう。

ヒナリ3x さんが書きました:別の要素にも代入される仕組みが知りたい。

範囲外へのアクセスによりたまたまアクセスした位置にあった要素に代入が行われたように見えます。
すなわち、メモリが破壊されています。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

ヒナリ3x

Re: 二次元配列で要素に代入すると別の要素にも代入されてしまいました

#3

投稿記事 by ヒナリ3x » 8年前

返信ありがとうございます。

配列masu[7]を宣言したら、確保されるメモリは
[0],[1],[2],[3],[4],[5],[6]の7個で、[7]に代入すると予期せぬ動作になる。
という理解で大丈夫ですか?

知識として知っていても、実際にやってみると失敗するものですね。
お恥ずかしい。


私の作りたいマス目の範囲は、
縦に15マス。(内、上下2マスは壁(1)にする)
横に8マス。(内、左右2マスは壁(1)にする)
です。

つまり、

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

としたいわけです。

そこで、返信を見てコードを修正しました。
MasuSyokika関数の内部です。

コード:

                masu[0][a]=1;
		masu[14][a]=1;
		masu[b][0]=1;
		masu[b][6]=1;
		if(a<7)a++;
		if(b<15)b++;
[\code]

これで、aは6。bは14で止まると思います。

masu[0][0~6]=1
masu[14][0~6]=1
masu[0~14][0]=1
masu[0~14][6]=1

になり、1の枠が完成すると思ったのですが
この考え方は大丈夫でしょうか?


結果は、思い通りの結果にはなりませんでした。

アバター
みけCAT
記事: 6734
登録日時: 14年前
住所: 千葉県
連絡を取る:

Re: 二次元配列で要素に代入すると別の要素にも代入されてしまいました

#4

投稿記事 by みけCAT » 8年前

ヒナリ3x さんが書きました:MasuSyokika関数の内部です。
[​code]
masu[0][a]=1;
masu[14][a]=1;
masu[0]=1;
masu[6]=1;
if(a<7)a++;
if(b<15)b++;
[\code]

これで、aは6。bは14で止まると思います。

masu[0][0~6]=1
masu[14][0~6]=1
masu[0~14][0]=1
masu[0~14][6]=1

になり、1の枠が完成すると思ったのですが
この考え方は大丈夫でしょうか?

ダメですね。
aは7、bは15で止まります。
a=6のときはa<7が真なのでa++;が実行され、a=7になってようやくa<7が偽でa++;が実行されなくなります。
aとbを同じ早さで増やしているので、bが15に到達するより前にaが7に到達し、masu[0][a]=1;で範囲外への書き込みが発生します。
また、
ヒナリ3x さんが書きました:

コード:

 		if(a==8&&b==16)
も修正しないとダメですね。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

ヒナリ3x

Re: 二次元配列で要素に代入すると別の要素にも代入されてしまいました

#5

投稿記事 by ヒナリ3x » 8年前

ありがとうございます。
勘違いしていたので修正です。

縦15、横8のマスを作りたいと言っていたのに、
masu[15][7];にしていた為、
masu[15][8];に修正します。

これで、aが7でも大丈夫のはずです。
指摘通り、ifも修正します。

コード:

#include"Dxlib.h"
 
 
void MasuSyokika();//マスに壁(1)の枠を作る
void MasuHyouzi();//表示
 
int masu[15][8];
int masu_syokika_flag=0;//マスの外側を壁(1)にしたか
 
int WINAPI WinMain(HINSTANCE hI,HINSTANCE hp,LPSTR lpC,int nC)
{
ChangeWindowMode(TRUE);         //ウィンドウズモードで起動
if(DxLib_Init()==-1)return(-1);     //DXライブラリ初期化
while(ProcessMessage()==0&&CheckHitKey(KEY_INPUT_ESCAPE)==0)
    {
    MasuSyokika();
    break;
    }
DxLib_End();                //Dxライブラリ終了
return(0);              //終了
}
 
void MasuSyokika()
    {
    int a=0;
    int b=0;
    while(ProcessMessage()==0&&CheckHitKey(KEY_INPUT_ESCAPE)==0&&masu_syokika_flag==0)            
        {       
        masu[0][a]=1;
        masu[14][a]=1;
        masu[b][0]=1;
        masu[b][7]=1;
        if(a<7)a++;
        if(b<14)b++;
        printfDx("a=%d,b=%d¥n¥n",a,b);
        MasuHyouzi();
        
        if(a==7&&b==14)
            {
            masu_syokika_flag=1;
            printfDx("a=%d,b=%d¥n終了¥n¥n",a,b);
            WaitKey();
            break;
            }
        }
    }
 
void MasuHyouzi()
    {
    int a=0;
    int b=0;
    while(ProcessMessage()==0&&CheckHitKey(KEY_INPUT_ESCAPE)==0)
        {
        printfDx("%2d",masu[b][a]);
        a++;
        if(a==8)
            {
            printfDx("¥n");
            b++;
            a=0;
            if(b==15)
                {
                WaitKey();
                clsDx();
                ClearDrawScreen();
                b=0;
                break;
                }
            }
        }
    
    }
これで、動きました。
頼ってばかりですみませんが、まだ問題があります。

ヒナリ3x

Re: 二次元配列で要素に代入すると別の要素にも代入されてしまいました

#6

投稿記事 by ヒナリ3x » 8年前

1番最初に投稿した内容の、配列外のメモリにも1と0が発生しています。

既に把握しているかもしれませんが、

MasuSyokika関数内の最後に、
MasuHyouzi関数を使うことで、
現在のマスの状況を把握しています。

マスを全て表示した後、
MasuHyouzi関数を抜ける直前にWaitKey関数によって
処理を止めています。
よって、何かキーを押すたびにマスが1つずつ1に変わるのを見れるんです。


そこで、MasuHyouzi関数内のif条件や、表示する要素を変更して
配列外のメモリを見ると、そこにも0と1が存在していました。

しかも、キーを押すたびに同じように1になっていくのです。
いろいろ変数やらをいじっても、逆にいろんな場所に1が生まれるし
あげく、作った枠内にも1が発生していました。
これが、今回ここの掲示板に投稿しようと思ったキッカケです。

変更したコードを貼ります。
7と14行目です。

コード:

void MasuHyouzi()
    {
    int a=0;
    int b=0;
    while(ProcessMessage()==0&&CheckHitKey(KEY_INPUT_ESCAPE)==0)
        {
        printfDx("%2d",masu[b][a-8]);//参照する配列の要素をマイナスするとか…
        a++;
        if(a==8)
            {
            printfDx("¥n");
            b++;
            a=0;
            if(b==20)//表示するb行の値を増やすとか…
                {
                WaitKey();
                clsDx();
                ClearDrawScreen();
                b=0;
                break;
                }
            }
        }
}
表示される要素が、a列は左方向に8増えて
b行は下方向に5くらい増えたかな?
ここら辺のメモリにも、0と1が発生していて
処理が進むと1に置き換わっていくんです。

なんとなく、同じアドレスを参照してる気もするんですけど
うまい具合に理解できていません。

ヒナリ3x

Re: 二次元配列で要素に代入すると別の要素にも代入されてしまいました

#7

投稿記事 by ヒナリ3x » 8年前

貼るコード内容を間違えました。

コード:

void MasuHyouzi()
    {
    int a=0;
    int b=0;
    while(ProcessMessage()==0&&CheckHitKey(KEY_INPUT_ESCAPE)==0)
        {
        printfDx("%2d",masu[b-2][a]);//参照する配列の要素をマイナスするとか…
        a++;
        if(a==15)
            {
            printfDx("¥n");
            b++;
            a=0;
            if(b==15)//表示するb行の値を増やすとか…
                {
                WaitKey();
                clsDx();
                ClearDrawScreen();
                b=0;
                break;
                }
            }
        }
}

ヒナリ3x

Re: 二次元配列で要素に代入すると別の要素にも代入されてしまいました

#8

投稿記事 by ヒナリ3x » 8年前

なんども投稿してすみません!


7行目の参照するbの値を-2に。
9行目のifの条件をa==15に変更しました。

コード:

        printfDx("%2d",masu[b-2][a]);
        if(a==15)
結果は、
3187681873103781 適当な数字が並ぶ。
2184637813107681 0 0 0 1 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 1 0 0 0 0 0 0 0
...

みたいな感じです
説明下手ですみません

アバター
みけCAT
記事: 6734
登録日時: 14年前
住所: 千葉県
連絡を取る:

Re: 二次元配列で要素に代入すると別の要素にも代入されてしまいました

#9

投稿記事 by みけCAT » 8年前

確保された領域の範囲外へのアクセス(読み書き)は未定義動作を起こします。
何が起きてもおかしくありません。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

アバター
みけCAT
記事: 6734
登録日時: 14年前
住所: 千葉県
連絡を取る:

Re: 二次元配列で要素に代入すると別の要素にも代入されてしまいました

#10

投稿記事 by みけCAT » 8年前

実装の例として、int masu[15][7];に対するmasu[a]へのアクセスが、int masu[15*7];に対するmasu[a*7+b]へのアクセスとして実行されるものがあります。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

ヒナリ3x

Re: 二次元配列で要素に代入すると別の要素にも代入されてしまいました

#11

投稿記事 by ヒナリ3x » 8年前

領域外へのアクセスが危険だということは知っているのですが…。
やはり気になるのです。

本来、領域外であるはずのメモリになぜ処理が行われているのか。
これを許容してプログラムを作成していいのか。
どこかでバグに繋がらないか。


みけCAT さんが書きました:実装の例として、int masu[15][7];に対するmasu[a]へのアクセスが、int masu[15*7];に対するmasu[a*7+b]へのアクセスとして実行されるものがあります。


うーん…、私の勉強不足ですね。理解できませんでした。ごめんなさい。

かずま

Re: 二次元配列で要素に代入すると別の要素にも代入されてしまいました

#12

投稿記事 by かずま » 8年前

規格では、配列の範囲外アクセスは未定義動作となっていますが、
通常の実装では、int masu[15][8]; と宣言した場合、
masu[0][8] と masu[1][0] は同じメモリ領域になります。
masu[15][-1] も masu[14][7] と同じです。

masu[1][0] に 1 を格納した後、
masu[0][8] を見ると 1 に変わっているのは当然です。

余談ですが、私ならこう書きます。

コード:

#include"Dxlib.h"

void MasuSyokika();
void MasuHyouzi(int a, int b);

enum { ROW = 15, COL = 8 };
int masu[ROW][COL];

int WINAPI WinMain(HINSTANCE hI, HINSTANCE hp, LPSTR lpC, int nC)
{
    ChangeWindowMode(TRUE);
    if (DxLib_Init() == -1) return 1;
    MasuSyokika();
    DxLib_End();
    return 0;
}

void MasuSyokika()
{
    int a, b;
    for (b = 0; b < ROW; b++) {
        if (b < COL) {
            a = b;
            masu[0][a] = masu[ROW-1][a] = 1;
        }
        masu[b][0] = masu[b][COL-1] = 1;
        MasuHyouzi(a, b);        
    }
    printfDx("\n終了\n");
    WaitKey();
}

void MasuHyouzi(int a, int b)
{
    clsDx();
    ClearDrawScreen();
    printfDx("a=%d,b=%d\n\n", a, b);
    for (int i = 0; i < ROW; i++) {
        for (int j = 0; j < COL; j++)
            printfDx("%2d", masu[i][j]);
        printfDx("\n");
    }
    WaitKey();
}

ヒナリ3x

Re: 二次元配列で要素に代入すると別の要素にも代入されてしまいました

#13

投稿記事 by ヒナリ3x » 8年前

返信ありがとうございます。
みけCATさん、かずまさん、親切に答えて下さりありがとうございました!
無事、疑問を解決できました!

余談ですが、私ならこう書きます。
結果的に同じことをするプログラムでも、やはり自分以外の人の書き方をみるのは楽しいですし、勉強になりますね!

私の場合、
enumで名前をつけるより、ただの数字の方がなんとなく見やすい。
さらに、今回は短いプログラムなので定数はいいかな、と。

初期化関数や表示関数にfor文の入れ子を使わなかったのも、
見やすくしたかったからです。

処理的には、whileとfor、どちらが負担少ないんですかね…?

コード:

masu[0][a] = masu[ROW-1][a] = 1;
masu[b][0] = masu[b][COL-1] = 1;
こういう代入の仕方もあるんですね!
豆知識みたいなのも、これからどんどん覚えたいですね。

返信

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