起因

想给 FC/NES、C64 这类 6502 老机器写程序,第一道坎是“在现代电脑上怎么给它编代码”:这些机器的内存和速度撑不起自己编译自己,工具链必须跑在 PC 上、产出目标机的机器码——这就是交叉编译。cc65 是 6502 体系最完整的开源交叉开发包。

软件简介

cc65 是 65(C)02系统的完整交叉开发包,包括强大的宏汇编器,C编译器,链接器,库管理器和其他几个工具。

最重要的是这个可以开发红白机游戏 FC / NES

cc65为许多旧的6502机器提供C和运行时库支持,包括:

技术实现原理

交叉编译:宿主与目标分离

编译器跑在 PC(宿主)上,生成的是 6502 指令。宿主/目标分离带来两件事:一是每个目标机要有自己的运行时库、启动代码和链接配置——同一份 C 源码,-t nes-t c64 编出来的二进制完全不同;二是调试要回到模拟器/实机,宿主上可用包里的 sim65 直接跑 6502 程序。

6502 是“对 C 最不友好”的 CPU 之一

C 的抽象模型(16 位以上整数、任意深度递归、指针随处可指)在 6502 上都有物理障碍,cc65 为此做了一堆底层工作,这也决定了它的代码风格:

  • 没有 16 位寄存器:A/X/Y 全是 8 位,一个 int(cc65 里 16 位)的加减要拆成多条 8 位指令完成;
  • 间接寻址只认零页:按指针访问数据必须借道 $00–$FF 的零页单元,编译器把最常用的变量放零页——零页只有 256 字节,是全机最挤的资源;
  • 硬件栈只有 256 字节且不能索引:$0100 页基本只放返回地址,C 的局部变量和参数传递靠 cc65 在普通内存里自建的软件栈,每次函数调用都有可观的固定开销;
  • 65C02 比 6502 多了若干指令(PHX/PLY、(zp,X) 寻址等),效率略好,架构限制不变。

所以 cc65 的定位很明确:用 C 写游戏逻辑、快速出原型;性能热点(帧循环、图形例程)用包里的 ca65 汇编写,两种代码在链接阶段合成一个二进制。

工具链分工

  • cc65:C 编译器,先把 C 翻成汇编;
  • ca65:宏汇编器,汇编成目标文件;
  • ld65:链接器,按 cfg 配置文件描述的目标机内存布局(比如 NES 的 PRG/CHR bank)分配地址——移植到新板卡时最常改的就是它;
  • cl65:驱动程序,一条命令串起编译、汇编、链接(cl65 -t nes -o demo.nes demo.c);
  • ar65:库管理器;sim65:宿主上的 6502 模拟器。

支持的机器

以下Commodore机器:

VIC20

C16 / C116和Plus / 4

C64

C128

CBM 510(又名P500)

600/700系列

较新的PET机器(不是2001年)。

Apple ][(苹果II)及其后继机型。

Atari 8位机器。

Atari 2600控制台。

Atari 5200控制台。

适用于C64、C128和Apple //e的GEOS。

Bit Corporation Gamate控制台。

NEC PC-Engine(又名TurboGrafx-16)控制台。

任天堂娱乐系统(NES)控制台。

Watara监督控制台。

VTech Creativision控制台。

Oric Atmos。

Oric Telestrat。

Lynx控制台。

Ohio Scientific Challenger 1P。

实战参考

本站《CC65 开发FC/NES 游戏》系列就是用它开发的:(三)使用摇杆(四)方向键控制一个简单的精灵

踩坑记录与注意事项

  • 性能预期要重校准:软件栈 + 8 位寄存器,C 代码比手写汇编大数倍、慢数倍是常态,性能热点直接上 ca65;
  • 零页是硬约束:只有 256 字节,C 运行时、驱动库、汇编模块都在抢,链接报零页溢出就是超了;
  • int 是 16 位:循环计数、数组下标尽量用 unsigned char,省指令也省内存;
  • 标准是 C89/C90 加部分 C99:更新的语法特性别硬上,编译器报错不一定直白;
  • 跑飞/花屏先查 ld65 的 cfg:内存布局、bank 划分错了,C 代码再对也没用;
  • 中断服务函数用 interrupt 关键字声明:让编译器生成完整的现场保护,拿普通函数当 ISR 会打乱运行时的寄存器约定。