たいした事じゃないけど、環境変数GCC_OPTIONに+を付け加えておくと、 C++式のコメント//が使えるようになる。 //から行末までコメントアウトされる。これは/* */の間にあっても大丈夫。 って言うと変に聞こえるかも知れないけど、例えば
foo(); /* 呼びだし1 */
zzz(); /* 呼びだし2 */
の2行をコメントアウトしたいときに、
/* foo(); /* 呼びだし1 */
zzz(); /* 呼びだし2 */
*/
とやってもエラーが出て、うまくいかない。これを
foo(); // 呼びだし1
zzz(); // 呼びだし2
としておけば、
/* foo(); // 呼びだし1
zzz(); // 呼びだし2
*/
とやってもエラーが出ない。
ちなみに、頻繁にコメントアウトし直すような部分では、
foo(); // 呼びだし1
zzz(); // 呼びだし2
/**/
としておいて、foo()の前に/*を付けたり外したりするだけで簡単に入れ替えられる。
/* foo(); // 呼びだし1
zzz(); // 呼びだし2
/**/
要するに、Cではコメントのネスト(入れ子)が出来ないって事に起因するものばかりなんだよね。
int enemy_x[10],enemy_y[10],enemy_hp[10];
というふうに出来るけど、これを
struct{
int x,y,hp;
} enemy[10];
とするようにする。こうする事による利点を確かめた事は無いけれど、x,yとかは当たり判定とかで
ほぼ同時に使う事が多いと思う。そういう時にx,y別々に配列を持っていると、アクセスする時には
それぞれの配列の先頭アドレスをいちいちレジスタに代入しなければならない。これが構造体なら、
1回だけレジスタに代入すれば、あとはインデックスの数を変えるだけなので実行が速くなる、
もしくはプログラムが短くなる(と思う)。
typedef struct{
int x,y,hp;
} EDATA;
EDATA enemy[10];
こうしておけば、型名を何度も書かなければいけない時にいちいちstructを書かなくていいので便利。
ちなみに、僕は構造体名などはなるべく8文字未満にしようとしている。その理由は後ほど。
構造体の内容を変えた場合は、その構造体を使ったソースファイルは面倒でもコンパイルし直すこと。
オブジェクトファイルは構造体の先頭からのオフセットを保持しているので、コンパイルしないと
古いオフセットのままになり、実行時に変になる。僕の友達はこれをやらずにゲームを作っていて、
原因不明のバグが多発して困っていた。
typedef struct{
char buf[256];
} STRING;
static STRING s1={"test"};
auto STRING s2;
s2=s1;
printf("%s\n",s2.buf);
といった具合だね。ただ、あまり勧めはしないけどね。この例で言えば、この構造体の代入では
どんな文字列が入っていても256バイト全部をmemcpyで転送する。とてももったいない。
struct{ short x,y; } a1,a2;
a1.x=a1.y=0;a2=a1;
とかね。何故なら、4バイト以内ならレジスタ1発で代入できるから。
#include <iocslib.h>
static struct FILLPTR fs={ 0,0,255,255,0 };
FILL(&fs);
と、
#include <iocslib.h>
FILL(&((struct FILLPTR){ 0,0,255,255,0 }));
は全く同じ。余計な変数を使わない分、すっきりしている。注意点は、括弧の数。1組余計な感じが
するけど、必要なのだ。
EDATA enemy[10];よりは、
#define ENEMY_MAX 10 EDATA enemy[ENEMY_MAX];とした方がいい。これだと、敵の数を変えたい時は#defineを変えて、このマクロを使っている ファイルをコンパイルし直すだけで済む。
enum{ item_yakusou=0,item_dokukeshi,item_potion,item_not=-1 };
/* このenumは下のdefineと同じ
#define item_yakusou 0
#define item_dokukeshi 1
#define item_potion 2
#define item_not -1
あるいは、こうでもいい。
enum{ item_not=-1,item_yakusou,item_dokukeshi,item_potion };
*/
そして、その名前をプログラム中では使うようにする。
if(equip==item_yakusou){ // 薬草を装備している時
equip=item_not;
...
}
あるいは
switch(equip){
case item_not: //何も持ってない
break;
case item_yakusou:
...
}
といった具合に。こうしておけば、アイテムの追加や削除を行った時のプログラムの
改造、あるいは番号の変更などが
ものすごーく楽になる。
変数なんかマクロにしてどうするんだとか言われそうだけど。言いたいのは、 構造体の特定のメンバを指定する場合はマクロにしておくと便利ってこと。
typedef struct{
int str,def,mp;
} PLAYER;
#define pl_str pdata.str
#define pl_def pdata.def
#define pl_mp pdata.mp
extern PLAYER pdata;
if(pl_mp>0) ...;
とか。あまり意味無いように見えるかも知れないけど。もっと便利そうな例としては
#define EDX(p) (p)->x
#define EDY(p) (p)->y
extern EDATA enemy[];
EDX(enemy+1)=10;
とか、
#define EDX(i) enemy[i].x
#define EDY(i) enemy[i].y
extern EDATA enemy[];
EDX(1)=10;
みたいな感じかなあ。関数に代入してるみたいに見えて変だけど。
この利点は、構造体を変更した時にすごくよく現れる。EDATAを
typedef struct{
struct{
int x,y;
} xy;
int hp;
} EDATA;
という風に改造したとする。この時、直接enemy[0].xとかやっていたら、それらを全てenemy[0].xy.xに
直さなければいけない。しかしマクロを使っていれば、マクロの部分だけ
#define EDX(i) enemy[i].xy.x #define EDY(i) enemy[i].xy.yという風に直すだけで済む。この差は大きいよ、ほんとに。
大した事じゃ無いけどね。もともとマクロは関数と形が似てるし。
例えば、グラフィック画面内で転送するルーチンを
void gcopy(int sx,int sy,int sp,int nx,int ny,int dx,int dy,int dp)
{// ページspの(sx,sy)からページdpの(dx,dy)へnx*nyの範囲だけコピーする
auto short *s=gad(sx,sy,sp),*d=gad(dx,dy,dp);
...
}
という具合に作ったとする。gadは
#define gad(x,y,page) ((short*)(0xc00000|((page)*0x80000)|(((y)*512+(x))*2)))という、座標から画面のアドレスを算出するマクロ。 ところが、これを改造して
void gcopy(short *s,int nx,int ny,short *d)
{// *sから*dへnx*nyの範囲だけコピーするルーチン
...
}
としたとする。これに伴って呼びだし側を全て変えなくてはいけないが、ここで
#define gcopy(sx,sy,sp,nx,ny,dx,dy,dp) gcopy_(gad(sx,sy,sp),nx,ny,gad(dx,dy,dp))というマクロを新設する。実際の関数gcopyは、マクロと区別する為にgcopy_と名前を変える。 gcopyを呼び出しているソースの方は、このマクロを先頭に加える以外は何も変える必要が無い。
ANSI−Cでは、構造体に代入できる。 初期化の代入もマクロにしてしまえば、構造体を変更した 時に便利。
#define init(x,y,hp) { (x),(y),(hp) }
EDATA enemy[ENEMY_MAX]={ init(0,0,10),init(1,0,20), ...};
例えば構造体が
typedef struct{
int hp,x,y;
} EDATA;
に変わったら(hpとx,yの順序が変わった)、マクロinitを
#define init(x,y,hp) { (hp),(x),(y) }
にするだけで、実際のデータの初期化の方は何も変える必要が無い。
後で見て混乱する可能性はあるけど。
関数や変数のマクロを使っていると、エラーが出たときに、一見どこにも間違いが 無いように見えることがある。そんな時は、マクロ定義の方が間違っているのかも 知れない。gcc -E で出てくるプログラムをコンパイルしてみれば、 間違っている部分が分かるかも。
static char test[]="foo" "zzz";というのがあったら、
static char test[]="foozzz";と同じことになる。printfとかで長い奴をまとめるのにいいんじゃないかな。
printf("test-func\n");
printf("usage: test.x ...\n");
というのと、
printf("test-func\n"
"usage: test.x ...\n");
というのは(見た目は)全く同じ動作をする。printfの実行が2つから1つになった分だけ速くなる
と思うよ。
#define test(x,y) x##yこの##というのがポイント。test(foo,1)はfoo1というトークンになる。 ゲーム作ってる時には使った事無いけど、知ってるので自慢出来る(?笑)。
#define pri(s) printf( #s "\n")というマクロは、pri(test)とすると、マクロ展開でprintf("test" "\n")となり、文字列の連結で printf("test\n")と同じになる。 これらは、あくまでコンパイル時に処理されるのであって、実行時に処理されるわけでは無いので、 注意。下手に変数なんかを使うとコンパイルエラーが出ると思うよ。
僕の場合は、構造体の定義ファイル・マクロの定義ファイル・変数の定義ファイルに分離している。 いずれも大域的な物のみだけど。 最近では、変数に直接関わる構造体は変数の定義ファイルに 加えてしまった方がいいんじゃないかと思ってるけど。
EDATA enemy[ENEMY_MAX];みたいな奴はね。そうでなければ、逆に構造体を使う変数だけさらに別のファイルにするか。 ファイルをincludeするのは、結構時間がかかる(iocslib.hとかdoslib.hなどを includeしてみれば良く分かる)。 なるべくなら、includeするのは少ない方がいい。ヘッダファイルを分けておけば、 関係ないヘッダはincludeしないという風に、融通がきく。 それから、関数の宣言ファイルも作っておく方がいいかも。 ANSI−C式の関数宣言をしておけば、引数の型のチェックはしてくれるからね。
// 関数の宣言のヘッダファイル void sub(int x,int y); int attack(ENEMY *ep); int dec_hp(int hp,int damage);特に引数がポインタや構造体の時にすごく役に立つ。ただ、 関数を新しく作ったり引数や返り値を変更したりする度にこのヘッダファイルを 書き換えなければいけないのが面倒だけど。 ところで、大域変数の宣言は
int data; extern int data;と2種類ある。上のは宣言し、その領域を確保する。下のは宣言だけで、領域の確保はしない。 もし全てのソースファイルで領域の確保がされていないと、リンクの段階でエラーになる。 逆に、領域確保されているのが2ヶ所以上あると、二重定義でエラーか警告になる。 そこで、僕の変数の定義ファイルでは
#ifndef EXT #define EXT extern #endif EXT int data; EXT ENEMY enemy[ENEMY_MAX];という具合になっている。そして、このファイルをincludeしているどれか1つのソースファイルのみ、 includeする前に
#define EXTという行を付け加えている。こうしておくと、そのファイルのコンパイル時には領域確保もするし、 それ以外のファイルでは領域の確保はしないので、大丈夫。 難点は、初期化が単純に書けないこと。初期化式だけは#ifとかを使って別々にしなければいけない。あるファイルのみ
#define INIT #include "variable.h"とやっておいて、ヘッダファイルの方は
#ifdef INIT
int data=10;
#else
extern int data;
#endif
という風にするとか。
これは68のGCCの拡張だと思う。trap命令なんかを記述するには便利。いろいろと複雑な事が 出来るみたいだけど、僕は単純なのしか知らないし、それだけで取り敢えず充分だと思うけど。 使うには、環境変数MARIKOにAを加えておく必要がある(かも)。 参考までに、僕のGCC関係の環境変数の一部を書いておこう。
SET MARIKO=ABD
SET GCC_OPTION=GTFAMLJKOE+
SET GCC_NO_XCLIB=USE_LIBC
で、asmの例として、ZMUSICのm_stat(0)に相当する関数をば。
int zmdstat(void)
{
register long rd0 asm("d0");
register long rd2 asm("d2");
rd2=0;
asm volatile(
"moveq.l #9,d1\n"
"trap #3"
:"=d"(rd0) /* 値が返るレジスタ変数 */
:"d"(rd2) /* 引数として使われるレジスタ変数 */
:"d1","a0" /* 破壊されるレジスタ */
);
return rd0;
}
registerというのは、autoとほぼ同じ。GCCでは、最適化の為にautoとregisterはほとんど
区別しないらしい。違いは、register指定した変数はアドレスを持たないということだけ。
この辺の詳しいことはANSI−Cの本に出てるでしょ。
register long rd0 asm("d0");
は、変数rd0にd0レジスタが割り当てられる事を明示している。
register long rd0;だと、変数rd0にはどのレジスタが割り当てられるか分からない。 ちなみにこのzmdstatの変数定義の部分は、マクロを使って
#define REG_LONG(reg) register long r##reg asm(#reg) REG_LONG(d0);REG_LONG(d2);という具合にすることも出来なくは無い。それは置いといて、
#define US unsigned
#define REGIST register
void sample(void)
{
static US char c;
auto int zzz;
auto EDATA *ep;
REGIST int i,j;
...
}
という具合に、他の指定と一緒に書くとき綺麗なのだ。
構造体名も8文字未満に抑えておけば、型名の所もぴったり収まる。タブが8文字ならね。
だから、デフォルトでタブが自由にならないemacsは大っ嫌いだ。
printf(format,arg...)
fprintf(FILE*,format,arg...)
sprintf(char*,format,arg...)
sprintfはこっちで用意するバッファに書き込んでくれるのだ。
ちなみにcprintfというのもあるのだが、これが何かは知らない。誰か教えて。
i=({ auto int d;while(1){ d=getc(fp);if(d==EOF||d=='\n') break; } d; });
{ }で作られたブロックを( )でくくるだけでいいのだ。このブロックの最後に変数がただ置いてある
事に注目。これがブロックの値になるのだ。値を返すマクロを作るのに便利かも。
#include "A:\gcc\include\test.h"は駄目で、
#include "A:\\gcc\\include\\test.h"あるいは
#include "A:/gcc/include/test.h"としなければならない。つまり、パス名の区切りのつもりで \ を使っても、だめだってこと。/で代用しよう。
typedef struct{
short x,y;
int v:1,h:1,dummy:2,color:4;
char sp_code;
short prw;
} SPSR;
という構造体で表す事が出来る。スーパーバイザーモードなら、0xeb0000に
この構造体のデータを直接書き込むことによってスプライトを表示することが出来る。
僕はたまにIOCSのVDISPSTを使った割り込みの中でスプライトを表示するけど、
この割り込みではスーパーバイザーモードだから、何の遠慮もなくこの方法が使える。
struct RGB{
int g:5,r:5,b:5,i:1;
} pal;
という風に表して、pal.r++;みたいな演算をやると、コンパイルされたプログラムは
結構複雑になる。
下手をすると、自分で専用のルーチンを書いた方が良くなることも有り得るので注意。
この場合でいえば、
auto int pal,r=pal; r+=1<<6;r&=0b11111<<6;pal&=~(0b11111<<6);pal|=r;とする方が良いかも知れない。何通りかプログラムを書いてみて、gcc -S で確認するのが確実でしょう。
#define __IOCS_INLINE__
#include <iocslib.h>
#include <interrupt.h>
static volatile int wc;
static interrupt void wait(void){
if(wc>0) wc--;
IRTE();
}
int main(void){
VDISPST(wait,0,1);
wc=10;while(wc);
}
という感じだね。これは、VDISPSTを呼んだ後に10/60秒待つプログラムだ。
もしwcの宣言部分にvolatileが無かったら、wc=10;while(wc);は最適化によって
while(10);に置き換えられてしまうのだ。そうすると、いつまで経っても終わらなくなってしまう。
割り込み関数はinterrupt修飾し、その関数はマクロIRTE()で終わるようにするのも忘れずに。
それと、疑問点を1つ。何故か、68の電源を入れて最初に実行したVDISPSTって、割り込みが
発生するのに時間がかかるんだよね。不思議だ。
extern long foo();
auto long (*funcp)(int a,int b);
funcp=foo;(*funcp)(1,2);
という具合。それと、何故か関数のポインタの配列の初期化が出来ないんだな。仕方ないから
extern int foo1(),foo2(),foo3();
static void* farr[]={ foo1,foo2,foo3 };
auto int (*funcp)(void);
funcp=farr[0];(*funcp)();
という具合にやってるけど。
ちょっと地域ローカルな話になるが、他機種で動くように配慮して68で作ったプログラムが、
研究室のUNIXのライブラリではうまく動かなかった事がある。
○68のsprintfは文字数を返すんだが、あっちはアドレスを返す。
○68でrealloc(NULL,10)はmalloc(10)と同じ動作になるのに、あっちはNULLを返す。
これにはハマったぞ。他にも何かありそうだし、みんなも移植する時は気をつけよう。
標準関数といえども当てにはならない。
それから、コードの問題もある。漢字コードなんかはその機種の物に変換した方がいいし、
^Mや^Zのコードは邪魔になり、ひどいときはその所為でコンパイルできない!
分かる人には分かると思うし、そんなの当たり前じゃんと
いう人もいるだろうけど、ちっとは役に立つこともあるんでないかい?
中級者向けとか銘打っておきながら、初心者相手みたいな事も書いて
しまったけど。
一番書きたかった事は、マクロで変数をアクセスすることかな。それと、
構造体を変更したら、コンパイルし直すことね。
えらそうな口調で書いてあるけど、あんまりうまく説明出来て無いし、
C言語とライブラリをごっちゃにしてるし、
間違ってる事もあるだろうけど(特に用語・概念・文法)、
参考くらいにはなると思うから、あとは自分でいろいろ試してみて。
それから、文中のプログラムについては 正しいとは思っているけれども
実際に全て確認したわけでは無いので。そこんところ、よろしく。