循環リストについてなんですが・・

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

循環リストについてなんですが・・

#1

投稿記事 by ひよこ » 16年前

最近興味を持ったことについて勉強をしています。
循環リストについてなんですが・・
「ためになるホームページ」のサンプルを改造していて
なぜ下のソースの実行結果の一番最後がbullet.x,bullet.yの内容なのでしょうか?
C言語はほとんどできません。
#include <stdio.h>
#include <malloc.h>

struct bullet_t{
	struct bullet_t *next;      /* 次の構造体変数のアドレス */
	int x;
	int y;
};

bullet_t bullet;

void List_print(bullet_t *head){
  bullet_t *p, *tmp;
  for(p=head->next;p!=head;)
  {
    tmp=p;
    p=tmp->next;
    printf("%d\n",p->x);
	printf("%d\n",p->y);
  }
}

int bullet_data_set(bullet_t *p,int x,int y){
	bullet_t *tmp;
	tmp = (bullet_t*)malloc(sizeof(bullet_t));
	//要素が存在した場合
	if(p->next!=NULL)
	{
		tmp->next=p->next;
		p->next=tmp;
		tmp->x=x;
		tmp->y=y;
	}
	else//要素がない場合
	{
		tmp->next=p;//pのアドレスをtmp->nextへ
		p->next=tmp;//tmpのアドレスをp->nextへ
		tmp->x=x;
		tmp->y=y;
	}
	return 0;
}

int main(){
	bullet.next=NULL;
	
	bullet.x=0;
	
	bullet.y=0;
	
	bullet_data_set(&bullet,80,90);//一番最後,後ろに行くほど先描く
	
	bullet_data_set(bullet.next,60,80);
	
	bullet_data_set(bullet.next,80,120);
	
	List_print(&bullet);//リスト内容を描く
	
	getchar();//一時停止
	
	return 0;
}

kazuoni

Re:循環リストについてなんですが・・

#2

投稿記事 by kazuoni » 16年前

>なぜ下のソースの実行結果の一番最後がbullet.x,bullet.yの内容なのでしょうか?



そのように関数が組んであるからですが・・・?

たぶんこういうことがやりたいのでは?っと勝手に予想してみます^^;
void List_print(bullet_t *head){

  bullet_t *p, *tmp;

  for(p=head->next;p!=head;p=p->next)

  {

    tmp=p;

    printf("%d\n",p->x);

	printf("%d\n",p->y);

  }

}

ひよこ

Re:循環リストについてなんですが・・

#3

投稿記事 by ひよこ » 16年前

kazuoniさん
たぶんこういうことがやりたいのでは?っと勝手に予想してみます^^;
>>実行結果が自分のおもったとおりになってます。ありがとうございます。
やりたいことは、弾の表示負荷を減らすため、リストを勉強しています。
弾を描き、その弾を全部一度に消す。そしてまた弾を描くということがしたいです。

kazuoniさんのすばらしいコードを変えていっているのですが、
リスト.exe の 0x6485c2b2 で初回の例外が発生しました: 0xC0000005: 場所 0xfeeefee8 を読み込み中にアクセス違反が発生しました。
リスト.exe の 0x6485c2b2 でハンドルされていない例外が発生しました: 0xC0000005: 場所 0xfeeefee8 を読み込み中にアクセス違反が発生しました。というエラーが出てきました。
どういう原因なんでしょうか?
下ソースです。
#include <stdio.h>
#include <malloc.h>

struct bullet_t{
	struct bullet_t *next;      /* 次の構造体変数のアドレス */
	int x;
	int y;
};

bullet_t bullet;

void List_print(bullet_t *head){
 bullet_t *p, *tmp;

  for(p=head->next;p!=head;p=p->next)
  {
    tmp=p;
    printf("%d\n",p->x);
	printf("%d\n",p->y);
  }
}

int bullet_data_set(bullet_t *p,int x,int y){
	bullet_t *tmp;
	tmp = (bullet_t*)malloc(sizeof(bullet_t));
	//要素が存在した場合
	if(p->next!=NULL)
	{
		tmp->next=p->next;
		p->next=tmp;
		tmp->x=x;
		tmp->y=y;
	}
	else//要素がない場合
	{
		tmp->next=p;//pのアドレスをtmp->nextへ
		p->next=tmp;//tmpのアドレスをp->nextへ
		tmp->x=x;
		tmp->y=y;
	}
	return 0;
}/*
int deletebullet(bullet_t* p)
{
  bullet_t *tmp;
  if(p->next==NULL)
    return -1;
  tmp = p->next;
  p->next = tmp->next;
  free(tmp);
  return 0;
}*/
void allDeletebullet(bullet_t *head)
{
  bullet_t *p, *tmp;

  for (p=head->next;p!=head;p=p->next)
  {
    tmp = p;
    free(tmp);
  }

}

int main(){
	bullet.next=NULL;
	
	bullet.x=0;
	
	bullet.y=0;
	
	bullet_data_set(&bullet,80,90);//一番最後,後ろに行くほど先描く
	
	bullet_data_set(bullet.next,60,80);
	
	bullet_data_set(bullet.next,80,120);
	
	List_print(&bullet);//リスト内容を描く
	allDeletebullet(&bullet);
	List_print(&bullet);//リスト内容を描く
	getchar();//一時停止
	
	return 0;
}

kazuoni

Re:循環リストについてなんですが・・

#4

投稿記事 by kazuoni » 16年前

>どういう原因なんでしょうか?

えっと、原因は、tmpにpのノードのアドレスを代入し、
freeしてしまうと、pのアドレス先を解放したのと同じになるので、
解放後、pをnextすると、不正のアドレスを指すことになり、
そのアドレスを解放するときに解放ができていないのが原因です。

こんな感じになるかと
void allDeletebullet(bullet_t *head)
{
  bullet_t *p, *tmp;
  for (p=head->next;p!=head;)
  {
    tmp = p;
    p=p->next;
    free(tmp);
  }

  p->next=NULL;
}

あと、リストはまず紙に書いて考えることをお勧めします。
そして、組み、あとはひたすらデバッグで状況を追って行ってください。
そうすれば早くリスト構造の仕組みが理解でき、
はかどるかと思いますよ。
ネットでいろいろ調べてみてもいいかも知れません。

ひよこ

Re:循環リストについてなんですが・・

#5

投稿記事 by ひよこ » 16年前

そうですね、紙に書いていくことから始めたいと思います。
kazuoniさんありがとうございました。

ひよこ

Re:循環リストについてなんですが・・

#6

投稿記事 by ひよこ » 16年前

解決押し忘れました。

ひよこ

Re:循環リストについてなんですが・・

#7

投稿記事 by ひよこ » 16年前

ただいま勉強してたところなんですが、(backを使わないけれど入れてみたりしてリストの勉強してました。)allDeletebullet関数直後にList_printがとおると、
不正なアドレスを指定するのでList_printにif(p->next!=NULL)をいれたら
Run-Time Check Failure #3 - The variable 'p' is being used without being initialized.
きっと初期化されてないpが使われているって意味なんだと思うのですが、
どう初期化すればいいでしょうか?
#include <stdio.h>
#include <malloc.h>

//注意!「/*:*/」は*backのアドレス関係の行であることを表します。
struct bullet_t{
	struct bullet_t *next;      /* 次の構造体変数のアドレス */
	struct bullet_t *back; 
	int x;
	int y;
};

bullet_t bullet;

void List_print(bullet_t *head){
 bullet_t *p;
 if(p->next!=NULL){//allDeletebullet関数直後に描かないようにする
  for(p=head->next;p!=head;p=p->next)
  {
	//printf("%d\n",p->back);
	printf("%d\n",p->x);
	printf("%d\n",p->y);
  }
 }
}

int bullet_data_set(bullet_t *p,int x,int y){
	bullet_t *tmp;
	tmp = (bullet_t*)malloc(sizeof(bullet_t));
	//要素が存在した場合
	if(p->next!=NULL)
	{
		tmp->next=p->next;
		
		/*:*/tmp->back=p;
		
		p->next=tmp;
		
		/*:*/p->back=tmp->next;
		
		tmp->x=x;
		
		tmp->y=y;
		
	}
	else//要素がない場合
	{
		tmp->next=p;//pのアドレスをtmp->nextへ
		
		/*:*/tmp->back=p;
		
		p->next=tmp;//tmpのアドレスをp->nextへ
		
		/*:*/p->back=tmp;
		
		tmp->x=x;
		
		tmp->y=y;
		
	}
	return 0;
}
void allDeletebullet(bullet_t *head)
{
  bullet_t *p, *tmp;
  for (p=head->next;p!=head;)
  {
    tmp = p;
    p=p->next;
    free(tmp);
  }

  p->next=NULL;//pがheadと一緒のアドレスになったときにその先のアドレスを
			   //NULLにする。
}

int main(){
	bullet.next=NULL;
	
	bullet.x=0;
	
	bullet.y=0;
	
	bullet_data_set(&bullet,80,90);//一番最後,後ろに行くほど先描く
	
	bullet_data_set(bullet.next,60,80);
	
	bullet_data_set(bullet.next,80,120);
	
	List_print(&bullet);//リスト内容を描く
	
	allDeletebullet(&bullet);
	
	List_print(&bullet);//リスト内容を描く
	getchar();//一時停止
	
	return 0;
}

kazuoni

Re:循環リストについてなんですが・・

#8

投稿記事 by kazuoni » 16年前

if(p->next!=NULL)

if(head->next!=NULL)
の間違えじゃないですか?

ひよこ

Re:循環リストについてなんですが・・

#9

投稿記事 by ひよこ » 16年前

できました。なんで、引数が初期化(bullet_t *head;)されて、中で同じようなこと(
bullet_t *p;)をしているのに、pじゃ、だめなんですか?

kazuoni

Re:循環リストについてなんですが・・

#10

投稿記事 by kazuoni » 16年前

※以下のコードは例えであって、
有効な関数ではないので、使わないでください。
void func(int a)
{
	int b;

	printf("a=%d",a);
	printf("b=%d",b);
}
上に示した関数でaとbは等しいですか?
とは限らないですよね。
bは初期化されていないintです。
bullet_tは構造体ですが、intやcharと同じ「型」と思ってください。

ひよこ

Re:循環リストについてなんですが・・

#11

投稿記事 by ひよこ » 16年前

headは引数で初期化されているけど、pはつくっただけみたいなことですか?

kazuoni

Re:循環リストについてなんですが・・

#12

投稿記事 by kazuoni » 16年前

そういうことですね。

入門サイトの「関数」の項目を見ておいたほうがいいかも知れません。
まだ、理解できていないor知らない事があるかもしてません。

ひよこ

Re:循環リストについてなんですが・・

#13

投稿記事 by ひよこ » 16年前

そうだったんですか、ただいま、もし画面外に出たら弾を消すという作業をやっているのですが、
deletebullet(bullet_t *head)//外に出た弾を消すという関数を使ったとたんにアクセス違反となるのですが
どうすればいいでしょうか?おそらくアドレス関係だと思いますがどうすればいいでしょうか?
#include "DxLib.h"
#include <string.h>  /* memsetを使うため */
#include <math.h>

//円周率
#define PI 3.14159265358979323846

int Key[256];
int image[16];
int GetHitKeyStateAll_2(int GetHitKeyStateAll_InputKey[/url]){
    char GetHitKeyStateAll_Key[256];
    GetHitKeyStateAll( GetHitKeyStateAll_Key );
    for(int i=0;i<256;i++){
        if(GetHitKeyStateAll_Key==1) GetHitKeyStateAll_InputKey++;
        else                            GetHitKeyStateAll_InputKey=0;
    }
    return 0;
}
struct bullet_t{
	struct bullet_t *next;      /* 次の構造体変数のアドレス */
	struct bullet_t *back;		/* 前の構造体変数のアドレス(代入はしているけど未使用) */
	double x,y;  //座標
    double spd;//スピード
    double angle;//角度
	int flag;
};
bullet_t bullet;

void Bullet_draw(bullet_t *head){
 bullet_t *p;
 if(head->next!=NULL){//allDeletebullet関数直後に描かないようにする
  for(p=head->next;p!=head;p=p->next)
  {
	  if(p->flag==1){//アクセス違反が発生
	  p->x+=cos(p->angle)*(p->spd);
	  p->y+=sin(p->angle)*(p->spd);
	  DrawRotaGraphF( (float)p->x , (float)p->y , 1.0 , p->angle, image[8] , TRUE );	  
	  }
  }
 }
}
void allDeletebullet(bullet_t *head)
{
  bullet_t *p, *tmp;
  if(head->next!=NULL){
	  for (p=head->next;p!=head;)
	  {
		  tmp = p;
		  p=p->next;
		  free(tmp);
	  }
	  p->next=NULL;//pがheadと一緒のアドレスになったときにその先のアドレスを
			   //NULLにする。
  }
}
void deletebullet(bullet_t *head)//外に出た弾を消す
{
  bullet_t *p, *tmp;
  //pはhead->nextからpと同じアドレスになるまでループ
   if(head->next!=NULL){
	   for(p=head->next;p!=head;){	
		   if(p->flag==1){
			   if(p->x<0 || p->x<=640 || p->y<=0 || p->y>=640){
				   tmp = p;//tmpをpのアドレスに
				   p=p->next;//pのアドレスをp->nextにする。			  
				   free(tmp);//tmp(昔のp)を解放
			   }
		   }
	   }
   }
}

int bullet_data_set(bullet_t *p,double x,double y ,double angle,double spd){
	bullet_t *tmp;
	tmp = (bullet_t*)malloc(sizeof(bullet_t));
	//要素が存在した場合
	if(p->next!=NULL)
	{
		tmp->next=p->next;
		
		/*:*/tmp->back=p;
		
		p->next=tmp;
		
		/*:*/p->back=tmp->next;
		
		tmp->flag=1;
		
		tmp->x=x;
		
		tmp->y=y;
		
		tmp->spd=spd;
		
		tmp->angle=angle;
		
	}
	else//要素がない場合
	{
		tmp->next=p;//pのアドレスをtmp->nextへ
		
		/*:*/tmp->back=p;
		
		p->next=tmp;//tmpのアドレスをp->nextへ
		
		/*:*/p->back=tmp;
		
		tmp->flag=1;
		
		tmp->x=x;
		
		tmp->y=y;
		
		tmp->spd=spd;
		
		tmp->angle=angle;
	}
	return 0;
}
void ini(){
	memset(&bullet,0,sizeof(bullet_t));
	bullet.next=NULL;
	bullet.back=NULL;
	bullet_data_set(&bullet,80,90,PI,0);
}
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){
    ChangeWindowMode(TRUE);//ウィンドウモード
    if(DxLib_Init() == -1 || SetDrawScreen( DX_SCREEN_BACK )!=0) return -1;//初期化と裏画面化

    LoadDivGraph( "char.png" , 16 , 4 , 4 , 32 , 32 , image );//画像を分割してimage配列に保存
	ini();
	bullet_data_set(bullet.next,240,77,PI/2,3);
    while(ProcessMessage()==0 && ClearDrawScreen()==0 && GetHitKeyStateAll_2(Key)==0 && Key[KEY_INPUT_ESCAPE]==0){
          //↑メッセージ処理          ↑画面をクリア           ↑入力状態を保存       ↑ESCが押されていない

		if(Key[KEY_INPUT_Z]==1)
			deletebullet(&bullet);
		if(Key[KEY_INPUT_X]==1)
			allDeletebullet(&bullet);
		Bullet_draw(&bullet);
 
        ScreenFlip();
    }
    DxLib_End();
    return 0;
}

kazuoni

Re:循環リストについてなんですが・・

#14

投稿記事 by kazuoni » 16年前

とりあえず、データは二つ入っていますが、
zを押すと、データは二つとも消えます。(解放)
(逸れますが、p->x<0 || p->x<=640 ...はおかしくないですか?)
その次に、データが先頭のノードのみになります。(このノードのnextは解放後何もされていない。)
そのまま、Drawするので、head->nextにアクセスし、
p->flagとすると不正なアドレスのアクセスになり、止まります。

あと、今のままだと、例ですが
A→B→C→D→A....
のリストでBのみ削除対象になると、
本体のリストはAだけになります。
A->next=C
の処理をしていないので。

今一度、リスト構造の基礎を見直してみてください。

ひよこ

Re:循環リストについてなんですが・・

#15

投稿記事 by ひよこ » 16年前

ただいま試行錯誤でやっています。

p->x<0 || p->x<=640 ...はおかしくないですか?)
>>おかしいですね、直しました。

デバックでZでdeletebulletを使うとなぜかフリーズするのですが、どうしてでしょうか
一回もprintfDxが動いた跡がありません。

void deletebullet(bullet_t *head)//外に出た弾を消す
{
bullet_t *p, *tmp;
//pはhead->nextからpと同じアドレスになるまでループ
if(head->next!=NULL){
for(p=head->next;p!=head;){
printfDx("一回きました");
if(p->flag==1){//アクセス違反
printfDx("一回きました");
if(p->x <=0 || p->x >=640 || p->y <=0 || p->y >=480){//xが0以下640以上だったら,yが0以下480以上だったら
printfDx("一回きました");
tmp = p;//tmpをpのアドレスに
p=p->next;//pのアドレスをp->nextにする。
free(tmp);//tmp(昔のp)を解放
}
}
}
}
}

kazuoni

Re:循環リストについてなんですが・・

#16

投稿記事 by kazuoni » 16年前

とまる理由、アクセス違反が起こる理由は
自分で調べれるようにならないと、
この先辛いのでまずはそれから。
環境が何か分かりませんが、VC++と仮定します。
とりあえず、止まる場所がだいたいどのあたりか、
目星をつけます。(止まるところがどこかはデバッグすればわかりますよね?)
あとはその前をひたすら追っていきます。
VC++でいう、ブレークポイントです。
行の左に赤丸が出ます。そこでは条件停止、無条件停止などさまざまな止め方があるので、
これで今は十分プログラム停止の原因がつかめます。

今回の件ですが、
ブレークポイントをはれば分かる通り、
無限ループに陥る場合があります。
(キャラが画面内にいるとき)
if(p->x <=0 || p->x >=640 || p->y <=0 || p->y >=480)
に初めがヒットせず、そのまま無限ループです。
ScreenFlipにたどり着かないので、
表示がされることはありません。
解決策は、
p=p->next;//pのアドレスをp->nextにする。
を外に出してください。

また前回も書きましたが、これだと、
deleteには不十分な構造です。
今一度、設計を見なおしてください。

ひよこ

Re:循環リストについてなんですが・・

#17

投稿記事 by ひよこ » 16年前

もう一回勉強したらできました。
これから少しずつリストについて勉強しようと思います。
kazuoniさんありがとうございました。

閉鎖

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