Hallo Besucher, der Thread wurde 5,9k mal aufgerufen und enthält 42 Antworten

letzter Beitrag von Claus am

Styleguide für Assembler

  • Mal eine Allgemeine Frage zum Coden in ASM:


    Gibt es irgendwo eine bewerte Styleguide zum Aussehen und Namenskonventionen beim C64-Assembler?
    Irgendeine Richtschnur zum Einrücken und leichter Lesbarkeit. In vielen Codeschnipseln die ich finde differieren alle möglichen Versionen.


    Und mir ist schon klar dass das eine, teils, philosophische Frage ist. Vorteile und Nachteile wären sinnvoll.


    Auch irgendwelche Richtlinien zur Speicherbelegung wären hilfreich. :rolleyes:

  • Richtlinien zur Speicherbelegung

    DIE eine Richtlinie wird es kaum geben, denn die Speicheraufteilung des C64 zeichnet sicht nicht durch optimale Anordnung aus. Zwischen Basic-Rom und Kernal-Rom liegt der Ram-Bereich $c000..$cfff, dann folgt erst der IO-Bereich, und alle Roms lassen sich auf Ram umschalten, aber nur, wenn a) das Programm sie nicht braucht und b) man beim Programmieren Vorsichtsmaßnahmen trifft.
    Folgende Kritierien bei der Speicherbelegung fallen mir spontan dazu ein:
    - Verhält sich das Programm generell systemfreundlich?
    Ja ==> Nur minimaler Gebrauch der Zeropage möglich. Sehr eingeschränkte Speicherbelegung
    - Wird der Kernal benutzt?
    Ja ==> IRQ über $314, Nein ==> IRQ auch über $fffe möglich.
    - Gibt es einen fortlaufenden IRQ, der nicht gesperrt werden darf (z. B. für Hintergrundmusik)?
    Ja ==> Umschalten zwischen Kernal und Ram nur möglich, wenn eine IRQ-Routine über $fffe den Kernal imitiert.
    - Wird für die Graphik eine volle VIC-Bank benötigt (z. B. für viele Sprites)?
    Ja ==> Nur VIC-Bank $4000..$7fff und $c000..$ffff möglich.
    - Gibt es Bitmapgraphik? Eventuell sogar mit Double-Buffering?
    Ja ==> Die Verwendung von $4000..$7fff bzw. $c000..$ffff ist wahrscheinlich.
    Werden (per Raster-IRQ) jedoch nur Teile der Anzeige gedoppelt?
    Ja ==> Auch andere Belegungen möglich.


    Von Nachteil ist, daß die VIC-Bank $4000..$7fff mitten in dem Bereich liegt, wohin normalerweise das Programm geladen wird. Nicht nur deswegen bietet es sich an, den Exomizer zu verwenden, um direkt nach dem Laden die Programm- und Datenbereiche an ihre richtige Stelle zu verschieben bzw. zu entpacken. Dennoch ist diese Zerpflückung des Speicherbereichs bei der Programmentwicklung recht nervig.


    Meine persönliche Empfehlung ist es, während des Programms (meistens einem Spiel) auf das Kernal-Rom zu verzichten, einen eigenen IRQ direkt über $fffe zu verwenden und auf die Speicherkonstallation RAM-RAM-RAM zu schalten, bei vielen Zugriffen auf den IO-Bereich RAM-IO-RAM. Auf jeden Fall sollte man sich das Ram bei $d000..$dfff nicht entgehen lassen. Diese 4kb Speicher sind zu wertvoll.


    Daraus ergibt sich auch die gängige Speicheraufteilung, als VIC-Bank den Bereich $c000..$ffff zu nehmen. Feste Daten wie Sprites können dann in das $d000-Ram geschoben werden, und der Rest der VIC-Bank bleibt frei für Textschirm, Zeichensatz oder Bitmap.

    bewerte Styleguide

    Ist mir nicht bekannt. Da braut jeder schon seit Jahren sein eigenes Süppchen ganz nach Geschmack. Persönlich arbeite ich lieber etwas entzerrter und nicht so an den linken Rand gequetscht. Auf heutigen Monitoren besteht dazu auch kein Anlaß mehr. Da kann man die ganze Breite ausnutzen, um das Programm für die optische Wahrnehmung übersichtlicher zu gestalten. Bei mir hat sich nach vielen Jahren folgender Stil herauskristallisiert:


    - 2 Tabulatoren á 8 Leerzeichen (also insgesamt 16 Leerzeichen) bis zum Mnemonic. Das Kommentar-Semikolon steht auf Position 57, darauf folgt noch ein Tabulator und erst anschließend der Kommentar.
    - Mnenomics werden klein geschrieben, da Großbuchstaben zu laut schreien.
    - Nach einem Komma folgt ein Leerzeichen.
    - Bei Rechenoperationen wird links und rechts vom Operator ein Leerzeichen eingefügt.
    - Konstanten werden groß geschrieben.
    Beispiel:

    Code
    1. <tab> <tab> lda #KONST + 3 ; Kommentar
    2. <tab> <tab> sta adresse, x

    Warum so gesperrt? Nun ja, irgendwann sind die Augen ganz dankbar, wenn sie Dinge leichter auseinanderhalten können.

  • Da braut jeder schon seit Jahren sein eigenes Süppchen ganz nach Geschmack.

    Der verwendete Assembler hat da noch den größten Einfluß, gerade wenn es um so Details wie Formelauswertung für Adressen, bedingte Assemblierung, Makros und Schleifen geht.


    Die Sprache selber gibt das nicht her - Struktur mußt Du da selbst reinbringen. Wichtig ist eine gute Aufteilung des Programms in Unterroutinen und ein konsistenter Stil bei der Auswahl der Labelnamen.


    Gerade, was letzteres angeht, bin ich dabei hängen geblieben, Labels in Unterroutinen durch einen angehängten Unterstrich plus zweistellige Nummer zu versehen. "Generische" Label wie "skip" oder "loop" meide ich wie die Pest. Typischer Code (ohne "Boilerplate" drumherum) sieht bei mir so aus:


    Der Assembler, den ich verwende, läßt auch mehrere Befehle in einer Zeile zu. Das kommt durchaus auch zum Einsatz, wenn die dadurch entstehende Befehlsgruppe ein klares Ziel hat (etwa "Datensammeln und am Ende der Zeile wegspeichern.") - erstreckt sich das dann noch über mehrere Zeilen, sieht so ein Programmteil manchmal einem Kristall nicht unähnlich. :D

  • M. J.


    Genau so hab ich mir eine Antwort vorgestellt. Danke für die Mühe.


    Der Speicher bei $d000 ist doch mit den VIC und SID registern belegt, wie kann ich den für Sprites nutzen?
    Bisher wäre ich davon ausgegangen das der VIC dort blind ist.
    Und vorallem ist das sinnvoll, wenn man sowas wie ein Game schreibt?

  • Mit Ausnahme der Einblendungen des Char-ROMs bei $1000..$1FFF und $9000..$9FFF sieht der VIC-II nur RAM, egal was im Portregister des 6510 steht.


    Heißt, der VIC-II kann mühelos auf das RAM bei $D000..$DFFF "unter" I/O und Char-ROM zugreifen. Tatsächlich sieht er gar nicht die "Kopie" des Char-ROMs bei $D000 - die existiert so nur für die CPU. Die Zuordnung wird so von der PLA realisiert.


    (Zugriff auf das Farb-RAM ist noch eine andere Sache - das wird parallel mit einem eigenen 4-Bit-Datenbus angesprochen und liegt aus Sicht des VIC-II *überall* - teilt sich aber die unteren 10 Bit der Adresse mit dem adressierten Screen-RAM).

  • @Mike


    Ich hätte das jetzt so umformatiert, da ich so die Labels besser erkenne. So rein als totaler Anfänger in 6510 Assembler.


    Momentan benutz ich das C64 Studio, weil ich mich um einiges nicht kümmern muss, bzw. es relativ einfach ist.
    Allerdings crashen dort Macros zur Zeit, was komplexeren Aufgaben sehr im Wege steht.


    EDIT: Die Befehle hätte ich noch klein geschrieben :D

  • Irgendeine Richtschnur zum Einrücken und leichter Lesbarkeit.

    Ganz klar: nein. Es gibt die hart codierte Syntax des jeweiligen Assemblers, und wenn die Dir sagt: Keine Einrückungen, oder genau ein Leerzeichen am Zeilenanfang, wenn es kein Label ist, dann führt da kein Weg dran vorbei. Manchmal gibt es ein sauber kolumniertes Assembler-Listing zum Objektcode dazu. Und Labelnamen sind gern mal auf 6 oder 8 Zeichen Länge begrenzt, auch da ist der Spielraum sehr eng. Obacht übrigens bei Labels, die mit einem Doppelpunkt enden: Manchmal gehört der zur Syntax der Label-Definition, wird also bei der Verwendung des Labels als Operand weggelassen; manchmal ist der aber auch Teil des Label-Namens... und im 8502-BIOS des 128er CP/M gibt es Label der Art kernal$go$anywhere; hier dienen die Dollarzeichen nur der optischen Gleiderung und gehören nicht zum Symbolnamen...


    Natürlich gibt es auch die original-Commodore-Label. Die werden mal emhr, mal weniger verwendet- beid en Kernal-Einsprüngen udn den wichtigsten Pointern sind sie eigentlich übblich, aber lange nicht verpflichtend. Und viele sidn auch eher unbekannt bis nichtssagend. Und als wäre das alles noch nicht undurchsichtig genug, so gibt es neuerdings Überlegungen, die Kernal-Label in 'ausgeschriebene' Lange Labels umzuschreiben. Herzlichen Glückwunsch, wenn Du 30 Jahre lang mit den Kurzformen gearbeitet hast und Dein Lieblingsassembler zur 8-Zeichen-Fraktion gehört.

  • Ganz klar: nein. Es gibt die hart codierte Syntax des jeweiligen Assemblers, und wenn die Dir sagt: Keine Einrückungen, oder genau ein Leerzeichen am Zeilenanfang, wenn es kein Label ist, dann führt da kein Weg dran vorbei.

    Da ist was dran, dass viele Assembler auf dem C64 quasi einen Codestyle hart durchgedrückt haben, auch wenn der Anlass wohl eher technische Nützlichkeit war. Quelltext wurde ja nicht einfach als Textdatei behandelt, sondern vom Editor speichersparend aufbereitet.


    Für Cobol hatten wir uns in der Schule auch streng an Vorgaben auf den Formularen zu halten, allerdings vermute ich, dass das mehr so eine traditionelle Rücksichtnahme auf Lochkarten und Datenerfasser war und seit den Zeiten "besserer" Terminals oder gar PCs nicht mehr nötig war.


    In C64-Assembler am PC hatte ich mir einige Präfixe angewöhnt:
    s_ Unterprgramm
    b_ byte-Variable
    c_ Konstante
    w_ Word
    ba_ Byte-Array
    wa_ Word-Array
    So waren oft genug Fehler schon an komischen Kombinationen aus Befehl und Label zu erkennen, Arrays hatten Zugriffe mit indizierten Befehlen, Konstanten kamen mit # daher und so. Bei komplizierteren Strukturen ging das aber nicht mehr so schön auf.


    Lokale Label hab ich oft ähnlich wie Mike behandelt, die Dinger häufen sich oft dermaßen, dass sprechende Namen schwer werden oder gar die Struktur verschleiern. Würden Menschen wie Fische Eier legen, gäbe man Kindern auch keine Namen. 1 oder 2 Buchstaben + Zahl, z.B. li, re, ob un liegen mir bei eine Joystick-Abfrage mehr als eine lange Beschreibung die vielleicht noch bis in die Befehlsspalte ragt.
    Mit Namen wie "loopX" oder "CountBalls" für wichtigere Sprungziele hab ich aber keine Probleme. Wenn dann mal ein Unterprogramm doch über mehrere Seiten auswuchert, dann sind etwas sprechendere Namen für Schleifen oder andere Hochsprachen-Konstrukte doch ganz gut.

  • DEN styleguide kann es, wie ja schon ausgeführt wurde, nicht geben. Ich persönlich entwickle am liebsten am PC mit ca65 und steuere den Build mit GNU make, speziell dafür einfach mal einige Tips:

    • Unbedingt den Code in Module aufteilen, die möglichst wenige Abhängigkeiten voneinander haben. "Ringabhängigkeiten" wenn irgend möglich vermeiden. Hier kann man sich die Fähigkeiten eines gescheiten Linkers (zum Paket cc65 gehört der ld65) zunutze machen, man definiert sich Segmente für Code, Daten, BSS und Zeropage. Bei meinem aktuellen Projekt sieht die Linker-Konfiguration z.B. so aus:

      damit kann man in jedem Modul z.B. einen Abschnitt .zeropage haben in dem man Zeropage-Variablen definiert, der Linker sorgt schon dafür, dass sich nichts überschneidet. Auch die Anordnung der Segmente ist so vorgegeben wenn die Segmente im gleichen Speicherbereich landen, so ist also sichergestellt, dass aller Code der in .segment "MAIN" liegt vor allem code kommt, der in .code liegt.

    • Bei einer größeren Anzahl von Modulen sind Include-Files, die deren Schnittstelle definieren, sehr hilfreich. Für den Sound-Player den ich gerade bastle habe ich z.B. sound.inc mit diesem Inhalt:
      Code
      1. .import snd_init
      2. .import snd_out
      3. .import snd_settune
      4. .import snd_step

      In jedem Modul, das Sound-Routinen braucht, steht dann einfach nur .include "sound.inc" und wenn sich das Interface ändert muss erst mal nur ein File angefasst werden.

    • Eine einheitliche Namensgebung ist immer sehr hilfreich. Persönlich statte ich gerne alle Routinen eines Moduls mit einem gemeinsamen Prefix aus, wie oben z.B. snd_.
    • Brauchbare Assembler können Konstanten berechnen bei der Assemblierung, das sollte man unbedingt nutzen, da es den Code wesentlich lesbarer macht. Wenn man einfach die fertig berechnete Konstante (magic number!) hinschreibt, muss man jedesmal wieder darüber nachdenken, wie man darauf kam. Als Beispiel zeige ich mal das File, in dem ich die Konfiguration des VIC ablege:
  • @zrs1 nur wuerde ich Konstanten zur besseren Unterscheidung von Labels dann vollstaendig in Grossbuchstaben schreiben.

    Kann man machen, mache ich persönlich jetzt teilweise: Bei mir sind Konstanten, die wirklich "fest" sind, all-caps (wie z.B. die symbolischen Namen für die Bewegungsrichtungen auf dem Board, die abgespielten Noten, usw -- in anderen Projekten auch die Namen für ROM-Routinen), während konfigurierbare Konstanten klein sind. Wichtig ist vor allem, dass man auch hier insgesamt konsistent bleibt :)

  • Das auf jeden Fall. Zumal man in vielen Faellen sowieso der einzige ist, der den eigenen Code jemals lesen wird. Da ist es einfach nur wichtig, dass man sich selbst drin auskennt :)

  • Leicht OT:
    Wann in den vergangenen Jahrzehnten ist eigentlich der Trend aufgekommen, Variablen, Typen usw. gleichen Namens, aber unterschiedlicher Groß/Kleinschreibung zu erlauben? In Java fand ich es lästig, in PHP war es das Grauen, und in C# geht mir das auch schon wieder auf den Sack. Waren es BMW und FIAT oder doch bMW und fiAt? Bis ich die richtige Schreibweise gefunden habe, hab ich schon wieder vergessen, wozu ich das eigentlich brauchte. Imho unnötige ABlenkung.

  • Das gibt es schon seit Ewigkeiten, auch in C ist das so, und unter Unix-basierten Betriebssystemen sind auch "readme.txt" und "Readme.txt" und "README.txt" und "README.TXT" 4 verschiedene Dateien.


    Gerade einem solchen Wildwuchs, wie Du ihn jedoch herbeifuerchtest, schiebt dies jedoch einen Riegel vor: Denn Du kannst nun eben NICHT einfach schreiben, wie es Dir passt, sondern Du sollst gefaelligst den korrekten Namen verwenden, naemlich so, wie die Variable definiert wurde.


    Wenn man sich nun an einen (eigenen oder fremden) Styleguide haelt, dann ist das auch ueberhaupt kein Problem. Wenn Du beispielsweise Variablen immer klein schreibst, Konstanten immer GROSS, und Klassen- oder Typnamen immer mit GrossemAnfangsbuchstaben, dann wirst Du sehen, dass das ueberhaupt keine Herausforderung darstellt. Im Gegenteil, durch das Erzwingen der korrekten Schreibweise kannst Du nun sogar immer an einem Namen direkt ablesen, ob es sich dabei um eine variable, eine KONSTANTE oder einen TypNamen handelt.

  • Wann in den vergangenen Jahrzehnten [..]

    Gilt das nicht für alle C-Sprachen?



    Gerade einem solchen Wildwuchs, wie Du ihn jedoch herbeifuerchtest, schiebt dies jedoch einen Riegel vor: Denn Du kannst nun eben NICHT einfach schreiben, wie es Dir passt, sondern Du sollst gefaelligst den korrekten Namen verwenden, naemlich so, wie die Variable definiert wurde.

    :abgelehnt Der Grund, warum C und Linux keine Gemischtschreibweise erlauben, dürfte wohl weniger an einem durchdachten Style liegen, als viel mehr daran, daß damals in dern 1970ern die Compiler möglichst kurz gehalten wurden und keine Umwandlung von Klein- und Großbuchstaben erlaubten bzw. die damaligen Systeme nicht einmal Groß- und Kleinbuchstaben kannten. Für mich persönlich gilt die Grundregel: Nicht Menschen sollen sich Maschinen anpassen, sondern die Maschinen sollen sich gefälligst den Menschen anpassen, und für einen Menschen ist ein "e" ein "E". Das zählt. Inbesondere wenn man sich vor Augen hält, daß auch Nicht-Nerds Computer benutzen (sollen). Für normale Menschen außerhalb der Softwareentwicklung sind nämlich Dateinamen wie "Oma.bmp" und "oma.bmp" ein und dasselbe. Wer mittels Groß- und Kleinschreibung die verschiedenen Arten von Bezeichnern identifizieren will, soll das gerne tun (mache ich auch zum Teil), aber als strenge Vorschrift nicht nur in einer Programmiersprache, sondern auch auf Dateisystemebene ist die willkürliche Unterscheidung zwischen Groß und Klein nur ein nerdiger Ballast aus den 70er Jahren des letzten Jahrhunderts.


    Auch eine OT-Frage:
    Was mich bezüglich des oft verwendeten Linkers mal interessieren würde: Wozu braucht man eigentlich den Linker für C64-Programme? Kann man nicht einfach den ganzen Sourcecode auf einmal assemblieren? Das ist doch auf heutigen Rechnern kein Problem mehr. Ich kann ja verstehen, daß bei großen Projekten für den PC, bei denen ein Assembliervorgang aufgrund der ganzen automatisierten Sprungoptimierungen eine halbe Minute dauern kann, eine Aufteilung in verschiedene Objektdateien sinnvoll ist. Aber auf dem C64? :gruebel


    BTT:
    Meine Empfehlung zu Macros: Besser nicht benutzen. Früher[tm] hatte ich auf dem Amiga einen Macroassembler, aber habe das Feature so gut wie nie genutzt und folglich später auf dem PC gar nicht mehr im Assembler implementiert. Macros habe ich seitdem nicht mehr verwendet und auch nie vermißt. Mein Eindruck bisher war viel mehr, daß
    - fremder Assemblercode mit Makros schlecht zu lesen ist,
    - bei einer optimierten Registerbelegung (insbesondere bei Prozessoren mit wenigen Registern wie dem 6502) feste Makros eher stören,
    - Programmcode, der erkennbar mit der Unterstüzung von Macros geschrieben wurde, weniger gut optimiert war (hinsichtlich Codelänge als auch Geschwindigkeit) wie handgeschriebener Code.

  • Für mich persönlich gilt die Grundregel: Nicht Menschen sollen sich Maschinen anpassen, sondern die Maschinen sollen sich gefälligst den Menschen anpassen

    Geht für mich hier am Thema vorbei, denn unabhängig davon was nun der Grund für "case sensitive" Code war, es zwingt einen zu sauberem Stil, und das ist meiner Meinung nach etwas gutes (und kommt nicht der Maschine, sondern einem selbst zugute, wenn das Projekt wächst).

    Was mich bezüglich des oft verwendeten Linkers mal interessieren würde: Wozu braucht man eigentlich den Linker für C64-Programme? Kann man nicht einfach den ganzen Sourcecode auf einmal assemblieren? Das ist doch auf heutigen Rechnern kein Problem mehr. Ich kann ja verstehen, daß bei großen Projekten für den PC, bei denen ein Assembliervorgang aufgrund der ganzen automatisierten Sprungoptimierungen eine halbe Minute dauern kann, eine Aufteilung in verschiedene Objektdateien sinnvoll ist. Aber auf dem C64?

    Um die Performance beim kompilieren geht es auch bei PC Software eher selten, da muss sie schon recht groß sein. Gründe für den Linker (lose Aufzählung):

    • Modularisierung (logische Einheiten)
    • daraus folgt bessere Übersicht über das Projekt,
    • Wiederverwendbarkeit einmal entwickelter Komponenten,
    • Änderung der "memory map" mit minimaler Codeänderung (hauptsächlich in der Linker config),
    • bessere Testbarkeit der Komponenten
    • ...
  • Ich habe mich jetzt eigentlich nur auf Programmiersprachen bezogen, und das mit dem Dateisystem eher beilaeufig erwaehnt. Da verstehe ich Deine Bedenken und wuerde auch nicht uneingeschraenkt sagen, dass es unter Unix zwingend besser ist (was mich aber unter Windows total stoert ist das Ausblenden der Dateinamen-Erweiterungen - das ist der groesste Krampf den ich jemals erlebt habe).


    Aber in Programmiersprachen finde ich es schon sehr gut, dass es da eine Unterscheidung gibt, denn Programmcode ist einfach etwas striktes, da sollte es keinen Wildwuchs geben. Als naechstes verlangt dann einer, dass Buchstabendreher vom Compiler erkannt werden. Ich finde da hat es durchaus seinen Sinn, dass nur korrekt geschriebene Namen auch wirklich vom Compiler erkannt werden. Ausserdem kann ich mir sehr wohl vorstellen, eine Konstante SPRITE1X zu haben und gleichzeitig eine Variable namens sprite1x. Das faende ich jetzt nicht wirklich ungewoehnlich. Zumal es bei einer konsequenten Einhaltung des Styleguides dann ja auch wie gesagt auf den ersten Blick ersichtlich ist, dass es sich einmal um eine Konstante und einmal um eine Variable handelt.


    EDIT: Oder eine Klasse "Player" und dazu eine Variable namens "player", z.B. bei Uebergabewerten von Funktionen (z.B. void setPlayerPos(Player* player)). Das finde ich jetzt wirklich nichts ungewoehnliches.