大したネタではありませんが、技術的な話題を少しだけ。
コンパイラとハードウェアの特徴を押さえれば、C言語でもそこそこの速度が出せます。
-
cc65のサイトに記載されている内容からざっくりと紹介。
-
- ISO標準との違い
cc65 Users Guide - Differences to the ISO standard <http://cc65.github.io/doc/cc65.html#s4> - 「double」と「float」は使用できません。The datatypes "float" and "double" are not available.
使用した場合はコンパイルエラーになります。整数を固定小数値として扱うことで回避します。
- 構造体(または共用体)の値を関数の引数として渡せないし、戻り値として返せない。C Functions may not return structs (or unions), and structs may not be passed as parameters by value.
However,struct assignment *is* possible.
関数呼び出し時のスタックに積めるサイズが限定されています。
ポインタ変数であれば構造体や共用体を引数や戻り値で受け渡しが可能です。 - 呼び出し規則の互換性がないため、fastcall関数ポインタとcdecl関数ポインタを混在させない。Most of the C library is available with only the fastcall calling convention.
It means that you must not mix pointers to those functions with pointers to user-written,
cdecl functions (the calling conventions are incompatible).
原文は標準ライブラリ関数の話題となっていますが、ユーザが作成した関数についても同様です。
関数ポインタで処理を切り替える場合は要注意です。v2.15よりfastcall呼び出しがデフォルトになりました。
__fastcall__もしくは__cdecl__修飾子が記述されていない場合、関数呼び出しはfastcallになります。
旧バージョンのライブラリは、リンクはできても正常に呼び出しができない場合があります。 - volatileキーワードは効果がありません。The volatile keyword doesn't have an effect.
This is not as bad as it sounds,
since the 6502 has so few registers that it isn't possible to keep values in registers anyway.
キャッシュはありませんし、アキュムレータと2本のインデックスレジスタでメモリをガシガシアクセスしてレジスタの値を維持しない(同一変数をポーリングする処理でも毎回メモリから読み込む)コードが出力されるため、もともと影響がないようです。
- コーディングヒント
cc65 coding hints <http://cc65.github.io/doc/coding.html> - プロトタイプ宣言を使う。Use prototypes
- ネストしたブロック内で自動変数を使わない。Don't declare auto variables in nested function blocks
今時のCPUであれば自動変数領域を確保、解放するコストは微々たるものですが、6502上では馬鹿になりません。
特にループ中で使用する変数は、ループ前に準備しましょう。 - コンパイラはハイレベルな最適化ができません。Remember that the compiler does no high level optimizations
素直に機械語コードへ落とし込むこととなります。
- long関連は遅い!Longs are slow!
- できるだけ符号なしの型を使う。Use unsigned types wherever possible
- int型よりchar型を使う。Use chars instead of ints if possible
8ビットCPUですので扱う値が0〜255で収まる場合はunsigned char型で扱うと高速化につながりますが、オーバーフローには注意が必要です。
画面停止中の計算など高速性がさほど求められない処理であれば、素直にint型を使った方が良いかもしれません。 - 配列の要素型のサイズは1, 2, 4, 8のいずれかで。Make the size of your array elements one of 1, 2, 4, 8
要素型のサイズが2, 4, 8の場合、インデックスからバイトオフセット値を計算する際に乗算ではなくシフト演算が使え、より高速なアドレス計算ができます。
要素型のサイズがコンパクトな計算結果を収めたテーブルから値を取り出すような処理で効果が期待されます。 - 式評価は左から右です。Expressions are evaluated from left to right
演算子の優先順位に従い左から右へ評価を行った際の一時的な値は、その都度変数に格納されます。
特に定数同士の演算は最初に置くか()で括ると、変数と定数の演算回数を減らすことができます。 - プリインクリメントかプリデクリメントを使う。Use the preincrement and predecrement operators
i++やi--の書き方の場合は、演算のために変数の値をメモリから再取得します。
変数の値を参照しないのであれば、++iや--iを使用することで少しだけ高速化できます。 - 絶対アドレスにアクセスする場合は定数を使う。Use constants to access absolute memory locations
定数であれば高速で省メモリなコードが出力されます。
PPUのコントロールレジスタ等の固定アドレスに配置されたものは予め定数を用意しておくと便利です。
大域変数は絶対アドレスで参照可能ですので、ポインタ変数へアドレスを代入して参照した場合より、直接参照した方が高速です。
当然ながらアドレス固定のため、汎用性は低下します。 - 初期値が設定されたローカル変数を使う。Use initialized local variables
変数宣言と初期化を分けず、
int i = 1;
の様に一度に行うと高速で省メモリなコードが出力されます。
初期値なしの変数はまとめて領域確保されるためブロックの先頭に、初期値付き変数はその後でまとめて宣言します。
まぜると遅くなります。 - ポインタ操作は[]演算子を使う。Use the array operator [] even for pointers
ポインタ変数をインデックス値と合わせて使用する場合、
*(a + b)
よりa[b]
の方が良いコードを出力します。アドレス定数値とインデックス値を格納した変数の組み合わせも同様です。
アドレス定数とインデックス値の組み合わせは、アドレス演算の方が絶対アドレスに変換するためコンパクトなコードが出力されます。
ポインタ変数とインデックス用変数の組み合わせではあまり変わらず、インデックス用変数のサイズに応じて16ビットと8ビットあるいは16ビットと16ビットの整数演算でアドレスを求めるため、コード量が一気に増えます。 - レジスター変数は注意!Use register variables with care
register修飾子を付加したレジスター変数はゼロページ領域から確保されますが、局所変数のため領域確保時に以前の値をスタックへ退避し、解放時に復帰させます。
通常の変数のアクセスと比較してアドレス指定が1バイトで済むため速いのですが、2バイトで約70サイクルのオーバーヘッドがかかるため、複数回使わないと処理全体としては遅くなります。
C言語の書き方にこだわらなければ、アセンブラ側で用意したゼロページ配置の領域を大域変数として扱った方がオーバーヘッドを気にせずに使えます。 - 0x7FFFより大きい10進数値はlong int扱いです。Decimal constants greater than 0x7FFF are actually long ints
int型の範囲が-32768〜32767であるため、32768以上はlong型扱いとなります。
サフィックスなしで-32768以下もしくは32768以上の定数にはコンパイル時に警告が出ますので、定数値の後にU、LやULを付加することで解決します。
8進数か16進数であればサフィックスなしで記述できます。
ただし、いずれの場合でも変数の代入可能な範囲を超えた分は破棄されます。 - 可変長引数へのアクセスは処理コストが高い。Access to parameters in variadic functions is expensive
プロトタイプ宣言なしの場合を含め、スタックに詰められた引数を操作する処理は処理コストがかかります。
利用する場合は引数の受け渡しに細心の注意を払い、限定的な利用に留めた方が良さそうです。
- ISO標準との違い
大域変数としてゼロページの領域を使用すると、出力されるコードサイズが小さくなり速度が向上します。
注意点はゼロページのサイズは256バイトしかないことと、大域変数のため関数間で共有してしまうことです。以下は変数を構造体にまとめ、ゼロページで確保した領域を参照する場合の一例です。
- アセンブラソース
-
- C言語ソースとサイズが同じになるよう構造体を定義
- ゼロページに構造体のタグでメモリ領域を確保
- メモリ領域の先頭に"_"付きのラベルを付加
- ラベル名を外部参照可能となるよう宣言
.setcpu "6502" .autoimport on ; ゼロページ配置の変数 .exportzp _zpw .zeropage ; 構造体の定義 .struct Work i .byte j .byte ptr .word array .res 4 .endstruct _zpw: .tag Work
- C言語ソース
-
- アセンブラソースとサイズが同じになるよう構造体を定義
- ゼロページに確保した領域のラベルを"_"なしで大域変数名としてextern宣言
- 「#pragma zpsym ("変数名");」で上記の変数名をゼロページのシンボルと結びつけ
- 常に大域変数として参照
typedef struct Work { char i, j; char *ptr; char array[4]; } Work; extern Work zpw; #pragma zpsym ("zpw"); /* zpw is in the zeropage */ void test04_memory_zeropage(void) { zpw.i = 0; zpw.j = 1; zpw.ptr = (char*)0x5000; for (zpw.i = 0; zpw.i < 4; ++zpw.i) { zpw.array[zpw.i] = zpw.j; } }
ld65やcl65コマンドでリンクを行う際に「-m ファイル名」を指定すると、大域変数や関数の配置状況が分かるマップファイルが出力されます。
これまでのMakefileをそのまま利用した場合は、/bin内の「プロジェクト名.map」として出力します。C言語で用意した変数名には"_"が付きます。
例えば、"padinfo"変数の場所を探したい場合は、「Exports list by value」内の"_padinfo"を探します。- 上記の例では、0x0526のアドレスに配置されています。
cl65のコマンドは、cc65、ca65、co65及びld65のフロントエンドです。
このコマンドを使ってコンパイルを行う際に「-l ファイル名」を指定すると、コンパイル結果をアセンブラソースリストとして出力します。先のMakefileでは以下のように修正を加えると、/obj内の「Cソースファイル名.o.asm」として出力します。
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c $(CC) $(CFLAGS) -l $@.asm --create-dep $@.dep -c -o $@ $<
-
いくつかの処理のコンパイル結果を確認してみました。
なおコンパイル結果はコンパイラ自身のバージョンやオプション指定によって変わる可能性があります。
以下のソースは「-Or -Os」のオプション指定にてコンパイルした結果です。- 関数呼び出し:通常の関数呼び出しと__cdecl__および__fastcall__をつけた場合の違いを確認
- 通常の関数(Ver2.14まで)
char test01_funcA(char a) { 000000r 1 .proc _test01_funcA: near return a; 000000r 1 A2 00 ldx #$00 } 000002r 1 A1 rr lda (sp,x) 000004r 1 4C rr rr jmp incsp1 000007r 1 .endproc
- 通常の関数(Ver2.15以降)
char test01_funcA(char a) { 000000r 1 .proc _test01_funcA: near return a; 000000r 1 20 rr rr jsr pusha } 000003r 1 A2 00 ldx #$00 000005r 1 A1 rr lda (sp,x) 000007r 1 4C rr rr jmp incsp1 00000Ar 1 .endproc
- __cdecl__関数:Ver2.14は無指定
char __cdecl__ test01_funcB1(char a) { 00000Ar 1 .proc _test01_funcB1: near return a; 00000Ar 1 A2 00 ldx #$00 } 00000Cr 1 A1 rr lda (sp,x) 00000Er 1 4C rr rr jmp incsp1 000011r 1 .endproc
- __fastcall__関数
char __fastcall__ test01_funcB2(char a) { 000011r 1 .proc _test01_funcB2: near return a; 000011r 1 20 rr rr jsr pusha } 000014r 1 A2 00 ldx #$00 000016r 1 A1 rr lda (sp,x) 000018r 1 4C rr rr jmp incsp1 00001Br 1 .endproc
- __cdecl__関数(戻り値なし):Ver2.14は無指定
void __cdecl__ test01_funcC1(char a) { 00001Br 1 .proc _test01_funcC1: near } 00001Br 1 4C rr rr jmp incsp1 00001Er 1 .endproc
- __fastcall__関数(戻り値なし)
void __fastcall__ test01_funcC2(char a) { 00001Er 1 .proc _test01_funcC2: near } 00001Er 1 20 rr rr jsr pusha 000021r 1 4C rr rr jmp incsp1 000024r 1 .endproc
- 関数の呼び出し元(Ver2.14まで)
void test01_call_function(void) { 000021r 1 .proc _test01_call_function: near char r; 000021r 1 20 rr rr jsr decsp1 000024r 1 A9 0A lda #$0A r = test01_funcA(10); 000026r 1 20 rr rr jsr pusha r = test01_funcB1(10); 000029r 1 20 rr rr jsr _test01_funcA r = test01_funcB2(10); 00002Cr 1 A0 00 ldy #$00 test01_funcC1(10); 00002Er 1 91 rr sta (sp),y test01_funcC2(10); 000030r 1 A9 0A lda #$0A } 000032r 1 20 rr rr jsr pusha 000035r 1 20 rr rr jsr _test01_funcB1 000038r 1 A0 00 ldy #$00 00003Ar 1 91 rr sta (sp),y 00003Cr 1 A9 0A lda #$0A 00003Er 1 20 rr rr jsr _test01_funcB2 000041r 1 A0 00 ldy #$00 000043r 1 91 rr sta (sp),y 000045r 1 A9 0A lda #$0A 000047r 1 20 rr rr jsr pusha 00004Ar 1 20 rr rr jsr _test01_funcC1 00004Dr 1 A9 0A lda #$0A 00004Fr 1 20 rr rr jsr _test01_funcC2 000052r 1 4C rr rr jmp incsp1 000055r 1 .endproc
- 関数の呼び出し元(Ver2.15以降)
void test01_call_function(void) { 000024r 1 .proc _test01_call_function: near char r; 000024r 1 20 rr rr jsr decsp1 000027r 1 A9 0A lda #$0A r = test01_funcA(10); 000029r 1 20 rr rr jsr _test01_funcA r = test01_funcB1(10); 00002Cr 1 A0 00 ldy #$00 r = test01_funcB2(10); 00002Er 1 91 rr sta (sp),y test01_funcC1(10); 000030r 1 A9 0A lda #$0A test01_funcC2(10); 000032r 1 20 rr rr jsr pusha } 000035r 1 20 rr rr jsr _test01_funcB1 000038r 1 A0 00 ldy #$00 00003Ar 1 91 rr sta (sp),y 00003Cr 1 A9 0A lda #$0A 00003Er 1 20 rr rr jsr _test01_funcB2 000041r 1 A0 00 ldy #$00 000043r 1 91 rr sta (sp),y 000045r 1 A9 0A lda #$0A 000047r 1 20 rr rr jsr pusha 00004Ar 1 20 rr rr jsr _test01_funcC1 00004Dr 1 A9 0A lda #$0A 00004Fr 1 20 rr rr jsr _test01_funcC2 000052r 1 4C rr rr jmp incsp1 000055r 1 .endproc
引数や戻り値は、C言語用のスタック領域やAレジスタを介して受け渡しを行います。
通常の関数呼び出しは、コンパイラのバージョンによって異なります。
Ver2.14以前はcdecl呼び出し、Ver2.15以降はfastcall呼び出しになっています。
呼び出し方法を明示するために__cdecl__と__fastcall__修飾子がありますが、__cdecl__修飾子はVer2.15より利用可能です。
cdecl呼び出しは、全ての引数をスタックに積んだ後に関数が呼び出されます。
fastcall呼び出しは、一番最後の引数はスタックに積まれず、Aレジスタに設定した状態で呼びだされます。
が、呼びだされた関数側では引数領域の解放手順が共通のためスタックに積んでしまいます。
呼び出し先をアセンブラで書き、引数を1つまでに抑えればスタックを使わずに呼び出すことが可能です。
戻り値サイズが1バイトの場合は、Aレジスタにセットされた状態で呼び出し元に戻ります。
- 通常の関数(Ver2.14まで)
- ポインタ操作:ポインタ値のアドレス演算と[]による配列演算の違いを確認
-
コーディングヒントの話題にあったポインタ操作を少し試してみました。
- 定数値を使う
定数同士のアドレス演算の場合、計算はコンパイル時に済ませてしまうため絶対アドレス参照になります。
#define RAM ((char *)0x6000) void test02_pointer_addr_1(void) { 000037r 1 .proc _test02_pointer_addr_1: near *(RAM + 0) = 1; 000037r 1 A9 01 lda #$01 *(RAM + 10) = 2; 000039r 1 8D 00 60 sta $6000 *(RAM +255) = 3; 00003Cr 1 A9 02 lda #$02 *(RAM +256) = 4; 00003Er 1 8D 0A 60 sta $600A } 000041r 1 A9 03 lda #$03 000043r 1 8D FF 60 sta $60FF 000046r 1 A9 04 lda #$04 000048r 1 8D 00 61 sta $6100 00004Br 1 60 rts 00004Cr 1 .endproc
配列演算の場合、インデックスレジスタを使用するため若干コードが長くなります。
void test02_pointer_array_1(void) { 00035Dr 1 .proc _test02_pointer_array_1: near RAM[ 0] = 1; 00035Dr 1 A2 60 ldx #$60 RAM[ 10] = 2; 00035Fr 1 A9 00 lda #$00 RAM[255] = 3; 000361r 1 85 rr sta ptr1 RAM[256] = 4; 000363r 1 86 rr stx ptr1+1 } 000365r 1 A9 01 lda #$01 000367r 1 A0 00 ldy #$00 000369r 1 91 rr sta (ptr1),y 00036Br 1 A9 02 lda #$02 00036Dr 1 A0 0A ldy #$0A 00036Fr 1 91 rr sta (ptr1),y 000371r 1 A9 03 lda #$03 000373r 1 A0 FF ldy #$FF 000375r 1 91 rr sta (ptr1),y 000377r 1 A9 00 lda #$00 000379r 1 20 rr rr jsr pushax 00037Cr 1 A9 04 lda #$04 00037Er 1 A0 01 ldy #$01 000380r 1 18 clc 000381r 1 48 pha 000382r 1 98 tya 000383r 1 71 rr adc (sp),y 000385r 1 91 rr sta (sp),y 000387r 1 68 pla 000388r 1 88 dey 000389r 1 4C rr rr jmp staspidx 00038Cr 1 .endproc
- ポインタ定数とインデックス用変数を使う
アドレス演算の場合、周囲の定数値も交えて結果を求めます。
インデックス用変数がunsigned charの場合は256以上の値がオーバーフローするため注意が必要です。
intやunsigned intの変数の場合は256バイトの境界はありませんが、アドレス計算のコード量が一気に増えます。
void test02_pointer_addr_2(void) { 00004Cr 1 .proc _test02_pointer_addr_2: near unsigned char i; 00004Cr 1 20 rr rr jsr decsp1 00004Fr 1 A9 00 lda #$00 i = 0; *(RAM + i) = 1; 000051r 1 A8 tay i = 10; *(RAM + i) = 2; 000052r 1 91 rr sta (sp),y i = 255; *(RAM + i) = 3; 000054r 1 A2 60 ldx #$60 i = 256; *(RAM + i) = 4; 000056r 1 18 clc } 000057r 1 71 rr adc (sp),y 000059r 1 90 01 bcc L0242 00005Br 1 E8 inx 00005Cr 1 85 rr L0242: sta ptr1 00005Er 1 86 rr stx ptr1+1 000060r 1 A9 01 lda #$01 000062r 1 91 rr sta (ptr1),y 000064r 1 A9 0A lda #$0A 000066r 1 91 rr sta (sp),y 000068r 1 A2 60 ldx #$60 00006Ar 1 98 tya 00006Br 1 18 clc 00006Cr 1 71 rr adc (sp),y 00006Er 1 90 01 bcc L0243 000070r 1 E8 inx 000071r 1 85 rr L0243: sta ptr1 000073r 1 86 rr stx ptr1+1 000075r 1 A9 02 lda #$02 000077r 1 91 rr sta (ptr1),y 000079r 1 A9 FF lda #$FF 00007Br 1 91 rr sta (sp),y 00007Dr 1 A2 60 ldx #$60 00007Fr 1 98 tya 000080r 1 18 clc 000081r 1 71 rr adc (sp),y 000083r 1 90 01 bcc L0244 000085r 1 E8 inx 000086r 1 85 rr L0244: sta ptr1 000088r 1 86 rr stx ptr1+1 00008Ar 1 A9 03 lda #$03 00008Cr 1 91 rr sta (ptr1),y 00008Er 1 98 tya 00008Fr 1 91 rr sta (sp),y 000091r 1 A2 60 ldx #$60 000093r 1 18 clc 000094r 1 71 rr adc (sp),y 000096r 1 90 01 bcc L0245 000098r 1 E8 inx 000099r 1 85 rr L0245: sta ptr1 00009Br 1 86 rr stx ptr1+1 00009Dr 1 A9 04 lda #$04 00009Fr 1 91 rr sta (ptr1),y 0000A1r 1 4C rr rr jmp incsp1 0000A4r 1 .endproc
unsigned char型のインデックス用変数で配列演算を行った場合、先頭アドレスとインデックスレジスタによるアクセスが可能になるためコード量がコンパクトにまとまります。
intやunsigned int型の変数も利用できますが、アドレス計算が発生するためコード量が増えます。
void test02_pointer_array_2(void) { 00038Cr 1 .proc _test02_pointer_array_2: near unsigned char i; 00038Cr 1 20 rr rr jsr decsp1 00038Fr 1 A9 00 lda #$00 i = 0; RAM[i] = 1; 000391r 1 A8 tay i = 10; RAM[i] = 2; 000392r 1 91 rr sta (sp),y i = 255; RAM[i] = 3; 000394r 1 A9 01 lda #$01 i = 256; RAM[i] = 4; 000396r 1 99 00 60 sta $6000,y } 000399r 1 A9 0A lda #$0A 00039Br 1 91 rr sta (sp),y 00039Dr 1 A8 tay 00039Er 1 A9 02 lda #$02 0003A0r 1 99 00 60 sta $6000,y 0003A3r 1 A9 FF lda #$FF 0003A5r 1 A0 00 ldy #$00 0003A7r 1 91 rr sta (sp),y 0003A9r 1 A8 tay 0003AAr 1 A9 03 lda #$03 0003ACr 1 99 00 60 sta $6000,y 0003AFr 1 A9 00 lda #$00 0003B1r 1 C8 iny 0003B2r 1 91 rr sta (sp),y 0003B4r 1 A9 04 lda #$04 0003B6r 1 99 00 60 sta $6000,y 0003B9r 1 4C rr rr jmp incsp1 0003BCr 1 .endproc
- ポインタ変数とインデックス用変数を使う
unsigned char型のインデックス用変数の場合でもかなりコード量が増えます。
void test02_pointer_addr_5(void) { 000158r 1 .proc _test02_pointer_addr_5: near unsigned char i; 000158r 1 20 rr rr jsr decsp1 char *ptr = RAM; 00015Br 1 A2 60 ldx #$60 00015Dr 1 A9 00 lda #$00 i = 0; *(ptr + i) = 1; 00015Fr 1 20 rr rr jsr pushax i = 10; *(ptr + i) = 2; 000162r 1 A0 02 ldy #$02 i = 255; *(ptr + i) = 3; 000164r 1 91 rr sta (sp),y i = 256; *(ptr + i) = 4; 000166r 1 AA tax } 000167r 1 B1 rr lda (sp),y 000169r 1 18 clc 00016Ar 1 A0 00 ldy #$00 00016Cr 1 71 rr adc (sp),y 00016Er 1 85 rr sta ptr1 000170r 1 8A txa 000171r 1 C8 iny 000172r 1 71 rr adc (sp),y 000174r 1 85 rr sta ptr1+1 000176r 1 98 tya 000177r 1 88 dey 000178r 1 91 rr sta (ptr1),y 00017Ar 1 A9 0A lda #$0A 00017Cr 1 A0 02 ldy #$02 00017Er 1 91 rr sta (sp),y 000180r 1 18 clc 000181r 1 A0 00 ldy #$00 000183r 1 71 rr adc (sp),y 000185r 1 85 rr sta ptr1 000187r 1 8A txa 000188r 1 C8 iny 000189r 1 71 rr adc (sp),y 00018Br 1 85 rr sta ptr1+1 00018Dr 1 A9 02 lda #$02 00018Fr 1 88 dey 000190r 1 91 rr sta (ptr1),y 000192r 1 A9 FF lda #$FF 000194r 1 A0 02 ldy #$02 000196r 1 91 rr sta (sp),y 000198r 1 18 clc 000199r 1 A0 00 ldy #$00 00019Br 1 71 rr adc (sp),y 00019Dr 1 85 rr sta ptr1 00019Fr 1 8A txa 0001A0r 1 C8 iny 0001A1r 1 71 rr adc (sp),y 0001A3r 1 85 rr sta ptr1+1 0001A5r 1 A9 03 lda #$03 0001A7r 1 88 dey 0001A8r 1 91 rr sta (ptr1),y 0001AAr 1 8A txa 0001ABr 1 A0 02 ldy #$02 0001ADr 1 91 rr sta (sp),y 0001AFr 1 18 clc 0001B0r 1 A8 tay 0001B1r 1 71 rr adc (sp),y 0001B3r 1 85 rr sta ptr1 0001B5r 1 8A txa 0001B6r 1 C8 iny 0001B7r 1 71 rr adc (sp),y 0001B9r 1 85 rr sta ptr1+1 0001BBr 1 A9 04 lda #$04 0001BDr 1 88 dey 0001BEr 1 91 rr sta (ptr1),y 0001C0r 1 4C rr rr jmp incsp3 0001C3r 1 .endproc
[]演算子を使った場合でもコード量が一気に増えました。
void test02_pointer_array_5(void) { 000464r 1 .proc _test02_pointer_array_5: near unsigned char i; 000464r 1 20 rr rr jsr decsp1 char *ptr = RAM; 000467r 1 A2 60 ldx #$60 000469r 1 A9 00 lda #$00 i = 0; ptr[i] = 1; 00046Br 1 20 rr rr jsr pushax i = 10; ptr[i] = 2; 00046Er 1 A0 02 ldy #$02 i = 255; ptr[i] = 3; 000470r 1 91 rr sta (sp),y i = 256; ptr[i] = 4; 000472r 1 AA tax } 000473r 1 B1 rr lda (sp),y 000475r 1 18 clc 000476r 1 A0 00 ldy #$00 000478r 1 71 rr adc (sp),y 00047Ar 1 85 rr sta ptr1 00047Cr 1 8A txa 00047Dr 1 C8 iny 00047Er 1 71 rr adc (sp),y 000480r 1 85 rr sta ptr1+1 000482r 1 98 tya 000483r 1 88 dey 000484r 1 91 rr sta (ptr1),y 000486r 1 A9 0A lda #$0A 000488r 1 A0 02 ldy #$02 00048Ar 1 91 rr sta (sp),y 00048Cr 1 18 clc 00048Dr 1 A0 00 ldy #$00 00048Fr 1 71 rr adc (sp),y 000491r 1 85 rr sta ptr1 000493r 1 8A txa 000494r 1 C8 iny 000495r 1 71 rr adc (sp),y 000497r 1 85 rr sta ptr1+1 000499r 1 A9 02 lda #$02 00049Br 1 88 dey 00049Cr 1 91 rr sta (ptr1),y 00049Er 1 A9 FF lda #$FF 0004A0r 1 A0 02 ldy #$02 0004A2r 1 91 rr sta (sp),y 0004A4r 1 18 clc 0004A5r 1 A0 00 ldy #$00 0004A7r 1 71 rr adc (sp),y 0004A9r 1 85 rr sta ptr1 0004ABr 1 8A txa 0004ACr 1 C8 iny 0004ADr 1 71 rr adc (sp),y 0004AFr 1 85 rr sta ptr1+1 0004B1r 1 A9 03 lda #$03 0004B3r 1 88 dey 0004B4r 1 91 rr sta (ptr1),y 0004B6r 1 8A txa 0004B7r 1 A0 02 ldy #$02 0004B9r 1 91 rr sta (sp),y 0004BBr 1 18 clc 0004BCr 1 A8 tay 0004BDr 1 71 rr adc (sp),y 0004BFr 1 85 rr sta ptr1 0004C1r 1 8A txa 0004C2r 1 C8 iny 0004C3r 1 71 rr adc (sp),y 0004C5r 1 85 rr sta ptr1+1 0004C7r 1 A9 04 lda #$04 0004C9r 1 88 dey 0004CAr 1 91 rr sta (ptr1),y 0004CCr 1 4C rr rr jmp incsp3 0004CFr 1 .endproc
- 静的変数を使う
静的なポインタ変数を利用するだけで、先の例と比較すると結構コード量が減ります。
static char *static_ptr; void test02_pointer_addr_8(void) { 000280r 1 .proc _test02_pointer_addr_8: near unsigned char i; 000280r 1 20 rr rr jsr decsp1 static_ptr = RAM; 000283r 1 A2 60 ldx #$60 000285r 1 A9 00 lda #$00 i = 0; *(static_ptr + i) = 1; 000287r 1 8D rr rr sta _static_ptr i = 10; *(static_ptr + i) = 2; 00028Ar 1 8E rr rr stx _static_ptr+1 i = 255; *(static_ptr + i) = 3; 00028Dr 1 A8 tay i = 256; *(static_ptr + i) = 4; 00028Er 1 91 rr sta (sp),y } 000290r 1 18 clc 000291r 1 6D rr rr adc _static_ptr 000294r 1 AE rr rr ldx _static_ptr+1 000297r 1 90 01 bcc L0275 000299r 1 E8 inx 00029Ar 1 85 rr L0275: sta ptr1 00029Cr 1 86 rr stx ptr1+1 00029Er 1 A9 01 lda #$01 0002A0r 1 91 rr sta (ptr1),y 0002A2r 1 A9 0A lda #$0A 0002A4r 1 91 rr sta (sp),y 0002A6r 1 18 clc 0002A7r 1 6D rr rr adc _static_ptr 0002AAr 1 AE rr rr ldx _static_ptr+1 0002ADr 1 90 01 bcc L0276 0002AFr 1 E8 inx 0002B0r 1 85 rr L0276: sta ptr1 0002B2r 1 86 rr stx ptr1+1 0002B4r 1 A9 02 lda #$02 0002B6r 1 91 rr sta (ptr1),y 0002B8r 1 A9 FF lda #$FF 0002BAr 1 91 rr sta (sp),y 0002BCr 1 18 clc 0002BDr 1 6D rr rr adc _static_ptr 0002C0r 1 AE rr rr ldx _static_ptr+1 0002C3r 1 90 01 bcc L0277 0002C5r 1 E8 inx 0002C6r 1 85 rr L0277: sta ptr1 0002C8r 1 86 rr stx ptr1+1 0002CAr 1 A9 03 lda #$03 0002CCr 1 91 rr sta (ptr1),y 0002CEr 1 98 tya 0002CFr 1 91 rr sta (sp),y 0002D1r 1 18 clc 0002D2r 1 6D rr rr adc _static_ptr 0002D5r 1 AE rr rr ldx _static_ptr+1 0002D8r 1 90 01 bcc L0278 0002DAr 1 E8 inx 0002DBr 1 85 rr L0278: sta ptr1 0002DDr 1 86 rr stx ptr1+1 0002DFr 1 A9 04 lda #$04 0002E1r 1 91 rr sta (ptr1),y 0002E3r 1 4C rr rr jmp incsp1 0002E6r 1 .endproc
[]演算子を使った場合も、自動変数よりコンパクトです。
void test02_pointer_array_8(void) { 0005E2r 1 .proc _test02_pointer_array_8: near unsigned char i; 0005E2r 1 20 rr rr jsr decsp1 static_ptr = RAM; 0005E5r 1 A2 60 ldx #$60 0005E7r 1 A9 00 lda #$00 i = 0; static_ptr[i] = 1; 0005E9r 1 8D rr rr sta _static_ptr i = 10; static_ptr[i] = 2; 0005ECr 1 8E rr rr stx _static_ptr+1 i = 255; static_ptr[i] = 3; 0005EFr 1 A8 tay i = 256; static_ptr[i] = 4; 0005F0r 1 91 rr sta (sp),y } 0005F2r 1 18 clc 0005F3r 1 6D rr rr adc _static_ptr 0005F6r 1 AE rr rr ldx _static_ptr+1 0005F9r 1 90 01 bcc L027D 0005FBr 1 E8 inx 0005FCr 1 85 rr L027D: sta ptr1 0005FEr 1 86 rr stx ptr1+1 000600r 1 A9 01 lda #$01 000602r 1 91 rr sta (ptr1),y 000604r 1 A9 0A lda #$0A 000606r 1 91 rr sta (sp),y 000608r 1 18 clc 000609r 1 6D rr rr adc _static_ptr 00060Cr 1 AE rr rr ldx _static_ptr+1 00060Fr 1 90 01 bcc L027E 000611r 1 E8 inx 000612r 1 85 rr L027E: sta ptr1 000614r 1 86 rr stx ptr1+1 000616r 1 A9 02 lda #$02 000618r 1 91 rr sta (ptr1),y 00061Ar 1 A9 FF lda #$FF 00061Cr 1 91 rr sta (sp),y 00061Er 1 18 clc 00061Fr 1 6D rr rr adc _static_ptr 000622r 1 AE rr rr ldx _static_ptr+1 000625r 1 90 01 bcc L027F 000627r 1 E8 inx 000628r 1 85 rr L027F: sta ptr1 00062Ar 1 86 rr stx ptr1+1 00062Cr 1 A9 03 lda #$03 00062Er 1 91 rr sta (ptr1),y 000630r 1 98 tya 000631r 1 91 rr sta (sp),y 000633r 1 18 clc 000634r 1 6D rr rr adc _static_ptr 000637r 1 AE rr rr ldx _static_ptr+1 00063Ar 1 90 01 bcc L0280 00063Cr 1 E8 inx 00063Dr 1 85 rr L0280: sta ptr1 00063Fr 1 86 rr stx ptr1+1 000641r 1 A9 04 lda #$04 000643r 1 91 rr sta (ptr1),y 000645r 1 4C rr rr jmp incsp1 000648r 1 .endproc
- ゼロページ変数を使う
基本的には静的変数を利用する場合と変わりません。
メモリアドレッシングの違いから若干コンパクトにまとまります。extern Work zpw; #pragma zpsym ("zpw"); /* zpw is in the zeropage */ void test02_pointer_addr_z(void) { 00035Dr 1 .proc _test02_pointer_addr_z: near zpw.i = 0; *(zpw.ptr + zpw.i) = 1; 00035Dr 1 A9 00 lda #$00 zpw.i = 10; *(zpw.ptr + zpw.i) = 2; 00035Fr 1 85 rr sta _zpw zpw.i = 255; *(zpw.ptr + zpw.i) = 3; 000361r 1 A5 rr lda _zpw+2 zpw.i = 256; *(zpw.ptr + zpw.i) = 4; 000363r 1 A6 rr ldx _zpw+2+1 } 000365r 1 18 clc 000366r 1 65 rr adc _zpw 000368r 1 90 01 bcc L0279 00036Ar 1 E8 inx 00036Br 1 85 rr L0279: sta ptr1 00036Dr 1 86 rr stx ptr1+1 00036Fr 1 A9 01 lda #$01 000371r 1 A0 00 ldy #$00 000373r 1 91 rr sta (ptr1),y 000375r 1 A9 0A lda #$0A 000377r 1 85 rr sta _zpw 000379r 1 A5 rr lda _zpw+2 00037Br 1 A6 rr ldx _zpw+2+1 00037Dr 1 18 clc 00037Er 1 65 rr adc _zpw 000380r 1 90 01 bcc L027A 000382r 1 E8 inx 000383r 1 85 rr L027A: sta ptr1 000385r 1 86 rr stx ptr1+1 000387r 1 A9 02 lda #$02 000389r 1 91 rr sta (ptr1),y 00038Br 1 A9 FF lda #$FF 00038Dr 1 85 rr sta _zpw 00038Fr 1 A5 rr lda _zpw+2 000391r 1 A6 rr ldx _zpw+2+1 000393r 1 18 clc 000394r 1 65 rr adc _zpw 000396r 1 90 01 bcc L027B 000398r 1 E8 inx 000399r 1 85 rr L027B: sta ptr1 00039Br 1 86 rr stx ptr1+1 00039Dr 1 A9 03 lda #$03 00039Fr 1 91 rr sta (ptr1),y 0003A1r 1 84 rr sty _zpw 0003A3r 1 A5 rr lda _zpw+2 0003A5r 1 A6 rr ldx _zpw+2+1 0003A7r 1 18 clc 0003A8r 1 65 rr adc _zpw 0003AAr 1 90 01 bcc L027C 0003ACr 1 E8 inx 0003ADr 1 85 rr L027C: sta ptr1 0003AFr 1 86 rr stx ptr1+1 0003B1r 1 A9 04 lda #$04 0003B3r 1 91 rr sta (ptr1),y 0003B5r 1 60 rts 0003B6r 1 .endproc
変数を使う場合は、[]演算子でゼロページ変数を使った場合が一番コンパクトになります。
void test02_pointer_array_z(void) { 0006BFr 1 .proc _test02_pointer_array_z: near zpw.i = 0; zpw.ptr[zpw.i] = 1; 0006BFr 1 A9 00 lda #$00 zpw.i = 10; zpw.ptr[zpw.i] = 2; 0006C1r 1 85 rr sta _zpw zpw.i = 255; zpw.ptr[zpw.i] = 3; 0006C3r 1 A5 rr lda _zpw+2 zpw.i = 256; zpw.ptr[zpw.i] = 4; 0006C5r 1 A6 rr ldx _zpw+2+1 } 0006C7r 1 18 clc 0006C8r 1 65 rr adc _zpw 0006CAr 1 90 01 bcc L0281 0006CCr 1 E8 inx 0006CDr 1 85 rr L0281: sta ptr1 0006CFr 1 86 rr stx ptr1+1 0006D1r 1 A9 01 lda #$01 0006D3r 1 A0 00 ldy #$00 0006D5r 1 91 rr sta (ptr1),y 0006D7r 1 A9 0A lda #$0A 0006D9r 1 85 rr sta _zpw 0006DBr 1 A5 rr lda _zpw+2 0006DDr 1 A6 rr ldx _zpw+2+1 0006DFr 1 18 clc 0006E0r 1 65 rr adc _zpw 0006E2r 1 90 01 bcc L0282 0006E4r 1 E8 inx 0006E5r 1 85 rr L0282: sta ptr1 0006E7r 1 86 rr stx ptr1+1 0006E9r 1 A9 02 lda #$02 0006EBr 1 91 rr sta (ptr1),y 0006EDr 1 A9 FF lda #$FF 0006EFr 1 85 rr sta _zpw 0006F1r 1 A5 rr lda _zpw+2 0006F3r 1 A6 rr ldx _zpw+2+1 0006F5r 1 18 clc 0006F6r 1 65 rr adc _zpw 0006F8r 1 90 01 bcc L0283 0006FAr 1 E8 inx 0006FBr 1 85 rr L0283: sta ptr1 0006FDr 1 86 rr stx ptr1+1 0006FFr 1 A9 03 lda #$03 000701r 1 91 rr sta (ptr1),y 000703r 1 84 rr sty _zpw 000705r 1 A5 rr lda _zpw+2 000707r 1 A6 rr ldx _zpw+2+1 000709r 1 18 clc 00070Ar 1 65 rr adc _zpw 00070Cr 1 90 01 bcc L0284 00070Er 1 E8 inx 00070Fr 1 85 rr L0284: sta ptr1 000711r 1 86 rr stx ptr1+1 000713r 1 A9 04 lda #$04 000715r 1 91 rr sta (ptr1),y 000717r 1 60 rts 000718r 1 .endproc
- 定数値を使う
-
上記を含めたテストコードをまとめたものはこちら。
なお、コンパイル結果を楽しむものです。実行はできません。- nesapp-asmtest.zipアセンブラソースリスト出力テスト用プロジェクト
ついでにcc65@wiki様の「kihon.h」のパッドの変数をゼロページに配置してみたサンプルです。
動作確認用のプロジェクトも作業用の変数とコサインテーブルをゼロページに配置したバージョンです。
テーブルコピーの関数が一つ増えたにも関わらず、プログラムサイズは数バイトですが減りました。- nesapp-libkihon2.zip「ライブラリ本体」
- nesapp-test_libkihon2.zip「ライブラリの動作確認用プロジェクト」
Exports list by value:
----------------------
sp 000002 RLZ sreg 000004 RLZ
regsave 000006 RLZ ptr1 00000A RLZ
ptr4 000010 RLZ tmp1 000012 RLZ
nsd_work_zp 00001C RLZ __STACKSIZE__ 000200 REA
_chara 000400 RLA _ball 000450 RLA
_game 000458 RLA _gamemap 00045E RLA
_rand_x 000522 RLA _rand_y 000523 RLA
_rand_z 000524 RLA _sync 000525 RLA
_padinfo 000526 RLA _padother 00052C RLA
_dma_cnt 00052D RLA _dma_cnt_old 00052E RLA
_cmd_cnt 00052F RLA _screen_adr 000530 RLA
_scroll_x 000532 RLA _scroll_y 000533 RLA