C言語でNESアプリを作成(標準関数を使ったサンプル)

cc65に付属する標準関数を使って簡単なサンプルを作成してみました。
conio.h をインクルードするとgotoxy()とcprintf()の組み合わせで画面に処理結果を表示できます。
言語は違いますが、昔の8ビットPCな雰囲気を味わえます。

型サイズ
基本型、配列、構造体のサイズを表示するサンプルです。
#include <stdlib.h>
#include <conio.h>

int main(void)
{
    unsigned int frame = 0;
    int y = 1;

    // 画面の消去
    clrscr();

    // 型サイズの表示
    gotoxy(1, y++); cprintf("sizeof(char)  =%d", sizeof(char));
    gotoxy(1, y++); cprintf("sizeof(short) =%d", sizeof(short));
    gotoxy(1, y++); cprintf("sizeof(int)   =%d", sizeof(int));
    gotoxy(1, y++); cprintf("sizeof(long)  =%d", sizeof(long));
    gotoxy(1, y++); cprintf("sizeof(float) =%d", sizeof(float));
    gotoxy(1, y++); cprintf("sizeof(double)=%d", sizeof(double));
    gotoxy(1, y++); cprintf("sizeof(size_t)=%d", sizeof(size_t));
    gotoxy(1, y++); cprintf("sizeof(void*) =%d", sizeof(void*));

    y++;

    // 配列サイズの表示
    {
        const static char array[1024];
        gotoxy(1, y++); cprintf("const static char array[1024];");
        gotoxy(1, y++); cprintf("sizeof(array)=%d", sizeof(array));
        gotoxy(1, y++); cprintf("sizeof(array[0])=%d", sizeof(array[0]));
        gotoxy(1, y++); cprintf("sizeof(&array)=%d", sizeof(&array));
    }

    y++;

    // 構造体サイズの表示
    {
        struct Foo {
            char name[5];
            int exp;
            long flags;
        };
        struct Foo foo[3];

        gotoxy(1, y++); cprintf("struct Foo {");
        gotoxy(1, y++); cprintf("    char name[5];");
        gotoxy(1, y++); cprintf("    int exp;");
        gotoxy(1, y++); cprintf("    long flags;");
        gotoxy(1, y++); cprintf("};");
        gotoxy(1, y++); cprintf("sizeof(struct Foo)=%d", sizeof(struct Foo));

        y++;
        gotoxy(1, y++); cprintf("struct Foo foo[3]");
        gotoxy(1, y++); cprintf("sizeof(foo)=%d", sizeof(foo));
        gotoxy(1, y++); cprintf("sizeof(foo[0])=%d", sizeof(foo[0]));
        gotoxy(1, y++); cprintf("sizeof(&foo)=%d", sizeof(&foo));
    }

    // 処理を終わらせないための無限ループ
    while(1) {
        gotoxy(0, 0);
        cprintf("%8d", frame++);
    }

    return 0;
}

8ビットCPUですがint型のサイズは2バイトです。
読み込み専用であれば、ちょっと大きめの配列も作成できます。
構造体も作れます。変数間のパディングがありません。

*注意*
floatとdoubleはサポートされていません。実機では画面が崩れます。
なお、浮動小数点数を代入等で実際に使用した場合は、以下のコンパイルエラーが表示されます。
Fatal: Floating point type is currently unsupported

配列
配列操作を行うサンプルです。
#include <stddef.h>
#include <stdlib.h>
#include <conio.h>

void print_array(const char array[], int count) {
    int i;

    for (i = 0; i < count; ++i) {
        unsigned char a = array[i];

        cprintf("%02x ", a);
    }
}

void swap_array(char array[], int count) {
    int i;

    for (i = 0; i < count/2; ++i) {
        int j = count - 1 - i;
        char w = array[i];

        array[i] = array[j];
        array[j] = w;
    }
}

int compare_signed(const void* p0, const void* p1) {
    // 符号ありで比較 (char型を符号付きで扱う場合は、コンパイラオプションで-jを指定すること)
    const char* d0 = p0;
    const char* d1 = p1;

    return *d0 - *d1;
}

int compare_unsigned(const void* p0, const void* p1) {
    // 符号なしで比較
    const unsigned char* d0 = p0;
    const unsigned char* d1 = p1;

    return *d0 - *d1;
}

struct Character {
    char name[5];
    int max_hp;
    int hp;
};

void print_characters(int x, int y, const struct Character* characters, int count) {
    int i;

    for (i = 0; i < count; ++i) {
        int row = y;
        int col = x + i * 9;

        gotoxy(col, row  ); cprintf("+-%-5.5s-+", characters[i].name);
        gotoxy(col, row+1); cprintf("|HP :%3d|", characters[i].hp);
        gotoxy(col, row+2); cprintf("|MAX:%3d|", characters[i].max_hp);
        gotoxy(col, row+3); cprintf("+-------+");
    }
}

void recovery_characters(struct Character* characters, int count) {
    int i;

    for (i = 0; i < count; ++i) {
        characters[i].hp = characters[i].max_hp;
    }
}

int main(void)
{
    unsigned int frame = 0;
    int y = 1;

    // 画面の消去
    clrscr();

    // 配列操作
    {
        char array[] = {0x1a,0x1b,0x1c,0x1d,0x1e,0x1f};

        gotoxy(1, y++); cprintf("array=%p", array);
        gotoxy(1, y++); cprintf("sizeof(array)   =%d", sizeof(array));
        gotoxy(1, y++); cprintf("sizeof(array[0])=%d", sizeof(array[0]));

        // 配列を表示
        gotoxy(1, y++); print_array(array, sizeof(array)/sizeof(array[0]));

        // 配列内の入れ替え
        swap_array(array, sizeof(array)/sizeof(array[0]));

        // 配列を表示
        gotoxy(1, y++); print_array(array, sizeof(array)/sizeof(array[0]));
    }

    y++;

    {
        char array[] = {0x51,0xa2,0x43,0xf4,0x25,0x56};

        // 配列を表示
        gotoxy(1, y++); print_array(array, sizeof(array)/sizeof(array[0]));

        // 配列内のソート(符号付き)
        gotoxy(1, y++); cprintf("qsort(compare_signed);");
        qsort(array, sizeof(array)/sizeof(array[0]), sizeof(array[0]), compare_signed);

        // 配列を表示
        gotoxy(1, y++); print_array(array, sizeof(array)/sizeof(array[0]));
    }

    y++;

    {
        char array[] = {0x51,0xa2,0x43,0xf4,0x25,0x56};

        // 配列を表示
        gotoxy(1, y++); print_array(array, sizeof(array)/sizeof(array[0]));

        // 配列内のソート(符号なし)
        gotoxy(1, y++); cprintf("qsort(compare_unsigned);");
        qsort(array, sizeof(array)/sizeof(array[0]), sizeof(array[0]), compare_unsigned);

        // 配列を表示
        gotoxy(1, y++); print_array(array, sizeof(array)/sizeof(array[0]));
    }

    y++;

    {
        char array[] = {0x03,0x12,0x3a,0x5b,0x6c,0x75};
        char key = 0x75;
        char *p = NULL;

        // 配列を表示
        gotoxy(1, y++); print_array(array, sizeof(array)/sizeof(array[0]));

        // 配列内の検索
        gotoxy(1, y++); cprintf("bsearch(%02x,compare_signed);", key);
        p = bsearch(&key, array, sizeof(array)/sizeof(array[0]), sizeof(array[0]), compare_signed);

        // 検索位置を表示
        gotoxy(1, y++); cprintf("array=%p, p=%p, ofs=%d", array, p, p-array);
    }

    y++;

    {
        // 構造体の配列操作
        struct Character characters[] = {
            {"TOM", 25},
            {"SAM", 20},
            {"ANNY", 15},
        };

        // 構造体の内容を表示
        print_characters(1, y, characters, sizeof(characters)/sizeof(characters[0]));
        y += 4;

        // 値を設定
        gotoxy(1, y++); cprintf("recovery_characters();");
        recovery_characters(characters, sizeof(characters)/sizeof(characters[0]));

        // 構造体の内容を表示
        print_characters(1, y, characters, sizeof(characters)/sizeof(characters[0]));
        y += 4;
    }

    // 処理を終わらせないための無限ループ
    while(1) {
        gotoxy(0, 0);
        cprintf("%8d", frame++);
    }

    return 0;
}
自動変数としての配列が作成でき、初期値の設定も行ってくれます。
ソート関数を呼び出してデータの並び替えもできます。
構造体の配列も扱えます。
メモリアクセス
メモリアクセスを行うサンプルです。
大域変数と局所変数が配置されている場所の確認と、標準関数のうちメモリアクセスに関するものをいくつか呼び出してみました。
#include <stdlib.h>
#include <conio.h>
#include <string.h>

const char FMT_VALUE[] = "%-6s %1c %1c %3d %p";
const char GLOBAL[] = "global";
const char LOCAL[] = "local";
const char CHECKED = '*';
const char UNCHECKED = ' ';

const int global_const_v = 100;
int global_v = 110;

void print_array(const char array[], int count) {
    int i;

    for (i = 0; i < count; ++i) {
        unsigned char a = array[i];

        cprintf("%02x ", a);
    }
}

int main(void)
{
    unsigned int frame = 0;
    int y = 1;

    // 画面の消去
    clrscr();

    // メモリ配置テスト
    {
        const static int local_const_static_v = 200;
        const int local_const_v = 210;
        static int local_static_v = 220;
        int local_v = 230;

        gotoxy(1, y++); cprintf("Memory C S V   Addr");
        gotoxy(1, y++); cprintf("------ - - --- ------");
        gotoxy(1, y++); cprintf(FMT_VALUE, GLOBAL, CHECKED, CHECKED,
                                global_const_v, &global_const_v);
        gotoxy(1, y++); cprintf(FMT_VALUE, GLOBAL, UNCHECKED, CHECKED,
                                global_v, &global_v);
        gotoxy(1, y++); cprintf(FMT_VALUE, LOCAL, CHECKED, CHECKED,
                                local_const_static_v, &local_const_static_v);
        gotoxy(1, y++); cprintf(FMT_VALUE, LOCAL, CHECKED, UNCHECKED,
                                local_const_v, &local_const_v);
        gotoxy(1, y++); cprintf(FMT_VALUE, LOCAL, UNCHECKED, CHECKED,
                                local_static_v, &local_static_v);
        gotoxy(1, y++); cprintf(FMT_VALUE, LOCAL, UNCHECKED, UNCHECKED,
                                local_v, &local_v);
    }

    y++;

    // メモリアクセス関数テスト
    {
        // memchr
        char a[] = {'A','B','c','d'};
        char *p;

        gotoxy(1, y++); cprintf("a[]=%4.4s", a);
        p = memchr(a, 'A', sizeof(a));
        gotoxy(2, y++); cprintf("memchr(%p,'A',%d)=>%p", a, sizeof(a), p);
        p = memchr(a, 'c', sizeof(a));
        gotoxy(2, y++); cprintf("memchr(%p,'c',%d)=>%p", a, sizeof(a), p);
    }

    y++;

    {
        // memcmp
        char a1[] = {'A','b','\0','c'};
        char a2[] = {'b','\0','c'};
        int result;

        gotoxy(1, y++); cprintf("a1[]="); print_array(a1, sizeof(a1)/sizeof(a1[0]));
        gotoxy(1, y++); cprintf("a2[]="); print_array(a2, sizeof(a2)/sizeof(a2[0]));
        result = memcmp(a1, a2, sizeof(a2));
        gotoxy(2, y++); cprintf("memcmp(a1,a2)=>%d", result);
        result = memcmp(&a1[1], a2, sizeof(a2));
        gotoxy(2, y++); cprintf("memcmp(&a1[1],a2)=>%d", result);
    }

    y++;

    {
        // memmove
        char a[] = {1, 2, 3, 4};

        gotoxy(1, y++); cprintf("a[]="); print_array(a, sizeof(a)/sizeof(a[0]));
        memmove(&a[1], &a[2], 2);
        gotoxy(2, y++); cprintf("memmove(&a[1],&a[2],2)");
        gotoxy(1, y++); cprintf("a[]="); print_array(a, sizeof(a)/sizeof(a[0]));
    }

    y++;

    {
        // memset
        char a[4] = {0};

        gotoxy(1, y++); cprintf("a[]="); print_array(a, sizeof(a)/sizeof(a[0]));
        memset(a, 255, sizeof(a));
        gotoxy(2, y++); cprintf("memset(a,255,sizeof(a))");
        gotoxy(1, y++); cprintf("a[]="); print_array(a, sizeof(a)/sizeof(a[0]));
    }

    y++;

    // メモリ領域制御関数テスト
    {
        // memset, malloc, free
        char *p = malloc(sizeof(char)*8);
        gotoxy(1, y++); cprintf("malloc(sizeof(char)*8)=>%p", p);
        free(p);
    }

    // 処理を終わらせないための無限ループ
    while(1) {
        gotoxy(0, 0);
        cprintf("%8d", frame++);
    }

    return 0;
}

大域変数でconst修飾子付き、あるいは局所変数でconst staticな変数はROM領域、
大域変数でconst修飾子なし、あるいは局所変数でstaticな変数はバッテリーバックアップなどで使われる拡張RAM領域、
その他の局所変数はRAM領域にマッピングされています。

ヘッダファイルを参照すると以下の関数群が宣言されています。

string.h
void* __fastcall__ memchr (const void* mem, int c, size_t count);
int __fastcall__ memcmp (const void* p1, const void* p2, size_t count);
void* __fastcall__ memcpy (void* dest, const void* src, size_t count);
void* __fastcall__ memmove (void* dest, const void* src, size_t count);
void* __fastcall__ memset (void* s, int c, size_t count);
stdlib.h
void* __fastcall__ malloc (size_t size);
void* __fastcall__ calloc (size_t count, size_t size);
void* __fastcall__ realloc (void* block, size_t size);
void __fastcall__ free (void* block);
mem系は普通に使えますが、alloc系の関数はNULLポインタを返却しますので使えません。

文字列
文字や文字列操作を行うサンプルです。
標準関数をいくつか呼び出してみました。
#include <stdlib.h>
#include <stdio.h>
#include <conio.h>
#include <locale.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>

int main(void)
{
    unsigned int frame = 0;
    int y = 1;

    // 画面の消去
    clrscr();

    // ロケール確認
    {
        char *p = setlocale(LC_ALL, NULL);

        if (p == NULL) {
            p = strerror(errno);
        }
        gotoxy(1, y++); cprintf("locale: %s", p);
    }

    // ロケール設定
    {
        char *p = setlocale(LC_ALL, "JPN");

        if (p == NULL) {
            p = strerror(errno);
        }
        gotoxy(1, y++); cprintf("locale: %s", p);
    }

    y++;

    // 文字列長
    {
        char s[] = "ABCDEFGHIJ";
        size_t l = strlen(s);

        gotoxy(1, y++); cprintf("%s strlen()=%u", s, l);
    }

    y++;

    // 文字列比較
    {
        char s1[] = "ABCDEF";
        char s2[] = "ABC";
        char s3[] = "ABCDEF";
        int c1 = strncmp(s1, s2, sizeof(s1));
        int c2 = strncmp(s1, s3, sizeof(s1));

        gotoxy(1, y++); cprintf("strncmp(%s,%s,%d)=%d", s1, s2, sizeof(s1), c1);
        gotoxy(1, y++); cprintf("strncmp(%s,%s,%d)=%d", s1, s3, sizeof(s1), c2);
    }

    y++;

    // 文字列連結
    {
        char s1[10] = "ABC";
        char s2[]   = "DEF";
        char *p;

        gotoxy(1, y++); cprintf("strncat(%s,%s,%d)=>", s1, s2, sizeof(s1));
        p = strncat(s1, s2, sizeof(s1));
        cprintf("%s", p);
    }

    y++;

    // 文字列複写
    {
        char s1[10] = "ABCD";
        char s2[]   = "EFG";
        char *p;

        gotoxy(1, y++); cprintf("strncpy(%s,%s,%d)=>", s1, s2, sizeof(s1));
        p = strncpy(s1, s2, sizeof(s1));
        cprintf("%s", p);
    }

    y++;

    // 文字検索
    {
        char s[] = "ABCDEF";
        char c1 = 'E';
        char c2 = 'B';
        char *p;

        gotoxy(1, y++); cprintf("strchr(%s,%c)=>", s, c1);
        p = strchr(s, c1);
        cprintf("%s", p);

        gotoxy(1, y++); cprintf("strrchr(%s,%c)=>", s, c2);
        p = strrchr(s, c2);
        cprintf("%s", p);
    }

    y++;

    // 文字列検索
    {
        char s1[] = "ABCDEF";
        char s2[] = "DE";
        char *p;

        gotoxy(1, y++); cprintf("strstr(%s,%s)=>", s1, s2);
        p = strstr(s1, s2);
        cprintf("%s", p);
    }
    {
        char s1[] = "ABCABCDE";
        char s2[] = "ABC";
        char s3[] = "DE";
        size_t len;

        gotoxy(1, y++); cprintf("strspn(%s,%s)=>", s1, s2);
        len = strspn(s1, s2);
        cprintf("%u", len);

        gotoxy(1, y++); cprintf("strcspn(%s,%s)=>", s1, s3);
        len = strcspn(s1, s3);
        cprintf("%u", len);
    }


    y++;

    // 文字列分解
    {
        char s1[] = "val=ABC|DE|FG";
        char s2[] = "=";
        char s3[] = "|";
        char *p;
        int i;

        gotoxy(1, y++); cprintf("strtok(%s,%s)=>", s1, s2);
        p = strtok(s1, s2);
        cprintf("%s", p);

        for (i = 0; ; ++i) {
            p = strtok(NULL, s3);
            if (p == NULL) break;
            gotoxy(1, y++); cprintf("%d: strtok(NULL,%s)=>%s", i, s3, p);
        }
    }

    y++;

    // 文字列変換
    {
        char s[12] = {'\0'};
        int v = 0xa5;
        int r = 2;
        char *p;

        gotoxy(1, y++); cprintf("itoa(%d,%s,%d)=>", v, s, r);
        p = itoa(v, s, r);
        cprintf("%s", p);
    }

    y++;

    // 文字判定
    {
        char s[30];
        int c = 'C';
        int len;

        len = snprintf(s, sizeof(s), "'%c' Is alpha=%d, lower=%d", c, isalpha(c), islower(c));
        gotoxy(1, y++); cprintf("%s (%d)", s, len);
    }

    // 処理を終わらせないための無限ループ
    while(1) {
        gotoxy(0, 0);
        cprintf("%8d", frame++);
    }

    return 0;
}

ヘッダファイルを参照すると以下の関数群が宣言されています。

string.h
char* __fastcall__ strcat (char* dest, const char* src);
char* __fastcall__ strchr (const char* s, int c);
int __fastcall__ strcmp (const char* s1, const char* s2);
int __fastcall__ strcoll (const char* s1, const char* s2);
char* __fastcall__ strcpy (char* dest, const char* src);
size_t __fastcall__ strcspn (const char* s1, const char* s2);
char* __fastcall__ strerror (int errcode);
size_t __fastcall__ strlen (const char* s);
char* __fastcall__ strncat (char* s1, const char* s2, size_t count);
int __fastcall__ strncmp (const char* s1, const char* s2, size_t count);
char* __fastcall__ strncpy (char* dest, const char* src, size_t count);
char* __fastcall__ strrchr (const char* s, int c);
size_t __fastcall__ strspn (const char* s1, const char* s2);
char* __fastcall__ strstr (const char* str, const char* substr);
char* __fastcall__ strtok (char* s1, const char* s2);
size_t __fastcall__ strxfrm (char* s1, const char* s2, size_t count);
ctype.h
int __fastcall__ isalnum (int c);
int __fastcall__ isalpha (int c);
int __fastcall__ iscntrl (int c);
int __fastcall__ isdigit (int c);
int __fastcall__ isgraph (int c);
int __fastcall__ islower (int c);
int __fastcall__ isprint (int c);
int __fastcall__ ispunct (int c);
int __fastcall__ isspace (int c);
int __fastcall__ isupper (int c);
int __fastcall__ isxdigit (int c);
int __fastcall__ isblank (int c);       /* New in C99 */
int __fastcall__ toupper (int c);
int __fastcall__ tolower (int c);
stdio.h
int snprintf (char* buf, size_t size, const char* format, ...);
int sprintf (char* buf, const char* format, ...);
ASCII文字に限定されますが、特に問題なく使用できます。

数学関数
数学関数を扱うサンプルです。
#include <stdlib.h>
#include <conio.h>
#include <cc65.h>

int main(void)
{
    unsigned int frame = 0;
    int y = 1;

    // 画面の消去
    clrscr();

    // 乱数
    {
        unsigned int seed = 11;

        srand(seed);
        gotoxy(1, y++); cprintf("srand(%u)", seed);
        gotoxy(1, y++); cprintf("rand()=%d, %d, %d", rand(), rand(), rand());

        srand(seed);
        gotoxy(1, y++); cprintf("srand(%u)", seed);
        gotoxy(1, y++); cprintf("rand()=%d, %d, %d", rand(), rand(), rand());
    }

    y++;

    // 絶対値
    {
        int a = -10;
        int b = 0xfffe;

        gotoxy(1, y++); cprintf("abs(%d)=>%d", a, abs(a));
        gotoxy(1, y++); cprintf("abs(%d)=>%d", b, abs(b));
    }

    y++;

    // 剰余
    {
        int n = 100;
        int d = 12;
        div_t v;

        v = div(n, d);
        gotoxy(1, y++); cprintf("div(%d,%d)>rem:%d,quot:%d", n, d, v.rem, v.quot);
    }

    y++;

    // 割り算
    {
        long r = 100;
        int  l = 12;
        long v;

        v = cc65_idiv32by16r16(r, l);
        gotoxy(1, y++); cprintf("cc65_idiv32by16r16(%ld,%d)", r, l);
        gotoxy(1, y++); cprintf("=>%ld %lx", v, v);
    }

    y++;

    // 掛け算
    {
        int l = 32765;
        int r = 100;
        long v;

        v = cc65_imul16x16r32(l, r);
        gotoxy(1, y++); cprintf("cc65_imul16x16r32(%d,%d)", l, r);
        gotoxy(1, y++); cprintf(">%ld %lx", v, v);
    }

    y++;

    // 処理を終わらせないための無限ループ
    while(1) {
        gotoxy(0, 0);
        cprintf("%8d", frame++);

        // 三角関数
        {
            const static char pt[] = {'o', '*', '.', ' '};
            static unsigned int d = 0;
            unsigned int r = d % 360;
            char ch = pt[(d / 360) % sizeof(pt)];
            int s = cc65_sin(r);
            int c = cc65_cos(r);

            gotoxy(1, y); cprintf("cc65_sin/cos(%4u)=%4d,%4d", r, s, c);
            gotoxy(1 + c/32 + 14, y + s/64 + 7); cprintf("%c", ch);
            d += 5;
        }
    }

    return 0;
}

ヘッダファイルを参照すると以下の関数群が宣言されています。

stdlib.h
int rand (void);
void __fastcall__ srand (unsigned seed);
int __fastcall__ abs (int val);
long __fastcall__ labs (long val);
int __fastcall__ atoi (const char* s);
long __fastcall__ atol (const char* s);
div_t __fastcall__ div (int numer, int denom);
cc65.h
long __fastcall__ cc65_idiv32by16r16 (long rhs, int lhs);
unsigned long __fastcall__ cc65_udiv32by16r16 (unsigned long rhs, unsigned lhs);
int __fastcall__ cc65_imul8x8r16 (signed char lhs, signed char rhs);
long __fastcall__ cc65_imul16x16r32 (int lhs, int rhs);
unsigned __fastcall__ cc65_umul8x8r16 (unsigned char lhs, unsigned char rhs);
unsigned long __fastcall__ cc65_umul16x8r32 (unsigned lhs, unsigned char rhs);
unsigned long __fastcall__ cc65_umul16x16r32 (unsigned lhs, unsigned rhs);
int __fastcall__ cc65_sin (unsigned x);
int __fastcall__ cc65_cos (unsigned x);
浮動小数点数は未サポートのためmath.hは用意されていません。
cc65.hに長整数演算の掛け算、割り算と整数演算のsin()/cos()が用意されています。

サンプルプロジェクト

上記までのファイルをまとめたものはこちら。

  1. 基本型、配列、構造体のサイズを表示する
  2. 配列操作を行う
  3. メモリアクセスを行う
  4. 文字列操作を行う
  5. 数学関数を扱う