Kompilator języka C dla małego Atari
- TeBe / Madteam
Witajcie drodzy czytelnicy, w tym artykuliku TeBe/Madteam przybliży trochę język C w wydaniu na 8-bitowe Atari. Z kompilatorem C dla rodzinki 6502 mogliście się też spotkać czytając artykuł FOX-a w nr 1 Atarynki z 2002 roku.
Jeśli jednak go nie czytaliście, bo macie np. uczulenie na papier ;) to w skrócie przypomnę o co chodzi.
Sam program piszemy na PC w dowolnym edytorze tekstu jak Notatnik czy CodeGenie itp, a kompilator C czyli cc65 zamieni nam go na kod maszynowy 6502. Cały pakiet programów do tworzenia w C znajdziemy na stronie domowej projektu http://www.cc65.org
Możemy też od razu wpaść na ftp-a ftp.musoftware.de/pub/uz/cc65/ i pobrać plik z bibliotekami dla Atari XE/XL "cc65-atari-2.10.0-1.zip" oraz plik z kompilatorem przeznaczonym dla konkretnej platformy OS/2, Linux, Windows, np. "cc65-win32-2.10.0a-1.zip" czyli Windows.
W tym arykule skupie się właśnie na wersji cc65 dla Windows. Jeśli mamy już ściągnięte oba pliki, znajdujemy dla nich miejsce na którejś z partycji (ja wybrałem C) i rozpakowujemy je do np. katalogu C:\\CC65
Teraz musimy jeszcze ustawić troche zmiennych środowiskowych, aby kompilator, linker wiedział gdzie co jest. W przypadku rodziny Windows 9x, dopisujemy w pliku AUTOEXEC.BAT:
PATH=tutaj cos juz jest;C:\\CC65\\BIN SET CC65_INC=C:\\CC65\\INCLUDE SET CC65_LIB=C:\\CC65\\LIB SET LD65_CFG=C:\\CC65\\DOC
W przypadku Windows XP (i chyba też 2000), robimy to następująco:
Mój Komputer -> Właściwości -> Zaawansowane -> Zmienne środowiskowe -> dopisujemy w oknie Zmienne systemowe, czyli klikamy "Nowa" i uzupełniamy oba pola "Nazwa zmiennej" i "Wartość zmiennej" podobnie jak ma to miejsce w AUTOEXEC-u.
Teraz resetujemy komputer i możemy zacząć bawić się cc65 pod Windowsem.
W katalogu C:\\CC65\\BIN znajdują się wszystkie pliki wykonywalne pakietu cc65:
ar65 - program do tworzenia bibliotek *.lib i operacji na nich, np. ar65 l atari.lib wyswietli nam listę modułów wchodzących w skład bibioteki atari.lib ca65 - makro assembler dla procesorów 6502, 65C02 i 65816 cc65 - kompilator języka C przeznaczony dla rodziny procesorów 6502 cl65 - kompilator i linker w jednym co65 - programik do operacji na skompilowanych plikach *.o od65 - też coś robi z plikami *.o da65 - disassemler, stosuje zapis strawny na makro assemblera ca65 grc - to się Atarowcom nie przyda, bo jest to kompilator dla systemu GEOS z C64 ld65 - linker, czyli programik który składa do kupy wszystko i tworzy atarowskiego exe-ka
Teraz wypadałoby coś skompilować, czas więc na nieśmiertelny przykład, pewnie w Basic`u też to pisaliście.
Przykład 1:
#includeint main (void) { printf("Hello, world!\\n"); return 0; }
Nasz program zapiszemy do pliku np. "hello.c", aby go skompilować piszemy "cl65 -O -t atari hello.c -o hello.xex" i to wszystko, powstał plik HELLO.XEX który możemy już wczytać spod DOS-a. Uruchomienie go bezpośrednio nic nie da, musi być uruchomiony spod DOS-a.
Parametry które przekazaliśmy kompilatorowi cl65 oznaczają (wielkość liter jest rozróżniana):
-O optymalizacja kodu, tworzy szybszy kod maszynowy -t sys docelowy system, domyślnie jest to C64 :P -o plik nazwa pliku wyjściowego
Wywołanie cl65 to najszybszy i najprostszy sposób kompilacji. Skompilowany program domyślnie ładuje się od $2E00, startuje od $2E01, maksymalna długość skompilowanego programu może wynieść $8E20 bajtów. Stos programowy liczy $0800 bajtów pamięci. Strona zerowa używana jest od bajtu $82-$FF.
Domyślne ustawienia dla linkera (plik ATARI.CFG):
MEMORY { ZP: start = $82, size = $7E, type = rw, define = yes; HEADER: start = $0000, size = $6, file = %O; RAM: start = $2E00, size = $8E20, file = %O; # $8E1F: matches upper bound $BC1F } SEGMENTS { EXEHDR: load = HEADER, type = wprot; CODE: load = RAM, type = wprot, define = yes; RODATA: load = RAM, type = wprot; DATA: load = RAM, type = rw; BSS: load = RAM, type = bss, define = yes; ZEROPAGE: load = ZP, type = zp; AUTOSTRT: load = RAM, type = wprot; } FEATURES { CONDES: segment = RODATA, type = constructor, label = __CONSTRUCTOR_TABLE__, count = __CONSTRUCTOR_COUNT__; CONDES: segment = RODATA, type = destructor, label = __DESTRUCTOR_TABLE__, count = __DESTRUCTOR_COUNT__; } SYMBOLS { __STACKSIZE__ = $800; # 2K stack }
Jednak jakiekolwiek zmiany w pliku *.CFG nie zostaną zauważone jeśli będziemy kompilować używając komendy "-t atari", trzeba ją zastąpić przez "-C atari.cfg". Jeśli chcemy mieć też wpływ na typ CPU dla którego będzie generowany kod, lepiej jeśli rozbijemy proces generowania pliku wyjściowego na kompilację, asemblację i linkowanie, wtedy wszystko będzie pod kontrolą.
cc65 -O --cpu 6502 -t atari hello.c -o hello.s ca65 -t atari hello.s -o hello.o ld65 -C atari.cfg -o hello.xex atari.o hello.o atari.lib
Kompilator cc65 tworzy z pliku hello.c plik hello.s, następnie makro assembler ca65 z pliku hello.s tworzy hello.o i ostatecznie linker ld65 z pliku hello.o i przy pomocy biblioteki atari.lib i atari.o oraz pliku konfiguracyjnego atari.cfg tworzy wykonywalny plik Atari hello.xex
Jeśli chcemy aby kod programu był wykonywany na 65816, zmieniamy zapis --cpu 6502 na --cpu 65816. Czyż nie jest to cudownie proste :).
Przykład 2:
#includeint main (void) { char str1[30],str2[30]; int i; puts("wprowadz lancuch 1"),gets(str1); puts("wprowadz lancuch 2"),gets(str2); for (i=0; str1[i]==str2[i]; i++) { } if (str1[i]==0) // standard ansi, ciag znakow zakonczony zerem { puts("oba lancuchy sa identyczne"); return 0; } puts("lancuchy sa rozne"); return 0; }
Progamik pyta się o dwa łańcuchy znaków, potem je porównuje i informuje czy łańcuchy są identyczne, czy też różne. Jest jeszcze biblioteczna funkcja C -> "strcmp" która porówna ciągi, ale przecież jej użycie byłoby zbyt proste :)
Przykład 3:
/* clock example: countdown */ #include#include void wait ( int seconds ) { clock_t endwait; endwait = clock () + seconds * CLK_TCK ; while (clock() < endwait) {} } int main () { int n; printf ("Starting countdown...\\n"); for (n=10; n>0; n--) { printf ("%d\\n",n); wait (1); } printf ("FIRE!!!\\n"); return 0; }
Programik zlicza od 10 do 0, wypisując stosowny komunikat na ekranie. Jest to też przykład wykonania opóźnienie z użyciem "time.h"
Ci co programują w C, na pewno ucieszą się z w/w kompilatora. Można stworzyć np. bibliotekę do odczytu obrazków w różnych formatach (HIP, TIP), czy playerów, dzięki temu w łatwy sposób powstałaby przeglądarka pod Sparte czy zwykły DOS. Może Macgyver napisze jeszcze raz MegaPlayera :)
Można też kazać kompilatorowi aby generował kod dla 65816, dzięki czemu w prosty sposób napiszemy aplikację dla 16-bitowej wersji 6502 :) nie zagłębiając się w poznawanie nowych rozkazów i trybów adresowania CPU.
Nie ma jednak róży bez kolców. Wstawianie wstawek assemblerowych musi odbywać się poprzez pliki z rozszerzeniem *.s, a tak zwiększona liczba plików nie sprzyja ogólnej czytelności projektu. Przydzialana programowi pamięć w zasadzie mieści się w granicy $2000-$BFFF, troche mało jeśli ktoś myśli o większym projekcie. Tego typu ograniczenia skutecznie przychamowały zapał Konopa do napisania dema w C. Problem z pamięcią miał też autor atarowskiej wersji systemu Contiki, który posłużył się specjalnie okrojoną wersją DOS - PicoDos aby tylko wcisnąć więcej kodu poniżej adresu $2000.
p.s. podziękowania dla Konopa, za oświecenie w kwestiach związanych z C :)