二次元配列のデータの最大値、最小値を求める

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
Look
記事: 3
登録日時: 11年前
住所: 関西

二次元配列のデータの最大値、最小値を求める

#1

投稿記事 by Look » 11年前

[1] 質問文
 [1.1] 自分が今行いたい事は何か
個人でのゲーム制作に用いるのを目的としています
 複数存在するキャラクターと、マップ上に複数存在するポイントの距離差を、
 kyori[j]という二次元配列を用いて、求めました。(iがキャラクター、jがポイントとしています)
 次に、其々のキャラクター別に、求めた距離差から、最短距離(最小値)、最長距離(最大値)を、
 関数(返り値int型)を用いて算出したいと思いました。
 そこで、まずは一次元配列を引数にして、同様に最大値、最小値を求める関数(上記のプレイヤーが単数になったもの)を作りました。
 そちらは成功しました、しかし、二次元配列を引数とするとどうも勝手が分かりません。
 さまざまなサイトを巡り、情報を収集し、実装可能なレベルで実装しましたが、うまくいかない。
 よって、ここでは、二次元配列を引数とし、最小値、あるいは最大値を返す関数を作りたい。
 しかし、分からないので(詳細は後記)ご教授願いたい。
 というのが質問の主旨となります。

 [1.2] どのように取り組んだか(プログラムコードがある場合記載)
 実際のプログラムを記述します。

コード:


//キャラクター(3人)からマップに存在するポイント(10点)との距離差をキャラクターごとに求め
//その最短距離(最小値)と最長距離(最大値)を返す関数を作成するサンプルソース


#include <stdio.h>	//出力用
#include <math.h>	//距離差算出用(sqrt,pow)

//マクロ
#define CHARA_MAX	3	//キャラクター数
#define POINT_MAX	10	//ポイント数
#define MIN		 0	 //最小値
#define MAX		 1	 //最大値

//関数プロトタイプ宣言
void Init(void); //初期化

//今回問題とする関数--------------------------------------------------------------------------
int MostValue2(int Target[][10], int Rotate1, int Rotate2, int pattern);
//--------------------------------------------------------------------------------------------------

//構造体
typedef struct
{
	int x, y;

}CHARA;

CHARA chara[CHARA_MAX];  //キャラクター
CHARA point[POINT_MAX];	  //ポイント

//メイン(void main(void)の略でmain() )----------------------------------------------------------------------------
main()
{
	//初期化
	Init();


	//カウント用
	int i, j;

	//最小値、最大値、それ以外の値を出力する用
	int min[CHARA_MAX], max[CHARA_MAX];

	//距離差(二次元配列を用いる)
	int kyori[CHARA_MAX][POINT_MAX];



	//charaとpointの(双方複数存在する)距離差を求めている。---------------------------

	for (i = 0; i < CHARA_MAX; i++)
	{
		for (j = 0; j < POINT_MAX; j++)
		{
			kyori[i][j] = sqrt(pow((chara[i].x - point[j].x), 2.0) + pow((chara[i].y - point[j].y), 2.0));
		}
	}

	//--------------------------------------------------------------------------------

	for (i = 0; i < CHARA_MAX; i++)
	{

		//最小値をminに入れる
		min[i] = MostValue2(kyori, CHARA_MAX, POINT_MAX, MIN);

		//最大値をmaxに入れる
		max[i] = MostValue2(kyori, CHARA_MAX, POINT_MAX, MAX);

	}

	//〓結果出力〓---------------------------------------------------------------------

	//キャラクター1の場合
	printf("%d,%d,%d\n", min[0], max[0]);
	//キャラクター2の場合
	printf("%d,%d,%d\n", min[1], max[1]);
	//キャラクター3の場合
	printf("%d,%d,%d\n", min[2], max[2]);

	//---------------------------------------------------------------------------------

}


//最小値、最大値を返す関数本体-------------------------------------------------------------------------------------

//◆Target[][]	→	算出元、今回の場合,kyori[CHARA_MAX][POINT_MAX]、
//◆Rotate1,2	→	繰り返し回数、今回の場合CHARA_MAX,POINT_MAX
//◆pattern	→	MINなら最小値、MAXなら最大値、それ以外なら-999を返す(値が返らない可能性があるというエラー防止用)

int MostValue2(int Target[][10], int Rotate1, int Rotate2, int pattern)
{
	//カウント用
	int i, j;
	//最小、最大値算出用。キャラクターごとの最小値を求めたい為、配列にしている。
	int min[Rotate1];
	int max[Rotate1];

	//初期化。始めの配列の添え字(0)と比較していくため、0に初期化している。
	for (i = 0; i < Rotate1; i++)
	{
		min[i] = Target[i][0];
		max[i] = Target[i][0];
	}

	switch (pattern)
	{
		//最小値を返す
	case MIN:

		for (i = 0; i < Rotate1; i++)
		{
			for (j = 0; j < Rotate2; j++)
			{
				if (Target[i][j] < min[i])
				{
					min[i] = Target[i][j];
				}
			}
			//ポイントとの比較を終えたら、そのキャラクタの場合の最小値を返す
			return min[i];
		}

		break;

		//最大値を返す
	case MAX:

		for (i = 0; i < Rotate1; i++)
		{
			for (j = 0; j < Rotate2; j++)
			{
				if (Target[i][j] > max[i])
				{
					max[i] = Target[i][j];
				}
			}
			//ポイントとの比較を終えたら、そのキャラクタの場合の最大値を返す
			return max[i];
		}

		break;

		//それ以外なら-999を返す
	default:
		return -999;


	}
}

//初期化-----------------------------------------------------------------------------------------------
void Init()
{
	int i;


	for (i = 0; i < POINT_MAX; i++)
	{
		chara[i].x = 530 + (i * 30);
		chara[i].y = 30;
	}

	point[0].x = 160;
	point[0].y = 250;
	point[1].x = 50;
	point[1].y = 250;
	point[2].x = 160;
	point[2].y = 120;
	point[3].x = 290;
	point[3].y = 80;
	point[4].x = 450;
	point[4].y = 80;
	point[5].x = 290;
	point[5].y = 300;
	point[6].x = 450;
	point[6].y = 300;
	point[7].x = 570;
	point[7].y = 300;
	point[8].x = 570;
	point[8].y = 100;
	point[9].x = 50;
	point[9].y = 420;
	point[10].x = 160;
	point[10].y = 420;
}


//補足。◆一次元配列の最小値、最大値を返す関数(原文ではコメントアウトしてある)---------------------------------------------

int MostValue(int *Target, int Rotate, int pattern)
{
	int i;
	int min = Target[0];
	int max = Target[0];

	switch (pattern)
	{
		//最小値を返す
	case 0:

		for (i = 0; i < Rotate; i++)
		{
			if (Target[i] < min)
			{
				min = Target[i];
			}
		}

		return min;

		break;

		//最大値を返す
	case 1:

		for (i = 0; i < Rotate; i++)
		{
			if (Target[i] > max)
			{
				max = Target[i];
			}
		}

		return max;

		break;

	default:
		return -999;


	}


}

 
 [1.3] どのようなエラーやトラブルで困っているか(エラーメッセージが解る場合は記載)

 まず、二次元配列[][]に格納されているデータの最小値、最大値を求めたい為、
 関数の引数には当然ながら如何なる数字が入ってもそれに対応した答えを返してきてほしい。
 なので17行目、90行目の「10」のように固定された数字は入れたくない。今回はエラー防止のために入れた。
 int Rotate2といれてみたが
 例)int MostValue2(int Target[][int Rotate2], int Rotate1, int pattern)
 ]が型の前にありません。などというコンパイルエラーが出る↑

 何もいればければ
 例)int MostValue2(int Target[][], int Rotate1,int Rotate2, int pattern)
 'Target' 添え字がありません。というコンパイルエラーが出る↑

 調べてみると、二次元配列の列(横)の添え字は、指定しないといけないとのことだったので
 今回) int MostValue2(int Target[][10], int Rotate1,int Rotate2, int pattern)
 とすることで解決を図った。しかし意図していたものではない。

 更に、95,96行目で
 int min[Rotate1];
 int max[Rotate1];
 といった風に宣言しているが、これも、サイズが不明です。というコンパイルエラーが出る。
 引数を、配列の添え字に当てはめる行為は間違っているようなのです。


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

 上記のエラーがでる理由。
 まず、二次元配列を引数とし、更にそのサイズは決まっていない(引数によって定める)場合の方法。
 そして、一次元配列での最大値、最小値を求めるプログラム(ソース内に記載)との違い。
 その改善法、あるいは考え方が知りたいです。

[2] 環境  
 [2.1] OS : Windows, Linux等々
 windows8.1
 [2.2] コンパイラ名 : VC++ 2008EE, Borand C++, gcc等々
 VS2013 x86 Native Tools コマンドプロンプト

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

 C言語を学び始めて3か月です。
 二次元配列は、csvファイル(stdio.hのFILE構造体関係)によるマップの構築にしか用いたことがなく、
 今回のように関数の引数として用いるというのは初めてです。
 ポインタ。に関してですが、今回一次元配列バージョンの最大値、最小値算出関数の中で
 「配列の先頭の値を渡す」という認識で用いています。
 これは自分で調べ、取り入れたものですので、「ポインタ」というものが何か。ほかにどのような用途があるのか
 その全貌を理解しているわけではありません。

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

Re: 二次元配列のデータの最大値、最小値を求める

#2

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

最大値を求めるには、配列の全ての要素をチェックして、その中で一番大きいものを保存し、それを返します。
逆に、最小値を求めるには、配列の全ての要素をチェックして、その中で一番小さいものを保存し、それを返します。
この考え方は、何次元配列でも、もしくは配列ではなく線形リストや木構造(二分木)でも利用可能です。

コード:

#include <stdio.h>
#include <stdlib.h>

#define WIDTH 10
#define HEIGHT 3

int getMax(int hoge[][WIDTH], int height) {
	int max=hoge[0][0];
	int i,j;
	for(i=0;i<height;i++) {
		for(j=0;j<WIDTH;j++) {
			if(hoge[i][j]>max)max=hoge[i][j];
		}
	}
	return max;
}

int main(void) {
	int hoge[HEIGHT][WIDTH];
	int i,j;
	int max;
	/* 適当なデータを生成する */
	srand(1);
	for(i=0;i<HEIGHT;i++) {
		for(j=0;j<WIDTH;j++)hoge[i][j]=rand();
	}
	/* 生成したデータを表示する */
	for(i=0;i<HEIGHT;i++) {
		for(j=0;j<WIDTH;j++)printf("%d%c",hoge[i][j],j+1<WIDTH?' ':'\n');
	}
	/* 最大値を求める */
	max=getMax(hoge,HEIGHT);
	/* 求めた最大値を表示する */
	printf("\nmax = %d\n",max);
	return 0;
}
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

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

Re: 二次元配列のデータの最大値、最小値を求める

#3

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

Look さんが書きました: まず、二次元配列[][]に格納されているデータの最小値、最大値を求めたい為、
 関数の引数には当然ながら如何なる数字が入ってもそれに対応した答えを返してきてほしい。
 なので17行目、90行目の「10」のように固定された数字は入れたくない。今回はエラー防止のために入れた。

 (中略)

 調べてみると、二次元配列の列(横)の添え字は、指定しないといけないとのことだったので
 今回) int MostValue2(int Target[][10], int Rotate1,int Rotate2, int pattern)
 とすることで解決を図った。しかし意図していたものではない。
C言語/C++では不可能です。
二次元配列int hoge[WIDTH][HEIGHT]の代わりに一次元配列int hoge2[WIDTH*HEIGHT]を用いて、
hoge[j]の代わりにhoge2[WIDTH*i+j]を用いるべきだと思います。
C++なら、二次元配列のようにアクセスできるクラスを作成するという方法もあります。

Look さんが書きました: 更に、95,96行目で
 int min[Rotate1];
 int max[Rotate1];
 といった風に宣言しているが、これも、サイズが不明です。というコンパイルエラーが出る。
 引数を、配列の添え字に当てはめる行為は間違っているようなのです。

(GCC拡張の無い)C言語/C++では、このような形での配列の動的確保はできません。
C言語ならmallocやcalloc、C++ならint *min=new int[Rotate1];のようにして確保するべきです。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

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

Re: 二次元配列のデータの最大値、最小値を求める

#4

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

Look さんが書きました:

コード:

	//--------------------------------------------------------------------------------

	for (i = 0; i < CHARA_MAX; i++)
	{

		//最小値をminに入れる
		min[i] = MostValue2(kyori, CHARA_MAX, POINT_MAX, MIN);

		//最大値をmaxに入れる
		max[i] = MostValue2(kyori, CHARA_MAX, POINT_MAX, MAX);

	}

そもそも、今回はこのようにして呼び出しているので、
素直に一次元配列の最大値/最小値を求める関数を用いればいいと思います。

コード:

	//--------------------------------------------------------------------------------

	for (i = 0; i < CHARA_MAX; i++)
	{

		//最小値をminに入れる
		min[i] = MostValue2_2(kyori[i], POINT_MAX, MIN);

		//最大値をmaxに入れる
		max[i] = MostValue2_2(kyori[i], POINT_MAX, MAX);

	}

/* 略 */

int MostValue2_2(const int kyori[], int n, int type) {
	int i;
	int res;
	if(kyori==NULL || n<=0 || (type!=MIN && type!=MAX))return -999;
	res=kyori[0];
	for(i=1;i<n;i++) {
		if((type==MIN && kyori[i]<res) || (type==MAX && kyori[i]>res)) {
			res=kyori[i];
		}
	}
	return res;
}
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

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

Re: 二次元配列のデータの最大値、最小値を求める

#5

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

Look さんが書きました:引数を、配列の添え字に当てはめる行為は間違っているようなのです。
C言語/C++において、(特に縛りプレイのような条件がない場合は)
「引数を、配列の添え字に当てはめる行為」自体は全く間違っていません。
ただし、バッファオーバーランなどの不都合を起こさないため、値のチェックは必要かもしれません。

コード:

int hoge[10];
における10は「添え字」ではなく、「要素の個数」です。
(参照:http://www.cc.kyoto-su.ac.jp/~yamada/pr ... array.html)
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

Look
記事: 3
登録日時: 11年前
住所: 関西

Re: 二次元配列のデータの最大値、最小値を求める

#6

投稿記事 by Look » 11年前

みけCAT様。 返信有難う御座います。 早くて驚いています。

まず。kyori(距離差を求める変数)を、私は二次元配列を用いて導き出しておりましたが
みけCAT様のご指摘を受け、一次元配列に改良いたしました。

以下に変更したプログラムの一部を記述したいと思いますが
長く考えたプロセスをきちんと明記しておきたかったので
非常に長文となってしまいました。

コード:


//関数プロトタイプ宣言---------------------------------------------------------------------------------------------

//変更前
int MostValue2(int Target[][10], int Rotate1, int Rotate2, int pattern);
//変更後
int MostValue2(const int Target[], int Rotate, int pattern)

/*
◆Target(最大値、最小値を割り出したい配列)を、二次元から一次元に。
これは、前述の通り距離差を求めるkyori変数を一次元配列に変更した故にです。
場所同じにint から const int になりました。constというのはどういったものなのか分からなかったので、
ウェブから情報を拾ってきました。大雑把に言えば、変数を定数にする修飾子と考えてよいのでしょうか
kyoriは、あらかじめ計算で求められているため、関数内で書き換えられる余地がない。事故を防ぐために・・といった感じでしょうか。

◆Rotate(回すという意味)という引数が、これまで1と2とありましたが、1つに収束された。
これは後述にありますが、事前にfor分で関数をキャラクター数分回しているため、省いた。といった感じでしょうか。

*/

//距離差を求める-------------------------------------------------------------------------------------------------------------------------

//変更前
int kyori[CHARA_MAX][POINT_MAX];
//変更後
int kyori[CHARA_MAX * POINT_MAX];

/*
変数宣言です。
kyoriが一次元配列になりました。これにより
*/

//変更前
 	for (i = 0; i < CHARA_MAX; i++)
	{
		for (j = 0; j < POINT_MAX; j++)
		{
       kyori[i][j] = sqrt(pow((chara[i].x - point[j].x), 2.0) + pow((chara[i].y - point[j].y), 2.0));
		}
	}
             
//変更後
	for (i = 0; i < CHARA_MAX; i++)
	{
		for (j = 0; j < POINT_MAX; j++)
		{
			//距離差を二次元ではなく一次元で求める。
			kyori[POINT_MAX * i + j] = sqrt(pow((chara[i].x - point[j].x), 2.0) + pow((chara[i].y - point[j].y), 2.0));
		}
	}

/*
算出方法は変わりません。しかし、一次元配列にしたことで最大値、最小値を求める際に支障がなくなりました
*/


//関数コール------------------------------------------------------------------------------------------------------------------

//変更前
      for (i = 0; i < CHARA_MAX; i++)
      {
 
           //最小値をminに入れる
           min[i] = MostValue2(kyori, CHARA_MAX, POINT_MAX, MIN);
 
           //最大値をmaxに入れる
           max[i] = MostValue2(kyori, CHARA_MAX, POINT_MAX, MAX);
 
      }
//変更後
	for (i = 0; i < CHARA_MAX; i++)
	{

		//最小値をminに入れる
		min[i] = MostValue2(kyori[i], POINT_MAX, MIN);

		//最大値をmaxに入れる
		max[i] = MostValue2(kyori[i], POINT_MAX, MAX);

	}

/*
プロトタイプ宣言の項で述べた通り、引数は減っています。
まず、kyoriという二次元配列を、Targetという二次元配列の引数に渡していましたが、
それが一次元配列になり、上記のkyori[i]は、二次元配列でいうとHEIGHT、つまりキャラクターの数分、
関数を回している。まとめると、kyori[CHARA1],kyori[CHARA2],kyori[CHARA3]それぞれ3回、関数内で
POINT_MAX回数分(WIDTH、つまりポイントの数分)回している。そしてMAXなら最大値。MINなら最小値を返す
といったところでしょうか。言葉選びが悪いかもしれません。
*/


//関数本体------------------------------------------------------------------------------------------------------------------------

//◆修正前
int MostValue2(int Target[], int Rotate, int pattern)
{
	
	int i, j;
	
	int min[Rotate1];
	int max[Rotate1];

	
	for (i = 0; i < Rotate1; i++)
	{
		min[i] = Target[i][0];
		max[i] = Target[i][0];
	}

	switch (pattern)
	{
		//最小値を返す
	case MIN:

		for (i = 0; i < Rotate1; i++)
		{
			for (j = 0; j < Rotate2; j++)
			{
				if (Target[i][j] < min[i])
				{
					min[i] = Target[i][j];
				}
			}
			//ポイントとの比較を終えたら、そのキャラクタの場合の最小値を返す
			return min[i];
		}

		break;

		//最大値を返す
	case MAX:

		for (i = 0; i < Rotate1; i++)
		{
			for (j = 0; j < Rotate2; j++)
			{
				if (Target[i][j] > max[i])
				{
					max[i] = Target[i][j];
				}
			}
			//ポイントとの比較を終えたら、そのキャラクタの場合の最大値を返す
			return max[i];
		}

		break;

		//それ以外なら-999を返す
	default:
		return -999;


	}
}

//◆修正後
int MostValue2(const int Target[], int Rotate, int pattern)
{
	int i;
	int res;	//minまたはmaxを返す(修正前はminとmaxという二つの動的変数を定義してしまっていた)

	//kyoriが0、またはRotateが0以下(つまり繰り返すことができない)、patternがMINでもMAXでもない時
	if (Target == NULL || Rotate <= 0 || (pattern != MIN && pattern != MAX))
	{
		//-999を返す
		return -999;
	}
	//比較するため、resに配列の先頭アドレスを入れる
	res = Target[0];
	//Rotate分繰り返す(今回ならポイントの数分。i = 1となっているのは、resにはすでに配列の0番目の値が入っているので比べる必要がない)
	for (i = 1; i < Rotate; i++)
	{
		//patternがMINかつ、kyoriがresより小さいならば、resにkyoriをいれる。MAXも同様
		//こうすることで、minとmaxという変数を2つ用意する必要がなくなり、メモリにも優しい?
		if ((pattern == MIN && Target[i]<res) || (pattern == MAX && Target[i]>res))
		{
			res = Target[i];
		}
	}
	//最後にresをレス
	return res;
}

/*
長くなっているので、こうかな?と思ったところは関数内でコメントで記載しました。
やはりかなりスッキリした印象を受けます。
訳も分からず二次元配列を用いておりましたが、みけCAT様のソースを拝見し、如何に効率の悪い。あるいは非論理的なことをしようとしていたか、かなり勉強にさせて頂きました。
おおよそご指摘頂いた箇所は理解出来たと思います。 しかし
エラーがでました。
*/

//エラー---------------------------------------------------------------------------------------------------------------------------------

//エラーが出ている箇所

	for (i = 0; i < CHARA_MAX; i++)
	{

		//最小値をminに入れる
		min[i] = MostValue2(kyori[i], POINT_MAX, MIN);

		//最大値をmaxに入れる
		max[i] = MostValue2(kyori[i], POINT_MAX, MAX);

	}

/*
この、
min[i] = MostValue2(kyori[i], POINT_MAX, MIN);
max[i] = MostValue2(kyori[i], POINT_MAX, MAX);
という部分において、

warning C4047: '関数' : 間接参照のレベルが 'const int *' と 'int' で異なっています。
warning C4024: 'MostValue2' : の型が 1 の仮引数および実引数と異なります。
warning C4047: '関数' : 間接参照のレベルが 'const int *' と 'int' で異なっています。
warning C4024: 'MostValue2' : の型が 1 の仮引数および実引数と異なります。

といったコンパイルエラーがでます。

そこで、
min[i] = MostValue2(kyori, POINT_MAX, MIN);
max[i] = MostValue2(kyori, POINT_MAX, MAX);
のように、[i]を消すとコンパイルは通りますが、実行時にエラーが出ます。
そもそも、kyori[]という配列の中身は[CHARA_MAX * POINT_MAX]なのに、
for分でCHARA_MAX 回数分しか回さないのは、間違いでした・・・
そこで、大幅に改良を加えました

*/

//私なりの改良---------------------------------------------------------------------------------------------------------------

//まず、関数の形から変更いたしました。

//変更後
int MostValue2(const int Target[], int Rotate, int pattern);
//変更後の更に変更後
int MostValue2(const int Target[], int Rotate1, int Rotate2, int pattern);

/*
結局Rotate1.2に戻ってしまう結果になりました。
なぜこのような形にせざるを得なかったのか。それは
*/

//変更後
	for (i = 0; i < CHARA_MAX; i++)
	{

		//最小値をminに入れる
		min[i] = MostValue2(kyori[i], POINT_MAX, MIN);

		//最大値をmaxに入れる
		max[i] = MostValue2(kyori[i], POINT_MAX, MAX);

	}

//変更後の更に変更後

	for (i = 0; i < CHARA_MAX; i++)
	{

		//最小値をminに入れる
		min[i] = MostValue2(kyori, i, POINT_MAX, MIN);

		//最大値をmaxに入れる
		max[i] = MostValue2(kyori, i, POINT_MAX, MAX);


	}

/*
エラーの項で述べた部分なのですが、
kyori[i]は、そもそもCHARA_MAX(3)と、POINT_MAX(10)の総計3*10で30個の要素数によってできており、形にすると
kyori[POINT_MAX * i + j]。となります。(iはキャラクターの数分。jはポイントの数分)
例えば
キャラクター0番目と、ポイント7番目を参照するなら、kyori[POINT_MAX * 0 + 7]
キャラクター1番目と、ポイント3番目を参照するなら、kyori[POINT_MAX * 1 + 3]
キャラクター2番目と、ポイント10番目を参照するなら、kyori[POINT_MAX * 2 + 10]
といった形にすると、変更後の関数では、足りない部分があるかもしれないと思い、関数の本体を以下のように改良いたしました
*/

//◆変更後
int MostValue2(const int Target[], int Rotate, int pattern)
{
	int i;
	int res;	//minまたはmaxを返す(修正前はminとmaxという二つの動的変数を定義してしまっていた)

	//kyoriが0、またはRotateが0以下(つまり繰り返すことができない)、patternがMINでもMAXでもない時
	if (Target == NULL || Rotate <= 0 || (pattern != MIN && pattern != MAX))
	{
		//-999を返す
		return -999;
	}
	//比較するため、resに配列の先頭アドレスを入れる
	res = Target[0];
	//Rotate分繰り返す(今回ならポイントの数分。i = 1となっているのは、resにはすでに配列の0番目の値が入っているので比べる必要がない)
	for (i = 1; i < Rotate; i++)
	{
		//patternがMINかつ、kyoriがresより小さいならば、resにkyoriをいれる。MAXも同様
		//こうすることで、minとmaxという変数を2つ用意する必要がなくなり、メモリにも優しい?
		if ((pattern == MIN && Target[i]<res) || (pattern == MAX && Target[i]>res))
		{
			res = Target[i];
		}
	}
	//最後にresをレス
	return res;
}

//◆変更後の更に変更後

int MostValue2(const int Target[], int Rotate1, int Rotate2, int pattern)
{
	int i;
	int res;	//minまたはmaxを返す(修正前はminとmaxという二つの動的変数を定義してしまっていた)

	//kyoriが0、またはRotate2が0以下(つまり繰り返すことができない)、patternがMINでもMAXでもない時
	if (Target == NULL || Rotate2 <= 0 || (pattern != MIN && pattern != MAX))
	{
		//-999を返す
		return -999;
	}
	//比較するため、resに配列の先頭アドレス(Rotate1は0~2(キャラクター数3体))を入れる
	res = Target[Rotate1 * Rotate2];
	//Rotate2分繰り返す。+1としているのは、すでに配列の先頭の値が入っているため、比べる必要がない。
	for (i = (Rotate1 * Rotate2) + 1; i < (Rotate1 + 1) * Rotate2; i++)
	{
		//patternがMINかつ、kyoriがresより小さいならば、resにkyoriをいれる。MAXも同様
		//こうすることで、minとmaxという変数を2つ用意する必要がなくなり、メモリにも優しい?
		if ((pattern == MIN && Target[i] < res) || (pattern == MAX && Target[i] > res))
		{
			res = Target[i];
		}
	}
	//最後にresをレス
	return res;
}

/*
このように改良することで、初めに比較する(0番目)を、配列通りに合わせたつもりなのですが。
こうすると、やはりコンパイルは問題なく通るのですが
実行時にエラーが出ます。
オーバーフローを起こしているのでしょうか。。
*/



解決には至っていませんが、考え方は分かってきています。
しかし、やはりコンパイルは通るのですが、実行の段階でエラーが出てしまいます。
上記のプログラム内で示した箇所以外は変更しておりません。
やはりなにか根本的に可笑しな点があるのでしょうか。。

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

Re: 二次元配列のデータの最大値、最小値を求める

#7

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

最大値と最小値は提示された変更点を適用したプログラムと自分が提示した最大/最小値を求めるプログラムで一致しており、
デバッガでkyori配列の値を確認しましたが、正しい最大/最小値が求まっているようでした。
しかし、それ以外の部分で以下の問題が見つかりました。(行番号はNo: 1のコードのもの)

・76,78,80行目:printfのフォーマット文字列と引数の数があっていません。
・160行目:charaはCHARA_MAX(==3)要素しか確保していないのに、chara[POINT_MAX(==10)-1]までアクセスしており、未定義の挙動になる。
・186,187行目:pointはPOINT_MAX(==10)要素しか確保していないのに、point[10]にアクセスしており、添字が範囲外である。

参考:コンパイラの警告メッセージ

コード:

YUKI.N>gcc --version
gcc (GCC) 4.8.1
Copyright (C) 2013 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.


YUKI.N>gcc -O2 -o 2darr_minmax_raw2_o2 2darr_minmax_raw2.c -Wall -Wextra
2darr_minmax_raw2.c:34:1: warning: return type defaults to 'int' [-Wreturn-type]

 main()
 ^
2darr_minmax_raw2.c: In function 'main':
2darr_minmax_raw2.c:77:2: warning: format '%d' expects a matching 'int' argument
 [-Wformat=]
  printf("%d,%d,%d\n", min[0], max[0]);
  ^
2darr_minmax_raw2.c:79:2: warning: format '%d' expects a matching 'int' argument
 [-Wformat=]
  printf("%d,%d,%d\n", min[1], max[1]);
  ^
2darr_minmax_raw2.c:81:2: warning: format '%d' expects a matching 'int' argument
 [-Wformat=]
  printf("%d,%d,%d\n", min[2], max[2]);
  ^
2darr_minmax_raw2.c:85:1: warning: control reaches end of non-void function [-Wr
eturn-type]
 }
 ^
2darr_minmax_raw2.c: In function 'Init':
2darr_minmax_raw2.c:153:7: warning: array subscript is above array bounds [-Warr
ay-bounds]
  point[10].x = 160;
       ^
2darr_minmax_raw2.c:154:7: warning: array subscript is above array bounds [-Warr
ay-bounds]
  point[10].y = 420;
       ^
2darr_minmax_raw2.c:129:14: warning: iteration 3u invokes undefined behavior [-W
aggressive-loop-optimizations]
   chara[i].x = 530 + (i * 30);
              ^
2darr_minmax_raw2.c:127:2: note: containing loop
  for (i = 0; i < POINT_MAX; i++)
  ^

YUKI.N>
添付ファイル
2darr_minmax_raw2.c
提示された変更点を適用したプログラム
(4.73 KiB) ダウンロード数: 104 回
2darr_minmax_test.c
自分が提示した最大/最小値を求めるプログラム
(4 KiB) ダウンロード数: 111 回
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

Look
記事: 3
登録日時: 11年前
住所: 関西

Re: 二次元配列のデータの最大値、最小値を求める

#8

投稿記事 by Look » 11年前

基本的なミスでしたね><

やはり、配列の存在しない領域までアクセスしてオーバーフローを引き起こしていたようです

最大値。最小値を求めるプログラムはみけCAT様が提示された構文で問題ありませんでした。

以下修正した箇所

コード:


//修正前
	//キャラクター1の場合
	printf("%d,%d,%d\n", min[0], max[0]);
	//キャラクター2の場合
	printf("%d,%d,%d\n", min[1], max[1]);
	//キャラクター3の場合
	printf("%d,%d,%d\n", min[2], max[2]);
//修正後
	//キャラクター1の場合
	printf("%d,%d\n", min[0], max[0]);
	//キャラクター2の場合
	printf("%d,%d\n", min[1], max[1]);
	//キャラクター3の場合
	printf("%d,%d\n", min[2], max[2]);

/*
こちらはみけCAT様の仰ったとおり、フォーマットの指定した文字数と実際の引数が照合していなかったようでした。
実行時に0という値が挿入されるだけで、根本的なエラーの原因ではありませんでした。
*/

//修正前
	for (i = 0; i < POINT_MAX; i++)
	{
		chara[i].x = 530 + (i * 30);
		chara[i].y = 30;
	}
//修正後
	for (i = 0; i < CHARA_MAX; i++)
	{
		chara[i].x = 530 + (i * 30);
		chara[i].y = 30;
	}

/*
こちらも、charaという配列の中身はCHARA_MAX(3)数分しかないのにも関わらず。
誤ってPOINT_MAX(10)という数でアクセスしていたことがエラーの原因となっていました。
*/

//修正前
   point[0].x = 160;
	point[0].y = 250;
	point[1].x = 50;
	point[1].y = 250;
	point[2].x = 160;
	point[2].y = 120;
	point[3].x = 290;
	point[3].y = 80;
	point[4].x = 450;
	point[4].y = 80;
	point[5].x = 290;
	point[5].y = 300;
	point[6].x = 450;
	point[6].y = 300;
	point[7].x = 570;
	point[7].y = 300;
	point[8].x = 570;
	point[8].y = 100;
	point[9].x = 50;
	point[9].y = 420;
	point[10].x = 160;
	point[10].y = 420;
//修正後
	point[0].x = 160;
	point[0].y = 250;
	point[1].x = 50;
	point[1].y = 250;
	point[2].x = 160;
	point[2].y = 120;
	point[3].x = 290;
	point[3].y = 80;
	point[4].x = 450;
	point[4].y = 80;
	point[5].x = 290;
	point[5].y = 300;
	point[6].x = 450;
	point[6].y = 300;
	point[7].x = 570;
	point[7].y = 300;
	point[8].x = 570;
	point[8].y = 100;
	point[9].x = 50;
	point[9].y = 420;

/*
こちらも同様にみけCAT様の仰る通り、配列の存在しない領域までアクセスしていました。
エラーの主原因となったのはこの誤りによりオーバーフローを引き起こしていたから。ということでした。
*/
以上のプログラムの修正で実行結果はきちんと求まりました。
十分に理解出来ました。
みけCAT様。プログラムも添えて、御丁寧な解説有り難う御座いました。

閉鎖

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