Entwickler-Dokumentation

Assembler-Projekt 1.31 - Gruppe 4
  strcmpnew
  Quellcode
Funktionsschritte
Realisierungsalternativen
Hinzufügen von weiteren Optionen
strncmpnew
  Quellcode
Funktionsschritte
Realisierungsalternativen
Hinzufügen von weiteren Optionen
Assemblierung
 
Navigation: Projekthauptseite

strcmpnew

Quellcode


;Datei: strcmpn.asm
;enthält Funktion strcmpnew
;
;Compilierung mit
; tasm /ml strcmpn
;oder
; tasm /ml /zi strcmpn
;
;/ml ist wichtig, damit die Schreibweise der Funktionen exakt beibehalten wird
;(C/C++ ist case-sensitive)
;
;/zi erzeugt Debuginformation mit Zeilennummern (für Debug-Versionen von Programmen)
;

.386

code segment byte public 'CODE' use32

    public _strcmpnew

    assume cs:code

_strcmpnew proc near
;nach C-Konvention muß der Funktionsname mit einem Unterstrich beginnen
;
;Prototyp in C:
;extern "C" int strcmpnew(const char *str1, const char *str2)
;

;Funktions-Code
        push  esi               ;Register esi und
        push  edi               ; edi müssen in Visual C++ gerettet werden
        pushfd                  ;EFlags sichern, da Direction-Flag verändert wird

;auf dem Stack befinden sich jetzt:
;   [esp+20]    dword-near-Zeiger auf str2
;   [esp+16]    dword-near-Zeiger auf str1
;   [esp+12]    near-Rücksprungadresse
;   [esp+ 8]    gerettetes esi
;   [esp+ 4]    gerettetes edi
;   [esp]       gerettete EFlags

;für Zugriff auf die übergebenen Zeiger:
    str2 equ [esp+20]
    str1 equ [esp+16]

        cld                     ;Blocktransportrichtung: inkrementell
        mov   esi,str1          ;Offset str1 -> esi
        mov   edi,str2          ;Offset str2 -> edi
        mov   ax,ds
        mov   es,ax             ;es mit ds initialisieren (für scas-Befehl)
cmploop:
        lods  byte ptr [esi]    ;Byte von str1 nach al laden
        scas  byte ptr [edi]    ;Vgl. mit Byte von str2
        jne short Diff          ;ungleich ?
        cmp   al,0              ;Stringende ?
        jnz   cmploop           ;wenn nicht, dann nächstes Zeichen
Equal:
        xor   eax,eax           ;str1 = str2 ->  0 zurückgeben
        jmp short _End          ;fertig
Diff:
        ja short Str2LessStr1   ;Flags von cmp sind immer noch gültig
        mov   eax,-1            ;str1 < str2 -> -1 zurückgeben
        jmp short _End          ;fertig
Str2LessStr1:
        mov   eax,1             ;str2 < str1 -> +1 zurückgeben
_End:
        popfd                   ;EFlags wiederherstellen
        pop   edi               ;edi und
        pop   esi               ; esi wiederherstellen
        ret                     ;Stack wird vom Aufrufer "aufgeräumt"

_strcmpnew endp

code ends
end
Anfang

Funktionsschritte

  1. Am Anfang der Funktion werden die Register esi und edi gesichert, da der Visual C++ Compiler erwartet, daß diese Register nicht verändert werden. Außerdem wird das Flag-Register gesichert, da auch das Direction-Flag erhalten bleiben muß.
  2. Danach werden die Register esi, edi und es auf die Verwendung der Blockbefehle lods und scas vorbereitet. Die Adressen der Strings str1 und str2 werden in die Register esi und edi geladen, und das Direction-Flag gelöscht, damit die Blockbefehle mit aufsteigenden Adressen arbeiten.
  3. Label "cmploop"
    In der Vergleichsschleife wird ein Byte von str1 nach al geladen, und dann mit dem entsprechenden Byte von str2 verglichen.
  4. Sind die beiden Bytes verschieden, wird mit 7. fortgefahren.
  5. Sind die beiden Bytes gleich, wird als nächstes geprüft, ob das Stringende erreicht wurde, also ob al=0. Wenn ja, wird mit 6. fortgefahren, ansonsten wird bei 3. das nächste Byte verglichen.
  6. Label "Equal"
    Die beiden Strings müssen gleich sein, also wird eax wird mit dem Funktionsergebnis 0 geladen und zum Ende der Funktion, Schritt 8 gesprungen.
  7. Label "Diff"
    Die beiden Strings müssen verschieden sein. Wenn das Byte von str1 kleiner als das von str2 ist, wird eax mit dem Funktionsergebnis -1 geladen, andernfalls mit +1 und zum Ende der Funktion, Schritt 8 gesprungen.
  8. Label "_End"
    Das zuvor unter 1. gesicherte Flag-Register sowie edi und esi werden wiederhergestellt und schließlich wird zum aufrufenden Programm zurückgekehrt.
Anfang

Assemblierung

Um eine Object-Datei aus dem Quellcode zu erzeugen, ist unter DOS folgender Aufruf des Borland Turbo Assemblers nötig:

Der Parameter /ml sorgt dafür, daß die Schreibweise "strcmpnew" exakt beibehalten wird, da in C-Programmen die Groß-/Kleinschreibung wichtig ist. Der Parameter /zi sorgt für die Aufnahme von Debuginformationen in die .obj-Datei. Damit ist es möglich beim Debuggen von Programmen die Verbindung zwischen den Programm-Modulen und deren Quelltext herzustellen.

Man hätte die Assembleranweisungen von strcmpnew auch innerhalb eines __asm-Blocks direkt in einer C-Bibliothek schreiben können (sog. inline-Assembler). Wir haben uns jedoch für die Erzeugung von .obj-Dateien entschieden, weil diese auch in anderen Programmiersprachen als C eingesetzt werden können und dem Programmierer außerdem größere Freiheiten bei der Codegestaltung läßt.

Anfang

Realisierungsalternativen

Eine Alternative zum gewählten Lösungsweg wäre es, wenn man die beiden Strings mit der Blockanweisung


  repe cmpsb
auf einmal vergleichen würde, man bräuchte auf diese Weise keine Wiederholungsschleife. Vorher müsste man jedoch die Länge eines der beiden Strings bestimmen (z.B. str1), indem man seine Ende-Markierung (ein '0'-Byte) sucht:

  mov   ecx,0ffffffffh    ;maximale Segmentgröße
  mov   al,0              ;'0'-Byte wird gesucht
  repne scasb             ;Vergleich von al mit es:[edi], bis '0' gefunden
Dies hat jedoch Nachteile in der Ausführungszeit, denn es wird immer ein ganzer String abgesucht, um dessen Ende zu finden. In ungünstigen Fällen wird dabei ein String ganz abgearbeitet, obwohl sich beide Strings schon in den ersten Zeichen unterscheiden, oder der andere String viel kürzer wäre. Die Ausführungsgeschwindigkeit hinge zu sehr von der Reihenfolge der Parameter ab.
Bei dem von uns gewählten Lösungsweg wird in einer Schleife immer ein Zeichen des ersten Strings mit dem entsprechenden des zweiten Strings verglichen. Auf diese Weise werden nur so viele Vergleiche wie nötig gemacht.

Anfang

Hinzufügen von weiteren Optionen

Wenn die Parameterliste der Funktion erweitert werden soll, muß beim Zugriff beachtet werden, daß der Compiler die Parameter von rechts nach links auf dem Stack ablegt. Am einfachsten ist deswegeb die Erweiterung, wenn neu hinzugefügte Parameter hinten an die bereits bestehenden angefügt werden. An der Adressierung der ursprünglichen Parameter muß dann nämlich nichts verändert werden, da sie weiterhin als letzes auf den Stack gelegt werden.
Wenn es neue Optionen notwendig machen, daß innerhalb des Unterprogramms mittels push/pop auf den Stack zugegriffen wird, ist es empfehlenswert, die Parameter nicht über esp, wie es in der jetzigen Version geschieht, zu adressieren, sondern einen Stackrahmen einzurichten und ebp zu verwenden. Anfang und Ende der Funktion sähen dann etwa so aus:


  push  ebp               ;ebp sichern
  mov   ebp,esp           ;jetzige Stackhöhe in ebp

  ...                     ;Anweisungen ...

  pop   ebp               ;ebp wiederherstellen
  ret                     ;Rücksprung
Eine sinnvolle Ergänzung der Funktion strcmpnew ist z.B. die Erweiterung um einen Parameter n, der die maximale Anzahl Zeichen zweier Strings angibt, die verglichen werden sollen (so geschehen in der unten beschriebenen Funktion strncmpnew). Der Parameter liegt dann 4 Bytes über dem Zeiger auf str2 auf dem Stack. Innerhalb der Vergleichsschleife muß dann noch geprüft werden, ob bereits n Zeichen verglichen wurden.

Sinnvoll wäre auch die Möglichkeit, neben dem normalen Funktionsergebnis einen Wert zurückzugeben, der den Index des ersten verschiedenen Zeichens enthält. Die Parameterliste müsste dann um einen Zeiger auf eine 32-Bit Integer-Variable ergänzt werden. Innerhalb der Vergleichsschleife würde ein Zählregister mit jedem Vergleichsschritt inkrementiert werden, und sein Wert am Ende der Variablen zugewiesen werden.

Anfang

strncmpnew

Quellcode


;Datei: strncmpn.asm
;enthält Funktion strncmpnew
;
;Compilierung mit
; tasm /ml strncmpn
;oder
; tasm /ml /zi strncmpn
;
;/ml ist wichtig, damit die Schreibweise der Funktionen exakt beibehalten wird
;(C/C++ ist case-sensitive)
;
;/zi erzeugt Debuginformation mit Zeilennummern (für Debug-Versionen von Programmen)
;

.386

code segment byte public 'CODE' use32

    public _strncmpnew

    assume cs:code

_strncmpnew proc near
;nach C-Konvention muá der Funktionsname mit einem Unterstrich beginnen
;
;Prototyp:
;extern "C" int strncmpnew(const char *str1, const char *str2, int n)
;

;Funktions-Code
        push  esi               ;esi und
        push  edi               ; edi müssen in Visual C++ gerettet werden
        pushfd                  ;EFlags sichern, da DF verändert wird

;auf dem Stack befinden sich jetzt:
;   [esp+24]    dword-Wert n
;   [esp+20]    dword-near-Zeiger auf str2
;   [esp+16]    dword-near-Zeiger auf str1
;   [esp+12]    near-Rücksprungadresse
;   [esp+ 8]    gerettetes esi
;   [esp+ 4]    gerettetes edi
;   [esp   ]    gerettete EFlags

;für Zugriff auf die übergebenen Parameter:
    n    equ [esp+24]
    str2 equ [esp+20]
    str1 equ [esp+16]


        mov   ecx,n             ;Zähler
        jecxz short nEqual      ;wenn n=0, dann kein Vergleich nötig

        cld                     ;Blocktransportrichtung: inkrementell
        mov   esi,str1          ;Offset str1 -> esi
        mov   edi,str2          ;Offset str2 -> edi
        mov   ax,ds
        mov   es,ax             ;es mit ds initialisieren (für scas-Befehl)
ncmploop:
        lods  byte ptr [esi]    ;Byte von str1 nach al laden
        scas  byte ptr [edi]    ;Vgl. mit Byte von str2
        jne short nDiff         ;ungleich
        cmp   al,0              ;Stringende ?
        jz short nEqual         ; => Strings müssen gleich sein
        dec   ecx               ;Zähler dekrementieren
        jnz   ncmploop          ;wenn noch keine n Bytes verglichen wurden, dann nächster Vergleich
nEqual:
        xor   eax,eax           ;str1 = str2 ->  0 zurückgeben
        jmp short nEnd          ;fertig
nDiff:
        ja short nStr2LessStr1  ;Flags von cmp sind immer noch gültig
        mov   eax,-1            ;str1 < str2 -> -1 zurückgeben
        jmp short nEnd          ;fertig
nStr2LessStr1:
        mov   eax,1             ;str2 < str1 -> +1 zurückgeben
nEnd:
        popfd                   ;EFlags wiederherstellen
        pop   edi               ;edi und
        pop   esi               ; esi wiederherstellen
        ret                     ;Stack wird vom Aufrufer "aufgeräumt"

_strncmpnew endp

code ends
end
Anfang

Funktionsschritte

  1. Am Anfang der Funktion werden die Register esi und edi gesichert, da der Visual C++ Compiler erwartet, daß diese Register nicht verändert werden. Außerdem wird das Flag-Register gesichert, da auch das Direction-Flag erhalten bleiben muß.
  2. Danach wird das Register ecx mit dem Zähler n geladen und über jecxz geprüft, ob es den Wert 0 enthält. In diesem Fall wird sofort mit Schritt 8. weitergemacht (bei Vergleich von 0 Zeichen wird als Resultat Gleichheit zurückgegeben)
  3. Als nächstes werden die Register esi, edi und es auf die Verwendung der Blockbefehle lods und scas vorbereitet. Die Adressen der Strings str1 und str2 werden in die Register esi und edi geladen, und das Direction-Flag gelöscht, damit die Blockbefehle mit aufsteigenden Adressen arbeiten.
  4. Label "ncmploop"
    In der Vergleichsschleife wird ein Byte von str1 nach al geladen, und dann mit dem entsprechenden Byte von str2 verglichen.
  5. Sind die beiden Bytes verschieden, wird mit 7. fortgefahren.
  6. Sind die beiden Bytes gleich, wird als nächstes geprüft, ob das Stringende erreicht wurde, also ob al=0. Wenn ja, wird mit 6. fortgefahren.
  7. Schließlich wird der Zähler der verbleibenden Vergleiche ecx um 1 dekrementiert und geprüft, ob er dadurch 0 geworden ist. Wenn ja, ist der Vergleich beendet und es wird mit 6. fortgefahren, ansonsten wird bei 3. das nächste Byte verglichen.
  8. Label "nEqual"
    Die beiden Strings müssen gleich sein, also wird eax wird mit dem Funktionsergebnis 0 geladen und zum Ende der Funktion, Schritt 8 gesprungen.
  9. Label "nDiff"
    Die beiden Strings müssen verschieden sein. Wenn das Byte von str1 kleiner als das von str2 ist, wird eax mit dem Funktionsergebnis -1 geladen, andernfalls mit +1 und zum Ende der Funktion, Schritt 8 gesprungen.
  10. Label "nEnd"
    Das zuvor unter 1. gesicherte Flag-Register sowie edi und esi werden wiederhergestellt und schließlich wird zum aufrufenden Programm zurückgekehrt.
Anfang

Realisierungsalternativen

Eine Alternative zum gewählten Lösungsweg wäre es, wenn man die beiden Strings mit der Blockanweisung


  repe cmpsb
auf einmal vergleichen würde, man bräuchte auf diese Weise keine Wiederholungsschleife. Vorher müsste man jedoch prüfen, ob die Länge eines der beiden Strings kleiner als die durch den Parameter n angegebene maximale Vergleichslänge ist, indem man seine Ende-Markierung (ein '0'-Byte) sucht:

  mov   ecx,n             ;maximale Vergleichslänge
  mov   al,0              ;'0'-Byte wird gesucht
  repne scasb             ;Vergleich von al mit es:[edi], bis '0' gefunden
Dies hat wieder den unter strcmpnew erwähnten Nachteil, daß unter ungünstigen Umständen mehr Vergleiche als notwendig ausgeführt werden.
Bei dem von uns gewählten Lösungsweg wird in einer Schleife immer ein Zeichen des ersten Strings mit dem entsprechenden des zweiten Strings verglichen. Auf diese Weise werden nur so viele Vergleiche wie nötig gemacht.

Anfang

Hinzufügen von weiteren Optionen

Für das Erweitern der Funktion strncmpnew gilt dasselbe wie oben bereits bei strcmpnew beschrieben.

Anfang

Assemblierung

Um eine Object-Datei aus den Quellcode zu erzeugen, ist unter DOS folgender Aufruf des Borland Turbo Assemblers nötig:

für strcmpnew

für strncmpnew

Der Parameter /ml sorgt dafür, daß die Schreibweise in Kleinbuchstaben von "strcmpnew" bzw. "strncmpnew" exakt beibehalten wird, da in C-Programmen die Groß-/Kleinschreibung wichtig ist. Der Parameter /zi sorgt für die Aufnahme von Debuginformationen in die .obj-Datei. Damit ist es möglich beim Debuggen von Programmen die Verbindung zwischen den Programm-Modulen und deren Quelltext herzustellen.

Man hätte die Assembleranweisungen von strcmpnew und strncmpnew auch innerhalb eines __asm-Blocks direkt in einer C-Bibliothek schreiben können (sog. inline-Assembler). Wir haben uns jedoch für die Erzeugung von .obj-Dateien entschieden, weil diese auch in anderen Programmiersprachen als C eingesetzt werden können und dem Programmierer außerdem größere Freiheiten bei der Codegestaltung läßt.

Anfang


zu den TGI-SeitenZu den TGI-Seiten der TUM

12.07.98