STGで頻繁にインスタンスの生成、削除を繰り返してもメモリの断片化が起きないように、クラスを決められた場所に作ったリスト構造で管理しようとしています。
TaskList , Task という2つのクラスを作り、TaskListのインスタンス作成時に必要になりそうな領域(sizeof(Task) * 予想されるタスク数n)をまとめて確保し、その領域をsizeof(Task)ごとに区切り、合わせてn個のTaskのインスタンスをメモリ上に等間隔に作りました。
使用中タスクのブロックと空きタスクのブロックに分けて、前者を双方向リストで、後者を単方向リストで管理します。前者はの先頭はbegin_used、後者の先頭はbegin_freeとしてTaskListクラスのstatic const なフィールドとして記憶されます。TaskからShip(自機)、Shot(自機ショット)クラスに派生させ、nextポインタをbegin_usedから始まってまたbegin_usedに戻ってくるまでmove() や draw() といった動作をさせます。
Taskクラスのnew演算子を呼ぶことで、空きタスクリストの先頭の次へのポインタを返し、そのポインタの指すインスタンスを空きタスクリストの連結でスキップされるようにオーバーライドしました。
次に、Taskのコンストラクタで使用中リストにthisを挿入したいのですが、ここが思い通りにできません。画像は動作をVisualStudioのデバッガで確認しているところなのですが、3枚目→4枚目の代入が思うようにいっていないようです。
なぜこのようなことが起こるのですか?
画像 → http://www1.axfc.net/uploader/so/2794200
画像は5枚あり、パスワード付きzipでまとめてあります。
DL時のキーワードは adu 、展開時のキーワードは aduaduadu です。
よろしくお願いします。
ポインタがズレて代入されてしまう
- softya(ソフト屋)
- 副管理人
- 記事: 11677
- 登録日時: 15年前
- 住所: 東海地方
- 連絡を取る:
Re: ポインタがズレて代入されてしまう
シンプルな再現コードを用意出来ませんか?
部分的なコードではよくわからないです。
部分的なコードではよくわからないです。
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。
-
adu
Re: ポインタがズレて代入されてしまう
始めから貼るべきでした。
Vector r は位置ベクトル、Vector v は速度ベクトル、 ~func は move() で呼ばれる関数ポインタです。null なら r+=v をします。
TaskクラスとShip, Shotクラスの間にMoverクラスがあります。
Ship.h
Task.h
Task.cpp
Vector r は位置ベクトル、Vector v は速度ベクトル、 ~func は move() で呼ばれる関数ポインタです。null なら r+=v をします。
TaskクラスとShip, Shotクラスの間にMoverクラスがあります。
Ship.h
#pragma once
#include "mover.h"
#include <cassert>
class Shot;
extern TaskList ship_list;
extern TaskList shot_list;
class Ship : public Mover{
public:
Ship( int graphics_,
Vector r_ = Vector(0,0),
Vector v_ = Vector(0,0),
double collision_radius_ = 5,
double speed_fast_ = 1,
double speed_slow_ = 0.5,
unsigned int life_ = 2,
unsigned int power_ = 1,
unsigned int bomb_ = 2 ,
void (*generate_shot_func_) (Mover*) = 0)
: Mover(graphics_ , r_ , v_,Circle(0.2)) ,
speed_fast(15.0) , speed_slow(speed_slow_) , life(life_) , power(power_) , bomb(bomb_), generate_shot_func(generate_shot_func_){
//リストの末尾に挿入
this->next = ship_list.begin_used;
this->prev = ship_list.begin_used->prev;
ship_list.begin_used->prev->next = this;
ship_list.begin_used->prev = this;
this->task_list = &ship_list;
//基本的にコントローラー制御
operate_move_func = 0;
}
void *operator new(size_t s){
return ship_list.fetch();
}
void operator delete(void* p){
ship_list.erase(p);
}
void move() override;
virtual void shot();
void (*generate_shot_func) (Mover*);
protected:
double speed_fast;
double speed_slow;
unsigned int life;
unsigned int power;
unsigned int bomb;
};
class Shot : public Mover{
public:
Shot( int graphics_,
Vector r_ = Vector(0,0),
Vector v_ = Vector(0,80),
double collision_radius_ = 5,
unsigned int power_ = 1
)
: Mover(graphics_ , r_ , v_,Circle(collision_radius_)) ,
power(power_) {
//リストの末尾に挿入
this->next = shot_list.begin_used;
this->prev = shot_list.begin_used->prev;
shot_list.begin_used->prev->next = this;
shot_list.begin_used->prev = this;
this->task_list = &shot_list;
//TODO : 引数に取れるようにする
operate_move_func = 0;
}
void* operator new(size_t s){
return shot_list.fetch();
}
void operator delete(void* p){
shot_list.erase(p);
}
virtual void move() override;
double power;
};
#pragma once
class Task;
class TaskList;
class TaskIter;
class Task{
friend class TaskList;
friend class TaskIter;
friend class Ship;
friend class Shot;
public:
Task(){}
Task(TaskList &tl);
protected:
Task* next;
Task* prev;
TaskList* task_list;
void* operator new(size_t t){};
void operator delete(void* p){};
};
class TaskList{
friend class TaskIter;
friend class Ship;
friend class Shot;
public:
//freeは単方向リスト usedは双方向リスト
TaskList(size_t,unsigned int);
Task* fetch();
void erase(void*);
unsigned int size();
bool empty();
bool full();
void move();
void draw();
protected:
Task* at(unsigned int);
Task* begin_free;
Task* begin_used;
unsigned int max_task_num;
unsigned int task_num;
size_t size_task;
};
class TaskIter{
public:
TaskIter(const TaskList*);
const TaskList* operating_task_list;
Task* target();
Task* next();
Task* prev();
void proceed();
bool end();
protected:
Task* target_task;
};
#include "Task.h"
#include "Mover.h"
#include "DxLib.h"
#include <cassert>
TaskList::TaskList(size_t size_, unsigned int num_){
max_task_num=num_;
task_num=0;
size_task=size_;
//動的に確保 charは1バイトなのでそのまま乗算
char* buffer = new char[size_*(num_+2)];
//初期状態 : begin_used , begin_free , フリータスク , フリータスク , フリータスク ...
begin_free=(Task*)(buffer+size_);
begin_used=(Task*)buffer;
//リスト構造を作る
//freeは単方向リスト usedは双方向リスト
//usedリストはこのような状態が空であることをあらわす
begin_used->next = begin_used;
begin_used->prev = begin_used;
begin_used->task_list = (TaskList*)buffer;
//freeリストは単方向なのでprevはさわらなくてよいはず(?)
for(unsigned int i=1; i<num_+1; i++){
Task* free_task = at(i);
free_task->next = at(i+1);
free_task->task_list = (TaskList*)buffer;
}
at(num_+1)->next = at(1);
at(num_+1)->task_list = (TaskList*)buffer;
}
Task* TaskList::fetch(){
//満杯ならエラー
assert( !full() );
//freeの先頭から抜き出し
Task* rtn_task = begin_free->next;
begin_free->next = begin_free->next->next;
//used末尾への挿入はコンストラクタで行う
rtn_task->task_list = this;
task_num++;
return rtn_task;
}
void TaskList::erase(void* useless_task){
//空ならエラー
assert(task_num>0);
Task* p=static_cast<Task*>(useless_task);
//usedから削除
p->prev->next = p->next;
p->next->prev = p->prev;
//free先頭へ挿入
p->next = begin_free->next;
begin_free->next = p;
task_num--;
return;
}
Task* TaskList::at(unsigned int n){
return (Task*)( (char*)begin_used + n*size_task );
}
unsigned int TaskList::size(){
return task_num;
}
bool TaskList::full(){
return begin_free->next == begin_free;
}
bool TaskList::empty(){
return begin_used->next == begin_used;
}
void TaskList::move(){
for(TaskIter ti(this); !ti.end(); ti.proceed() ){
Mover* p = static_cast<Mover*>(ti.target());
p->move();
}
}
void TaskList::draw(){
for(TaskIter ti(this); !ti.end(); ti.proceed() ){
Mover* p = static_cast<Mover*>(ti.target());
p->draw();
}
}
TaskIter::TaskIter(const TaskList* tl){
operating_task_list = tl;
target_task = tl->begin_used->next;
}
Task* TaskIter::target(){
return target_task;
}
Task* TaskIter::next(){
return target_task->next;
}
void TaskIter::proceed(){
target_task = next();
}
bool TaskIter::end(){
return target_task == operating_task_list->begin_used;
}
- softya(ソフト屋)
- 副管理人
- 記事: 11677
- 登録日時: 15年前
- 住所: 東海地方
- 連絡を取る:
Re: ポインタがズレて代入されてしまう
このままだと動かないので再現コードとはいえませんので、バグが再現できるmain もつけて下さいね。
あと、もう少し余分なものは削ってあるとベストです。
あと、もう少し余分なものは削ってあるとベストです。
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。
Re: ポインタがズレて代入されてしまう
sizeof(Task)のサイズのメモリ領域に、ShipクラスやShotクラスを構築したら確実にオーバーランしますけど、そんな単純な話ではないですよね。adu さんが書きました:TaskList , Task という2つのクラスを作り、TaskListのインスタンス作成時に必要になりそうな領域(sizeof(Task) * 予想されるタスク数n)をまとめて確保し、その領域をsizeof(Task)ごとに区切り、合わせてn個のTaskのインスタンスをメモリ上に等間隔に作りました。
コンストラクタで初期化されていない領域をクラスオブジェクトとしてアクセスしていますし、期待通り動いたとしてもたまたまかと。
- softya(ソフト屋)
- 副管理人
- 記事: 11677
- 登録日時: 15年前
- 住所: 東海地方
- 連絡を取る:
Re: ポインタがズレて代入されてしまう
あぁ、ここで確保しているんですね。
char* buffer = new char[size_*(num_+2)];
コンストラクタも動作していないし動いているのが奇跡かもしれません。
char* buffer = new char[size_*(num_+2)];
コンストラクタも動作していないし動いているのが奇跡かもしれません。
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。
Re: ポインタがズレて代入されてしまう
代入自体は間違っていないみたいですけど。adu さんが書きました:次に、Taskのコンストラクタで使用中リストにthisを挿入したいのですが、ここが思い通りにできません。画像は動作をVisualStudioのデバッガで確認しているところなのですが、3枚目→4枚目の代入が思うようにいっていないようです。
thisのアドレスと実際に代入されるアドレスが違うという話ですかね。
Task*へアップキャストされるためでしょうけど。
きちんとクラスオブジェクトの構築・解体していないのでnext,prevメンバのオフセットのズレを吸収できてないということですかね。
Re: ポインタがズレて代入されてしまう
オーバーライドしたnewで返す値と、インスタンス化した際のnewの戻り値(あるいはthis)が全部一致する前提で書かれていますよね。
これらのポインタすべてがクラスオブジェクト構築のために確保されたメモリ領域の先頭を指すとは限らないので、このコードではnext,prevの内容が(全体を通して見た場合)保証されません。
コンテナを用意して、コンテナ内部に確保したメモリ領域にクラスオブジェクトを構築する必要があります。
next,prevはコンテナのメンバとし、クラスオブジェクトのライフサイクルの影響を受けないようにします。
わたしのブログに同じようなものを作ろうとした記事があります。
中途で放置されてますが、基本は押さえていると思うので参考にできるところは参考にしてください。
これらのポインタすべてがクラスオブジェクト構築のために確保されたメモリ領域の先頭を指すとは限らないので、このコードではnext,prevの内容が(全体を通して見た場合)保証されません。
コンテナを用意して、コンテナ内部に確保したメモリ領域にクラスオブジェクトを構築する必要があります。
next,prevはコンテナのメンバとし、クラスオブジェクトのライフサイクルの影響を受けないようにします。
わたしのブログに同じようなものを作ろうとした記事があります。
中途で放置されてますが、基本は押さえていると思うので参考にできるところは参考にしてください。
-
adu
Re: ポインタがズレて代入されてしまう
すみませんsoftya(ソフト屋) さんが書きました:このままだと動かないので再現コードとはいえませんので、バグが再現できるmain もつけて下さいね。
あと、もう少し余分なものは削ってあるとベストです。
thisポインタは配列のように単純に先頭を返すとは限らないのですか。
内部的に this は xxx+t 、xxx->next は *(xxx + n) 、 xxx->prev は *(xxx + p) のようになっているのでしょうか…(+はビット単位の移動)
そしてそのズレ(t,n,mなど)は継承によって変わってくるのでしょうか…
softya(ソフト屋) さんが書きました:コンストラクタも動作していないし動いているのが奇跡かもしれません。
これらのご回答についてはnewで空きクラスが返された直後(?)にコンストラクタが呼ばれるはずだから大丈夫だと思っていたのですが…ISLe さんが書きました:コンストラクタで初期化されていない領域をクラスオブジェクトとしてアクセスしていますし、期待通り動いたとしてもたまたまかと。
紹介された方法などいろいろやってみます
Re: ポインタがズレて代入されてしまう
クラスのメンバの並びやオフセットがどうなるかは分かりません。adu さんが書きました:内部的に this は xxx+t 、xxx->next は *(xxx + n) 、 xxx->prev は *(xxx + p) のようになっているのでしょうか…(+はビット単位の移動)
そしてそのズレ(t,n,mなど)は継承によって変わってくるのでしょうか…
PODと呼ばれる特定の条件を守った構造体でなければ、メンバの並びやオフセットは特定できません。
構築されたクラスオブジェクトへのポインタやthisがどこを指すかとか、どのメンバがどれだけのオフセットを持つかとか考えてはいけないのです。
それはShipクラスやShotクラスの内部の話ですね。adu さんが書きました:これらのご回答についてはnewで空きクラスが返された直後(?)にコンストラクタが呼ばれるはずだから大丈夫だと思っていたのですが…
TaskListのメンバ関数で、char配列に対してTask*でアクセスしてます。
ここはクラスオブジェクトが構築される前の領域であり解体された後の領域です。
オフセットのズレと関係なしに内容は保証されません。
operator newで返すポインタと、operator deleteの引数で受け取るポインタは、オブジェクトを格納するのに十分な大きさのメモリ領域の先頭アドレスです。
基準として使えるのはこのアドレスしかなく、このアドレスからインスタンスにアクセスすることはできません。
めんどうくさいですが、一回書けば済むものなので頑張ってください。