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

letzter Beitrag von ZeHa am

Konstanten, Variablen, Lables & Symbole, was für ein Durcheinander! :-O

  • Verzeihung, ich hätte vielleicht die Bedeutung von "equ" dabei schreiben sollen. "EQU" (kurz für "equal") ist bei vielen Assemblern nichts anderes als "=", dient also nur dazu, einem Symbol einen bestimmten Wert zuzuweisen.

    Code
    1. foo: equ $1234
    2. foo: = $1223 ; meint dasselbe

    Ob der Assembler jetzt "equ" verwendet oder "=" ist eher eine Sache der Tradition. Manche verstehen auch beides. Ich persönlich verwende eher "equ", aber nur weil ich es so gewöhnt bin von früher.


    Ähnliches gilt auch für den Doppelpunkt. Persönlich füge ich gerne einen Doppelpunkt an, damit ich später besser danach suchen kann. Eine Suche nach "foo:" bringt mich sofort an die Stelle, wo foo deklariert wurde, wohingegen eine Suche nach "foo" mich zusätzlich zu all den Stellen bringt, wo foo verwendet wurde. Bei vielen Assemblern ist der Doppelpunkt Geschmacksache. Er wird aber dann entscheidend, wenn der Assembler eine freiere Syntax zuläßt, z. B. wenn Befehle ganz am linken Rand stehen oder Labels nach rechts eingerückt werden können. Dann hilft beim Parsen (wie bei C) nur eine Unterscheidung anhand des ':', ob es sich um ein Label handelt oder einen falsch geschriebenen Befehl. Wichtig: Nicht immer werden alle Label auch später im Programm verwendet.

    Code
    1. label ; dies ist ein eingerückter Label.
    2. ; Der Assembler erkennt, daß es kein Befehl ist,
    3. ; und merkt sich "label" als Symbol.
    4. ta ; dies ist ein falsch geschriebener Befehl,
    5. ; der eigentlich "tax" heißen sollte.
    6. ; Aber der Assembler denkt, es sei ein Label. Ups.

    Darf ich fragen für welchen Assembler und welche Platform
    diese Syntax ist?

    Der Assembler ist Marke Eigenbau und läuft unter Windows. Die Zielplattform kann alles sein, von C64 über AppleII, CPC oder Amiga... Assembler gibt es wie Sand am Meer, und oft unterscheiden sie sich in der Syntax ihrer Pseudoopcodes ('equ' vs. '=', 'org' vs. '*=' usw.). Einem Anfänger rate ich daher gerne zur Verwendung des ACME-Assemblers bzw. C64 Studios, weil dieser nicht nur recht mächtig, sondern auch dessen Syntax unter C64-Programmierern weit verbreitet ist und damit Quelltexte (z. B. Beispiele aus dem Internet) leicht ausgetauscht werden können.

  • Ja, siehe hier. Ich assembliere nicht mehr ohne dieses Feature (Au weia, es ist jetzt schon seit über zwei Jahren in ACME enthalten, und ich hab immer noch keine offiziellen Docs dafür geschrieben).

    Deshalb hatte ich schon mehrfach eine gute und ausfühliche und vor allem aktuelle Dokumentation für Anfänger "gefordert", damit sich auch Anfänger wie ich dort besser hineinarbeiten können...

  • Aha! "equ" bedeutet equals, "=".
    Danke!


    Wusste ich gar nicht dass manche Assembler das so schreiben.
    Und was genau ist der "&" und der ">>" operator schon wieder?
    Das eine ist glaub Bitshift, aber in welche Richtung..?
    Ich meine wegen dem hier:

    • lda #foo & $ff
    • sta $2
    • lda #foo >> 8


    Danke für die Erklärung mit dem Doppelpunkt!
    Das macht auch für mich verd. viel Sinn (kenne das wenn man
    nach etwas sucht das nach hinten / rechts offen ist)!
    Kann ich das in ACME auch so schreiben?

  • Und was genau ist der "&" und der ">>" operator schon wieder?
    Das eine ist glaub Bitshift, aber in welche Richtung..?

    & verhält sich wie der von C bekannte &-Operator, d. h. bitweises UND.
    >> ist das bitweise verschieben nach rechts.
    Ich benutze diese Operatoren standardmäßig, da ich auch oft mit Adressen >16 Bit zu tun habe (z. B. bei der Diskettenverwaltung), bei denen noch ein >> 16 hinzukommt.
    Für Adressen im 16 Bit-Bereich bieten viele Assembler (auch der ACME) folgende verkürzte Schreibweise an:

    In dem Link, den Dir GI-Joe genannt hat, findest Du auf Sourceforge in der Datei "QuickRef.txt" auch eine Auflistung aller in ACME möglichen Operatoren. (Es sind eine ganze Menge.)

  • Was vielleicht zum Verständnis noch nützlich sein könnte:
    -Assembler ist keine Hochsprache, mit Assembler kann man Hochsprachen bauen. Assembler hat keine Datentypen wie Int oder Float, mit Assembler denkt man sich die selber aus und programmiert entsprechend. Oder allgemein baut man einen Haufen von Bytes, die an der passenden Stelle im passenden Computer einen Sinn ergeben.
    -Heute sind wir 30 Jahre weiter und erwarten von einem Assembler einiges an Luxus, den wir aus neueren Sprachen gewöhnt sind. Ergebnis ist aber immer noch ein Haufen von Bytes.
    -Imho erklärt auch die Evolution der Assembler, was Label eigentlich sind. Selbst wenn diese Evolution vielleicht gar nicht stimmt:


    Generation 0: Hexeditor
    Mit einem Handbuch, dass die Befehle mit ihren Bytecodes beschreibt, kann man schon programmieren. Nicht mal optisch gibt es einen Unterschied zwischen Befehlen und Daten, geschweige denn, dass man "Konstanten" und "Variablen" darin erkennen könnte. Wenn man eine Variable braucht muss man sich deren Aufbau selber ausdenken, deren Speicherplatz selber festlegen und verwalten. Und man muss sehr aufpassen, dass man keine Befehle vergisst oder andere Fehler einbaut. Nachträglich was einfügen wird schwer, da sich dadurch viele Sprungziele und Speicherstellen ändern.


    Gen 1: Maschinensprache-Monitor.
    Es ist schon eine erhebliche Erleichterung, wenn es für die Befehle lesbare Mnemonics gibt, die in ein paar Buchstaben ausdrücken, was ein Befehl macht. Und besondere Schreibweisen für die verschiedenen Adressierungsarten helfen auch. Und je Zeile eine variable Anzahl von Bytes, 1 Zeile je Befehl. Und weiter die Möglichkeit, einfach eine Reihe von Bytes einzutippen, um Daten zu haben. "Variablen" könnte man als Teil von solchen Daten sehen, dann stecken die mit einem Initialwert mit im fertigen Bytehaufen und belegen Platz auf Diskette. Meist wird man aber "Variablen" wie vorher benutzen: Speicherstellen irgendwo im Speicher. Und Konstanten werden als die Zahl ausgeschrieben, die sie sind, und zusammen mit den paar Befehlen verwendet, die ein # in der Adressierungsart haben.
    Nachträglich Einfügen bleibt dennoch sehr schwer.


    Gen 2: Assembler ohne Label
    Keine Ahnung, ob es das je gegeben hat, denkbar wäre ein Assembler mit Zeilennummern, in denen die ganzen Eingaben wie im Monitor gespeichert werden. Das macht es nötig, statt Speicheradressen Zeilennummern einzugeben und es dem Assembler zu überlassen, die beim Assemblieren in richtige Speicherstellen umzurechnen. "Variablen" wie gehabt: Falls man sie als Teil der Daten sieht, dann müsste man sie jetzt über Zeilennummer ansprechen, aber man kann auch weiterhin einfach irgendwelche Speicherstellen verwenden.


    Gen 3: Assembler mit Labeln
    Zeilennummern mit Namen zu Kennzeichnen (=labeln) ist naheliegend. Und als Bonus kommt noch hinzu, dass man auch die Adressen, die man als "Variablen" außerhalb der Daten verwenden will, mit Namen versehen kann. Und Konstanten gleich genauso, denn wie in den alten Generationen: Zusammen mit # ist der Wert als solcher gemeint, sonst eine Adresse.
    Aus Sicht des Assemblers sind solche Label Variablen, die (einmal) geschrieben und mehrmals gelesen werden: Irgendwie muss er sich ja technisch zu einem Namen merken, welche Zahl sich dafür beim Assemblieren ergeben hat.
    Aus Sicht des fertigen Programms können die auch gar nix sein, falls ich eine Zeile mit Label versehen habe, aber das nie benutze.


    Gen 4: Assembler mit allerlei Kommandos zum Generieren.
    Bis hierher waren Assembler vor allem zur besseren Eingabe eines Programms gut, einfach von Vorne nach Hinten alle Befehle eingetippt. Und genauso stumpf gingen die Assembler die Zeilen beim Assemblieren durch. Nächste Stufe sind Möglichkeiten, diesen festen Assemblier-Ablauf zu ändern, und damit verschwimmt dann die Bedeutung von Labeln noch mehr. FOR..NEXT zählt eine Variable durch, und der Assembler wiederholt alle Zeilen dazwischen. "Variable" aus Sicht des Assemblers, aus Sicht des Programms ein "Label" wie gehabt: Die haben den Sinn, den die Programmbefehle diesem Label geben. Und mit Makros ist es genauso.

  • Hallo alle!


    Vielen Dank auch hier nochmal für die viele Hilfe! :-)


    Das hier schon durchgelesen ??und das hier ??


    Das erste kannte ich. Hab ich natürlich nicht im "Roman-Modus" durchgelsen,
    aber immer wieder mal benutzt zum etwas Nachschauen.
    Aber "&" und "<<" fand ich darin eben nicht.
    Ich hätt's eigentlich noch kennen müssen, nur war ich mir nicht mehr
    sicher und hab mich geärgert dass ich nicht weiss wo nachschauen...
    Das zweite, "QuickRef" ist genau das was ich gesucht hatte! BINGO!
    :):thumbup:
    Da stehen all die Dinge die ich noch suchte. Hab das File irgendwie
    übersehen, obwohl ich dachte ich hätte alle Doku Files angesehen..!
    DANKE! :):)



    & verhält sich wie der von C bekannte &-Operator, d. h. bitweises UND.>> ist das bitweise verschieben nach rechts.
    Ich benutze diese Operatoren standardmäßig, da ich auch oft mit Adressen >16 Bit zu tun habe (z. B. bei der Diskettenverwaltung), bei denen noch ein >> 16 hinzukommt.
    Für Adressen im 16 Bit-Bereich bieten viele Assembler (auch der ACME) folgende verkürzte Schreibweise an:

    In dem Link, den Dir GI-Joe genannt hat, findest Du auf Sourceforge in der Datei "QuickRef.txt" auch eine Auflistung aller in ACME möglichen Operatoren. (Es sind eine ganze Menge.)


    Super! Vielen Dank, geanu das wollte ich wissen!
    Jetzt ist alles klar! :)


    Was vielleicht zum Verständnis noch nützlich sein könnte:
    -Assembler ist keine Hochsprache, mit Assembler kann man Hochsprachen bauen. Assembler hat keine Datentypen wie Int oder Float, mit Assembler denkt man sich die selber aus und programmiert entsprechend.

    Ja danke! Das war mir grundsätzlich schon klar! :-)
    Aber eben vieleicht nicht bis die letzte Konsequenz bewusst... ;-)
    Bzw. musst ich mich da erst wieder "reindenken"... :anonym


    Danke für all die Hilfe! :-)


    LG,
    Markie

  • Ich baue gerade an einem neuartigen Assembler, der einem nicht nur viel Arbeit abnimmt sondern auch so einiges erst gar nicht erlaubt. Alles (incl, Variablen, Labels oder auch Zahlen) hat einen Typ und es wird gecheckt ob der Typ dort zulaessig ist bzw. entsprechend reagiert.

    C
    1. lda 0xd020 // konstante 32 (aka 0x20, der rest wird wie immer ignoriert)
    2. lda vic.background_color // register des vic
    3. ldx sizeof(vic)-1 // größe des vic minus 1
    4. here:
    5. lda here // Fehler: kann keine labels laden (oder speichern)
    6. static byte temp // reserviert ein Byte für die Variable temp (nicht exakt hier sondern in einem anderen, einstellbaren Speicherbereich)
    7. lda temp

    Noch kommt aus dem Assembler kein Byte raus, und es wird auch noch dauen! Es wird Möglichkeiten geben auch selbstmodifizierenden code zu erzeugen (für den Teil muss dann der Optimierer abgeschaltet werden) Zahlen in Adressen umzuwandeln und noch mehr. Der Assembler selber kann keine Makros oder ähnliches aber da er komplett in eine andere Sprache eingebettet ist kann diese leicht dazu verwendet werden.
    Auf der DoReCo kann ich Interessierten etwas zeigen. Ich werde einen entsprechenden Thread aufmachen wenn es mehr zu zeigen gibt. Der Assembler wird als Open-Source Veröffentlicht werden wenn fertig.

  • Man muss sich dann auch mit der Zeit einen Stil angewoehnen/erarbeiten, z.B. bei meinem ersten Assembler-Projekt (das wovon Du den Source-Code hast - Elektrosmog (gibts uebrigens auch in der csdb.dk)) habe ich alle "globalen" Variablen (der Assembler macht hierbei natuerlich keinen Unterschied aber ich spreche nun aus Programmierer-Sicht) an einer zentralen Stelle gehabt, und lokal habe ich stets Zero-Page-Adressen verwendet. Mit der Zeit wurde mir letzteres aber etwas zu unuebersichtlich, sodass ich z.B. bei Shotgun lokale Variablen meist oberhalb des Labels der Funktion bzw des Programmteils direkt definiert habe, wo sie verwendet werden. Das heisst, die Variable "sitzt" dann auch genau an dieser Adresse, sprich kurz vor dem Programmteil. Da dort aber nie jemand hinspringt, ist das natuerlich voellig gefahrlos.


    Das einzige was evtl dagegen spricht, das so zu tun, ist eben, dass diese Variablen in 16-Bit-Adressen gewandelt werden, was dann beim Zugriff soweit ich weiss langsamer ist als Zero-Page-Variablen. Aber darauf muss man wahrscheinlich nur achten, wenn man wirklich performance-kritische Dinge programmiert. Solang Dein Spiel also nicht das naechste "Sam's Journey" werden soll, ist es also vielleicht nicht unbedingt notwendig, alle lokalen Variablen in die Zero-Page zu legen :D


    Hier mal ein kurzes Beispiel wie ich das meine:


    Durch das Assemblieren bekommt update_player_position irgendeine Adresse zugewiesen, und die beiden Variablen liegen dann eben 2 bzw 4 Byte davor. Aber da dieser Speicherbereich wie gesagt niemals als Code ausgefuehrt wird (daher "Niemandsland"), koennen dort voellig gefahrlos Variablen liegen. Aber es ist eben eigentlich keine Variablendeklaration in dem Sinne, sondern es sind 2 Labels die jeweils 2 leere Bytes kennzeichnen. Diese werden aber reserviert und koennen dann vom Code als Variablen/Werte verwendet werden.

  • C
    1. lda 0xd020 // konstante 32 (aka 0x20, der rest wird wie immer ignoriert)

    Den Kommentar verstehe ich nicht. Wecher Rest wird ignoriert, warum wird überhaupt etwas ignoriert, und was hat 0xd020 mit 0x20 zu tun?

    lokal habe ich stets Zero-Page-Adressen verwendet. Mit der Zeit wurde mir letzteres aber etwas zu unuebersichtlich, sodass ich z.B. bei Shotgun lokale Variablen meist oberhalb des Labels der Funktion bzw des Programmteils direkt definiert habe, wo sie verwendet werden. Das heisst, die Variable "sitzt" dann auch genau an dieser Adresse, sprich kurz vor dem Programmteil. Da dort aber nie jemand hinspringt, ist das natuerlich voellig gefahrlos.


    Das einzige was evtl dagegen spricht, das so zu tun, ist eben, dass diese Variablen in 16-Bit-Adressen gewandelt werden, was dann beim Zugriff soweit ich weiss langsamer ist als Zero-Page-Variablen. Aber darauf muss man wahrscheinlich nur achten, wenn man wirklich performance-kritische Dinge programmiert.

    Das kann ich so nur unterstreichen. Gerade Assembler-Anfänger können am wenigsten beurteilen, wann sich Zeropage-Adressierung lohnt und wann nicht - oder wieviel Ersparnis sie in einem konkreten Fall überhaupt bringt. Wenn man $fb bis $fe für Zeiger benutzt und ansonsten alle Variablen direkt im oder hinter dem Programm ablegt, reicht das in 99% aller Fälle völlig aus.

  • Wenn man $fb bis $fe für Zeiger benutzt und ansonsten alle Variablen direkt im oder hinter dem Programm ablegt, reicht das in 99% aller Fälle völlig aus.

    $f7-fa gehen i. d. R. ja auch noch problemlos.


    $02 nehme ich gern, um mal kurz was zwischenzuspeichern oder als Flag für irgendwas. Ist zudem ja auch A/X/Y-unabhängig. PHA/PLA meide ich wie die Pest ^^ .


    Daneben kann man ja auch (wie ich kürzlich erst festgestellt hatte) $03-$06 ebenfalls 'missbrauchen'. Sind so insgesamt 13 Bytes, die man für Kopierschleifen, Flags, Zwischenspeicher nutzen kann. Es kann ja mal vorkommen, dass man parallel zwei Kopierschleifen am Laufen hat und somit acht ZP-Adressen braucht. Wer damit aber auch nicht auskommt...


    Alle 'richtigen' Variablen bekommen natürlichen einen festen Platz irgendwo. Die werden ja meist auch nicht so oft angesprochen, dass man das merken würde.

  • Den Kommentar verstehe ich nicht. Wecher Rest wird ignoriert, warum wird überhaupt etwas ignoriert, und was hat 0xd020 mit 0x20 zu tun?

    Wenn du reine Zahlen nutzt ist es für meinen Assembler (so wie auch in fast allen anderen Sprachen) eine Konstante und keine Adresse. Sprich das ist das selbe wie wenn du bei anderen assemblern ein # einbaust, nur halt automatisch am typ erkannt. (ein # führt bei meinem assembler zu einem parser Fehler.) Wenn du die Adresse von etwas als konstante haben willst musst du es auch so sagen, z.B.: "lda ADDR(vic.border_color) >> 8" (damit wird also $d0 nach A geladen, die konstante nicht der Inhalt der Adresse).

  • Zitat von Hexworx

    Es kann ja mal vorkommen, dass man parallel zwei Kopierschleifen am Laufen hat und somit acht ZP-Adressen braucht.

    Fuer Schleifen verwende ich ganz gerne Self-Modifying Code, das macht den Code oft letztendlich viel kuerzer und lesbarer, meiner Erfahrung nach zumindest.

  • Ich baue gerade an einem neuartigen Assembler, der einem nicht nur viel Arbeit abnimmt sondern auch so einiges erst gar nicht erlaubt. Alles (incl, Variablen, Labels oder auch Zahlen) hat einen Typ und es wird gecheckt ob der Typ dort zulaessig ist bzw. entsprechend reagiert.

    Das klingt nach einem verzuckerter Assembler. :weg:
    Ach nee, das war C ja schon. :whistling:


    Aber mal im ernst. Kommst Du mit Deinem geplanten Assembler nicht tatsächlich in die Nähe von C?

  • Es kann ja mal vorkommen, dass man parallel zwei Kopierschleifen am Laufen hat und somit acht ZP-Adressen braucht. Wer damit aber auch nicht auskommt...

    ...der nimmt die zwölf Bytes von FAC1 und FAC2. Die werden nur bei Float-Operationen benutzt und sind somit quasi frei verfügbar.

    Wenn du reine Zahlen nutzt ist es für meinen Assembler (so wie auch in fast allen anderen Sprachen) eine Konstante und keine Adresse. Sprich das ist das selbe wie wenn du bei anderen assemblern ein # einbaust, nur halt automatisch am typ erkannt. (ein # führt bei meinem assembler zu einem parser Fehler.) Wenn du die Adresse von etwas als konstante haben willst musst du es auch so sagen, z.B.: "lda ADDR(vic.border_color) >> 8" (damit wird also $d0 nach A geladen, die konstante nicht der Inhalt der Adresse).

    Ok, aber wenn bei "lda $d020" das "$d0" stillschweigend ignoriert wird, ist das ein neues Problem in der Größenordnung vom jetzigen "Scheiße, '#' vergessen und nicht gemerkt!".

  • Gerade Assembler-Anfänger können am wenigsten beurteilen, wann sich Zeropage-Adressierung lohnt und wann nicht - oder wieviel Ersparnis sie in einem konkreten Fall überhaupt bringt. Wenn man $fb bis $fe für Zeiger benutzt und ansonsten alle Variablen direkt im oder hinter dem Programm ablegt, reicht das in 99% aller Fälle völlig aus.

    WENN kein Kernal genutzt wird, was fast alle meine Spiele tun, kann man $02-$ff nutzen. Und manchmal ist das auch erforderlich[1] oder zumindest viel schneller. Aber das darf man nur machen wenn man keine Kernal Routinen nutzt und am besten auch IRQ/NMI abstellen, Kenrnal ausblendet und eigene Routinen nutzen.


    [1] Für unser aktuelles Spiel brauchte ich 32 pointer die per "(ptr), x" genutzt werden und das geht halt nur mit 64 ZP-Adressen. Ja, es wäre möglich die wo anders zu speichern und dann nur bei bedarf einen in die ZP zu kopieren, aber das wäre zu langsam in meinem Fall - ist auch so schon am Limit.

  • Fuer Schleifen verwende ich ganz gerne Self-Modifying Code, das macht den Code oft letztendlich viel kuerzer und lesbarer, meiner Erfahrung nach zumindest.

    Das stimmt. Insbesondere bei Schleifen, die eh nur einmal durchlaufen werden und nicht flexibel sein brauchen. Auch wenn es letztenendes meist etwas langsamer ist.


    Sobald eine Adresse aber mehrfach benötigt wird, ist es natürlich einfacher/schneller nur eine Adresse zu ändern, wie bei mir grad bei der Proportionalschrift, wo sowas wie:


    ora (bitmap),y
    sta (bitmap),y
    [...]
    ora (bitmap),y
    sta (bitmap),y


    drinsteckt. Da müsste ich ja sonst 8 x vier Adresse ändern, statt nur 8 x DEY.

  • Das stimmt. Insbesondere bei Schleifen, die eh nur einmal durchlaufen werden und nicht flexibel sein brauchen. Auch wenn es letztenendes meist etwas langsamer ist.

    Wenn man es richtig macht, ist selbstmodifizierender Code schneller, nicht langsamer.

  • Zitat von Hexworx

    Sobald eine Adresse aber mehrfach benötigt wird, ist es natürlich einfacher/schneller nur eine Adresse zu ändern


    Klar das stimmt natuerlich. Da faellt mir uebrigens auch gleich auf, dass ich mit Pointern bzw indirekten Adressen so gut wie nie gearbeitet habe. Ist aber sicherlich auch nicht verkehrt :)