In früheren Konzepten wurde erwähnt, dass die global-Direktive eine in einer Quelldatei definierte Funktion oder Variable für andere Dateien sichtbar macht.
Ebenso teilt die extern-Direktive dem Assembler mit, dass eine in der aktuellen Quelldatei verwendete Funktion oder Variable in einer anderen definiert ist.
Diese beiden Direktiven sind der wichtigste Mechanismus, mit dem Assembly-Code mit anderen Quelldateien interagiert, um Modularisierung zu erreichen.
Für diese Direktiven spielt es keine Rolle, ob der externe Code in Assembly oder in einer Hochsprache definiert oder verwendet wird.
So kann zum Beispiel sogar eine in einer Hochsprache definierte Funktion in x86-64-Code verwendet werden, wenn sie als extern deklariert ist.
Auf die gleiche Weise gilt die Aufrufkonvention für alle diese Funktionen, ob sie nun in Assembly oder in Hochsprachen definiert sind, und sogar für das Betriebssystem (OS).
Da Daten in Assembly jedoch nur eine Folge von Bytes sind, ist es wichtig zu wissen, wie die angebundene Hochsprache den Speicher verwaltet.
Die Sprache C ist eine verbreitete Wahl, wenn eine Hochsprache an Assembly-Code angebunden werden soll. Sie erleichtert die Interaktion mit dem Betriebssystem, denn sie bietet High-Level-Wrapper für viele wichtige Aufgaben wie die Ausgabe auf dem Bildschirm, das Lesen von der Tastatur oder das Reservieren von dynamischem Speicher.
In diesem Track sind die Tests in C geschrieben, deshalb ist es wichtig, einen allgemeinen Überblick über die Sprache zu geben.
Jeder der unten aufgeführten Typen kann mit const qualifiziert werden.
Dadurch werden sie schreibgeschützt.
In C gibt es viele primitive Typen, und ihre Größe in Bytes kann variieren. Einige davon sind in der folgenden Tabelle mit ihrer typischen Größe auf einem x86-64-System zusammengefasst:
| Typ | Anzahl der Bytes | Ganzzahl/Gleitkommazahl |
|---|---|---|
| _Bool | 1 | Ganzzahl |
| char | 1 | Ganzzahl |
| short | 2 | Ganzzahl |
| int | 4 | Ganzzahl |
| long | 4 oder 8 | Ganzzahl |
| long long | 8 | Ganzzahl |
| float | 4 | Gleitkommazahl |
| double | 8 | Gleitkommazahl |
Jeder dieser Ganzzahltypen kann vorzeichenbehaftet (die Standardeinstellung) oder vorzeichenlos sein.
Falls er vorzeichenlos ist, muss das ausdrücklich angegeben werden, zum Beispiel: unsigned int.
Für die Ganzzahltypen gibt es Aliase, die definiert sind, um eine präzise Kontrolle über ihre Größe zu ermöglichen:
| Alias | Anzahl der Bytes |
|---|---|
| int8_t | 1 |
| int16_t | 2 |
| int32_t | 4 |
| int64_t | 8 |
Diesen Aliassen kann ein u vorangestellt werden, um einen vorzeichenlosen Typ anzugeben, zum Beispiel: uint64_t.
Weitere erwähnenswerte Aliase sind:
| Alias | Typ |
|---|---|
| bool | _Bool |
| size_t | meist eine vorzeichenlose 8-Byte-Ganzzahl |
Ein _Bool oder sein Alias bool ist insofern einzigartig, als es zwar ein ganzes Byte belegt, aber nur einen von zwei Werten annehmen kann: true (1) und false (0).
Werte dieser primitiven Typen werden nach den üblichen Regeln für Ganzzahlen und Gleitkommawerte an Funktionen übergeben und von ihnen zurückgegeben.
Ein Enum ist ein Typ, der einen von mehreren ausdrücklich angegebenen Elementen annehmen kann:
enum example {
example_1,
example_2,
example_3,
example_4
};
In C werden Enums implizit in einen Ganzzahltyp umgewandelt, typischerweise in einen int, also eine 4-Byte-Ganzzahl.
Standardmäßig wird das erste mögliche Element eines Enums in 0 umgewandelt und jedes weitere Element in die jeweils nächste Ganzzahl.
Im oben definierten enum example hätte example_1 also den Wert 0, example_2 den Wert 1 und so weiter.
Du kannst den Wert für jedes beliebige Element eines Enums angeben; standardmäßig bekommt das nächste Element dann die nächste Ganzzahl als Wert:
enum example_2 {
example_1 = 4,
example_2 = 8,
example_3
}; // example_3 is equal to 9
In C wird eine Speicheradresse über einen Zeiger auf einen Typ referenziert und mit dem Operator * bezeichnet.
int64_t * verweist zum Beispiel auf eine Speicherstelle für einen oder mehrere int64_t.
Adressen werden wie üblich als 8-Byte-Ganzzahlen behandelt.
Arrays werden als Zeiger auf ihr erstes Element an Funktionen übergeben und von ihnen zurückgegeben.
int64_t example_arr[] = {1, 2, 3}; // this is an array of 3 signed 8-byte integers
fn(example_arr); // this is a function that passes a pointer (int64_t *) to the beginning of the array as an argument
Jedes Element eines Arrays hat die Größe des Elementtyps des Arrays.
Das oben definierte example_arr hat zum Beispiel 3 Elemente zu je 8 Bytes, insgesamt also 24 Bytes.
Da Arrays ihre Länge nicht speichern, ist meist ein separater Wert nötig, der die Anzahl der Elemente angibt, es sei denn, ein Sentinel-Wert markiert das Ende.
Strings sind in C ein Array aus char, einem 1-Byte-Typ.
Die meisten Strings bestehen aus ASCII-Zeichen und sind NUL-terminiert, enden also, sobald ein Byte mit dem Wert 0 auftritt.
Deshalb muss die Länge eines Strings meist nicht als separates Argument übergeben werden.
Dein Freund Chandler hat vor, exotische Länder auf der ganzen Welt zu bereisen. Leider sind Chandlers Mathekenntnisse nicht die besten. Er macht sich ziemliche Sorgen, dass er auf seiner Reise von Wechselstuben abgezockt wird, und er möchte, dass du ihm einen Währungsrechner programmierst.
Hier sind seine Vorgaben für die App.
Die Funktionen in dieser Übung werden in einer C-Quelldatei mit den angegebenen Signaturen deklariert. Dein x86-64-Assembly-Code muss diese Vorgaben einhalten.
Du hast ein enum currency_t erstellt, um den Überblick über die verschiedenen Währungen zu behalten, die Chandler auf seinen Reisen verwendet:
enum currency_t {
GBP,
EUR,
JPY,
AUD,
BRL,
CNY,
CAD,
INR
};
Manchmal musst du diese Währungen auf dem Bildschirm ausgeben, um zu prüfen, ob sie stimmen.
Erstelle die Funktion stringify_currency:
void stringify_currency(char *buffer, enum currency_t currency);
Diese Funktion hat keinen Rückgabewert und soll einen String im C-Stil an der von buffer angegebenen Stelle speichern.
Der String soll visuell darstellen, welche Art von Währung an die Funktion übergeben wurde:
char buffer[4];
stringify_currency(buffer, CAD);
// => "CAD"
Erstelle die Funktion exchange_rate:
double exchange_rate(enum currency_t domestic_currency, enum currency_t foreign_currency, const double *value_in_US_dollars);
Das Argument domestic_currency steht für Chandlers lokale Währung, die in eine foreign_currency umgetauscht werden soll.
value_in_US_dollars ist ein Zeiger auf ein Array von double, das den Wert einer Einheit jeder Währung in US-Dollar enthält.
Die Währungen sind Indizes in diesem Array.
Wenn EUR also ein gültiges Element für enum currency_t ist, liefert value_in_US_dollars[EUR] den Wert von 1 EUR in US-Dollar.
Diese Funktion soll den Wert einer Einheit der fremden Währung in der heimischen Währung zurückgeben.
const double value_in_US_dollars[8] = {
1.33329, 1.16443, 0.00654374, 0.650888,
0.185577, 0.140420, 0.714142, 0.0113863
};
exchange_rate(JPY, GBP, value_in_US_dollars);
// => 203.750454633
Erstelle die Funktion get_value_of_bills:
uint64_t get_value_of_bills(unsigned long long denomination, unsigned short number_of_bills);
Das Argument denomination ist der Wert eines einzelnen Geldscheins, während number_of_bills die Gesamtzahl der Geldscheine ist.
Diese Wechselstube handelt nur mit Bargeld in bestimmten Stückelungen. Der Gesamtbetrag, den du erhältst, muss durch den Wert eines „Geldscheins“ bzw. einer Einheit teilbar sein, wobei ein Bruchteil oder Rest übrig bleiben kann. Deine Funktion soll nur den Gesamtwert der Geldscheine (ohne Bruchteile) zurückgeben, den die Wechselstube auszahlen würde. Leider darf die Wechselstube den Rest bzw. das Wechselgeld als zusätzlichen Bonus behalten.
get_value_of_bills(5, 128);
// => 640
Erstelle die Funktion get_number_of_bills:
unsigned int get_number_of_bills(float amount, unsigned long long denomination);
Diese Funktion soll die Anzahl der Geldscheine zurückgeben, die du innerhalb des angegebenen Betrags erhalten kannst. Anders gesagt: Wie viele ganze Geldscheine passen in den Ausgangsbetrag? Denk daran: Du kannst nur ganze Geldscheine erhalten, keine Bruchteile von Scheinen, also teile entsprechend. Im Grunde rundest du auf den nächsten ganzen Geldschein bzw. die nächste ganze Stückelung ab.
get_number_of_bills(127.5, 5);
// => 25
Erstelle die Funktion exchangeable_value:
uint32_t exchangeable_value(float budget, double exchange_rate, uint8_t spread, unsigned long long denomination);
Der Parameter spread ist der einbehaltene Prozentsatz als Umtauschgebühr, angegeben als Ganzzahl.
Er muss in einen Dezimalwert umgerechnet werden, indem du ihn durch 100 teilst.
Beachte, dass der Spread zum Wechselkurs addiert wird, sodass ein Spread von 10 zu einem um 10 % höheren Wechselkurs führt.
Diese Funktion soll den maximalen Wert der neuen Währung zurückgeben, nachdem der um den Spread angepasste Wechselkurs berechnet wurde. Denk daran, dass die Stückelung der Währung eine ganze Zahl ist und nicht weiter unterteilt werden kann.
exchangeable_value(127.25, 0,873350884, 10, 20);
// => 120
exchangeable_value(127.25, 0,873350884, 10, 5);
// => 130
Melde dich bei Exercism an, um x86-64 Assembly mit 22 Konzepte130 Übungen und echtem menschlichen Mentoring zu lernen und zu meistern, alles kostenlos.