Im Tutorium haben wir uns hauptsächlich mit IEEE-Fließkommazahlen beschäftigt: Umwandeln, Rechnen und Runden.
Im folgenden ein paar Hinweise zu der letzten Aufgabe auf dem 5. Übungszettel.
Ihr könnt für die Aufgabe folgendes C-Framework benutzen:
// Funktionen für Standard-Ein-/Ausgabe einbinden
#include <stdio.h>
// Zahlentypen importieren, die fest definierte Bit-Längen haben
// Wir können damit "int" ersetzen, was auch je nach System nicht immer genau
// 32 bit sein muss…
// Eine Ganzzahl mit garantiert 32bit wäre int32_t, ohne Vorzeichen uint32_t
#include <inttypes.h>
// Funktionen um Strings zu manipulieren (wir benötigen die Funktion memset)
#include <string.h>
// Dies ist die Funktion, die eine 64bit (double precision) IEEE-Zahl entgegen-
// nimmt und einen Integer zurückgibt (natürlich ebenfalls 64bit)
uint64_t float_to_int(double);
// Diese Funktion müsst ihr implementieren
// Sie bekommt die IEEE-Zahl, einen Zeiger auf ein in C angelegten Speicher-
// bereich, in den der String als Bit-Darstellung reingeschrieben werden soll
// Zurückgegeben wird nichts
// Wir garantieren, dass der Speicherbereich >= 64 Zeichen ist, wir also ohne
// Probleme das Bitmuster reinschreiben können
void float_to_string(double, char[]);
// Konstanten definieren
#define SPEICHERGROESSE 65
int main() {
// Unsere Kommazahl, die dargestellt werden soll
double kommazahl = 345.2143;
// Wird die Ganzzahl enthalten
uint64_t ganzzahl;
// Lege Speicher für Bit-String an
char bitdarstellung[SPEICHERGROESSE];
// Ein String in C endet, wenn das Zeichen mit ASCII-Code 0 gelesen wurde
// Daher überschreiben wir erstmal alles, was zufällig in dem Speicher
// liegt mit Nullen
memset(bitdarstellung, 0, SPEICHERGROESSE);
// Zur Information erst noch einmal die Kommazahl ausgeben
printf("Kommazahl ist: %f\n", kommazahl);
// Integer aus der Kommazahl "umwandeln", sodass das Bitmuster der IEEE-
// Zahl als Integer interpretiert wird, und dann ausgeben
ganzzahl = float_to_int(kommazahl);
printf("Ganzzahl ist: %lu\n", ganzzahl);
// Bitmuster der IEEE-Kommazahl in den reservierten Speicher
// "bitdarstellung" schreiben und dann ausgeben
float_to_string(kommazahl, bitdarstellung);
printf("Bit-String ist: %s\n", bitdarstellung);
return 0; // Wir waren erfolgreich
}
Mit dem Framework und der darin angegeben Zahl sollte folgende Ausgabe erscheinen:
Kommazahl ist: 345.214300
Ganzzahl ist: 4644780690382403718
Bit-String ist: 0100000001110101100100110110110111000101110101100011100010000110
Was jetzt in NASM noch fehlt sind die beiden Funktionen float_to_int
und float_to_string
. Ihr müsst float_to_string
implementieren, ich gebe als Beispiel float_to_int
vor:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Das eigentliche Programm besteht hier nur aus den Zeilen 12 und 14. Wichtig zu wissen ist, dass die uns bisher bekannten Register (rax
, rbx
, …, rdi
, …) nicht für das Rechnen mit Kommazahlen genutzt werden (können). Dafür gibt es die Register xmm0
bis xmm15
. Daher wird der Parameter, der an die Funktion übergeben wird, statt in ein Ganzzahlen-Register in das erste dieser Register (xmm0
) geschrieben.
In diesem Register steht jetzt die Zahl als IEEE-Fließkommazahl. Prinzipiell wollen wir die Bitdarstellung komplett übernehmen und nur als Ganzzahl habe. Wir müssen also irgendwie die Bits aus xmm0
in ein Ganzzahlregister (z.B. rax
) bekommen.
Ein einfaches mov rax, xmm0
wirft jedoch Fehler, da mov
nicht auf die xmm
-Register zugreifen kann. Für diese Register gibt es spezielle Befehle unter anderem das Analoge zu mov
: movlpd
, welches aber nicht auf die Ganzzahl-Register zugreifen kann. Beide Befehle können jedoch in den Speicher schreiben und aus ihm lesen. Daher wenden wir einen kleinen Trick an: Wir kopieren erst die Fließkommazahl in den Speicher (Zeile 12) und kopieren dann die Bits aus dem Speicher in ein Ganzzahl-Register (14). Diese Ganzzahl wird dann zurückgegeben.
Ihr habt euch wahrscheinlich schon gewundert, was diese eckigen Klammern bei [temp]
bedeuten. In Zeile 4 werden ja 8 Byte Speicher reserviert und die Adresse des Speichers (was auch nur eine Zahl ist) in temp
gespeichert. Würden wir z.B. mov rax, temp
ausführen, würde danach in rax
die Adresse stehen, die in temp
gespeichert ist. Durch die zusätzlichen eckigen Klammern sagen wir: “Nimm nicht die Zahl aus temp
als Operand, sondern das Stück Speicher mit der Adresse, die in temp
steht.”
1 2 3 4 5 6 7 8 9 10 11 12 |
|
In der Funktion float_to_string
bekommen wir ein char[]
übergeben. Das ist nichts anderes als ein Array von chars
und ein char
ist der C-Datentype für ein Byte. Dieses Array liegt als ein durchgängiger Speicher-Block im Speicher. In unserem Fall ist das Array 65 Byte groß, im Speicher sind also 65 aufeinanderfolgende Byte für das Array reserviert. Der Wert, den wir für dieses Array übergeben bekommen ist die Adresse, des ersten Elements.
[1 Byte][1 Byte][1 Byte] ... [1 Byte]
^ ^
| Dieses Byte hat dann die Adresse rdi+1
Die Adresse von diesem Byte wird übergeben (z.B. in rdi)
Kleines Beispiel
array.c:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
array.asm:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
|
Ein String in C ist ebenfalls nichts anderes als ein char
-Array, welches in jedem Eintrag den ASCII-Code des jeweiligen Zeichens speichert. Wollt ihr also Beispielsweise in ein char
-Array der Länge 5 das Wort “Hallo” reinschreiben müssen in die einzelnen Array-Einträge die Zahlen 72, 97, 108, 108, 111 geschrieben werden. Zur Vereinfachung bietet NASM auch an, selber eingegebene Zeichen in ASCII-Code umzuwandeln, es ist statt mov [rbx], BYTE 70
auch möglich mov [rbx], BYTE 'F'
zu schreiben.
Das sollte euch erstmal einen groben Überblick auf Speicherzugriffe geben.
Der Ablauf eures Programm sollte also in etwa so aussehen:
Die Operation and ziel, quelle
macht ein bitweises logisches Und der beiden Operanden und schreibt das Ergebnis in ziel
– Beispiel:
ziel : 00011001
quelle: 10101111
----------------
=>ziel: 00001001
test op1, op2
macht genau das selbe, schreibt das Ergebnis aber nicht in op1
zurück, sondern setzt nur Statusregister. Für diese Aufgabe könnte das Zero-Flag (ZF
mit jz
/jnz
) interessant sein.
Weiteres werden wird dann im Tutorium am Mittwoch besprechen. Solltet ihr vorher schon Fragen haben, schickt mir eine E-Mail.