Beiträge von M. J. im Thema „Styleguide für Assembler“

    Und da sieht man dann auch, daß das CLC *gefälligst* vor die gesamte Gruppe gehört und nicht direkt vor das erste ADC

    Danke für den Hinweis. Das gehört sicherlich auch in einen Styleguide. Wenn ich noch ergänzen darf: Sollte man sich das vorhergehende CLC/SEC sparen können, weil das Carry bereits den gewünschten Wert hat, sollte man dies kommentieren, damit a) man später noch nachvollziehen kann, wie die Berechnung funktioniert und b) bei einer eventuellen späteren Änderung des Programms kein Fehler daraus wird. Also sowas wie

    Code
    ;		clc			bereits gelöscht
    		lda	var
    		adc	#<wert>
    		sta	var

    Das gilt auch für den Fall, daß vor einer Addition das Carry gesetzt ist:

    Code
    ;		sec
    		lda	var
    		adc	#<wert - 1>	; + 1!
    		sta	var


    Kleine Frage in die Runde:
    Gibt es irgendwo eine Liste von "Tips und Tricks" wie die oben genannte Verkürzung einer Addition, die man gerade Einsteigern in Assembler nahelegen könnte?

    Irgendwie habe ich den Eindruck, wir reden aneinander vorbei.

    nur der Linker muss wissen, wohin der Code letztendlich soll.

    Eben. Einer muß es wissen. Dabei ist es egal, wo diese Angabe steht: in der Assemblerdatei oder der Linkerdatei. Nur bei einer Linkerdatei muß man eben noch eine zusätzliche Datei erzeugen. Für meinen Geschmack nicht einfacher, sondern umständlicher.

    Ich will es aber in der Regel nicht für jedes einzelne Modul wissen!

    Solltest Du aber. Denn nur dadurch weißt Du am Ende, welchen Platz Dein Programm im Speicher belegt, kannst danach den Speicher besser ausnutzen und beim Debuggen im Monitor Dein Programm verfolgen. Natürlich heißt das nicht, daß Du für jedes Modul getrennt angeben mußt, wo es hinkommen soll, denn die Module werden ja einfach aneinandergehängt. und da reicht zunächst (sofern keine große Speicherfragmentierung vorliegt) eine einmalige Angabe der Ladeadresse zu Beginn des Codes.

    Klar könnte ich hingehen und allen Code per .include schon vor dem Assemblieren zusammenbauen

    Ganz genau. Darum geht es. Und was die Segmente anbelangt, so sind diese keine primäre Eigenschaft des Linkers, sondern des Assemblers. Natürlich wird man auch ohne Linker ein Programm in einzelne Segmente zerlegen, z. B. für die Zeropage oder den nicht-initialisierten Datenbereich. Ein Linker wird dafür nicht benötigt.

    es macht auch alles global, also müssen z.B. Labels über verschiedene Module eindeutig sein

    Da es hier ja um Style geht: In einem Assemblerprogramm sollten alle Label stets eindeutig sein. Daher ist es üblich, globale Labels nur für Module (Bibliotheken/Klassen...) zu verwenden und für Unterroutinen, Variablen etc lokale Labels. Für Sprungmarken innerhalb von Unterroutinen bieten sich je nach Assembler entweder Mikes Methode (".label_02"), anonyme Labels (-, +) oder drittlevel-Bezeichner ("?01", "!01" usw.) an. Sollte jemals ein Label mit einem anderen kollidieren, hat man einen Fehler im Programm.

    ich kann leicht aus Versehen in den privaten Daten eines anderen Moduls herumpfuschen.

    Aus Versehen sicherlich nicht. Kann ich mir bei Dir nicht ernsthaft vorstellen. Dafür hast Du zuviel Programmiererfahrung. Das Herumpfuschen in fremden Modulen ist bekanntlich mehr als schlechter Stil. Assembler ist eine Sprache für Leute, die wissen, was sie tun, und dazu gehört auch ausreichende Selbstdisziplin, ansonsten bekommt man früher oder später Probleme, so oder so.

    Wenn ich das jetzt richtig verstanden habe, ist das einzige Argument, das für den Einsatz eines Linkers spricht, die Kapselung von Bezeichnern, die man als Assemblerprogrammierer aber ohnehin vornehmen sollte auch ganz ohne Zwang. Als nachteilig für den Einsatz eines Linkers sehe ich hingegen das Fehlen eines kompletten Assemblerlistings, d. h. vollständiger Code mit Adreßangabe sowie tabellarische Auflistung aller Variablen und Adressen, damit man beim Debuggen sich schnell orientieren und Programm und Werte vergleichen kann. Für mich persönlich kann ich daher keinen Vorteil in der Verwendung eines Linkers erkennen, eher Nachteile: zusätzliche Konfigurationsdateien, mehr Aufwand beim Buildprozeß und kein vollständiges Listing zur Kontrolle.

    Dann sind wir wieder bei den "netten" Pseudo-Opcodes von anno dazumal

    Hmm... Das habe ich irgendwie nicht verstanden. Der 6502 kennt bis auf die Branchbefehle keine Unterstützung für relokatiblen Code. An irgendeiner Stelle muß man dem Assembler oder Linker sagen, für welche Adresse das Programm assembliert/gelinkt werden soll. Hinzukommt - Stichwort Speicherbelegung -, daß man auf dem C64 als Programmierer durchaus wissen sollte, von wo bis wo der Code im Speicher steht. Auf dem PC arbeite ich logischerweise auch nicht mit ORG, aber für einen notgedrungen vollinformierten C64-Programmierer sehe ich gar keine Möglichkeit, das ORG wegzulassen.

    ; **** ADD num 2 num in adresse
    !macro add_word_adr_num adr,num{

    Solch eine 16-Bit-Addition kam bislang in meinen Programmen so gut wie nie vor. Da war es eher so, daß ein Teil der Adresse in einem anderen Register lag oder das Ergebnis in ein Register geladen wurde usw. Nur mal als Beispiel:

    Code
    clc
    		lda	var
    		adc	#
    		sta	var
    		bcc	.skip
    		inc	var + 1
    .skip:

    Das ist eine übliche Variante, um bei Konstanten zwischen 0..255 die Addition zu verkürzen und zu beschleunigen. Ist von einer vorhergehenden Operation das Carry-Bit noch gelöscht, kann auch auf den Befehl CLC verzichtet werden. Und damit sind wir bei dem, was ich meine: Macros erzeugen häufig längeren und langsameren Code, besonders auf dem 6502.
    Was die Verwendung von Macros beim Setzen von VIC-Registern anbelangt, so halte ich die Befehlsfolge wie

    Code
    lda	#$1b
    		sta	$d011	; oder ein beliebiger Name für das Register

    für immer noch am schnellsten und übersichtlichsten.

    Code
    and	#VIC_ECM_Off
    		and	#VIC_BMM_Off

    Und hier reicht ein "and" aus. ;)

    Gründe für den Linker [...]

    Hmm... Da habe ich mich wahrscheinlich nicht richtig ausgedrückt. Die Modularisierung eines Programms wird ja nicht durch die Verwendung des Linkers erzielt, sondern durch die Organisation des Programms an sich. Man kann ja durchaus ein Programm modular gestalten mit sauber getrennten Klassen, Bibliotheken usw. und trotzdem das ganze Programm in einem Rutsch assemblieren ohne dabei auf einen Linker zurückgreifen zu müssen. Wie gesagt: Da es von der Geschwindigkeit eh nichts ausmacht, ob man das ganze Programm oder nur einen Teil davon assembliert, frage ich mich: wozu dann noch den Linker?

    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.

    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
    <tab>	<tab>	lda	#KONST + 3			;	Kommentar
    <tab>	<tab>	sta	adresse, x

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