У попередніх концепціях згадувалося, що директива global робить функцію або змінну, визначену в одному файлі з вихідним кодом, видимою для інших файлів.
Так само директива extern повідомляє асемблеру, що функція або змінна, яка використовується в поточному файлі з вихідним кодом, визначена в іншому файлі.
Ці дві директиви забезпечують основний спосіб, у який код асемблера взаємодіє з іншими файлами з вихідним кодом, щоб досягти модульності.
Для цих директив не має значення, чи зовнішній код визначено або використано в асемблері, чи мовою високого рівня.
Так, наприклад, навіть функцію, визначену мовою високого рівня, можна використати в коді x86-64, якщо оголосити її extern.
Так само угода про виклик спільна для всіх цих функцій, визначених в асемблері чи мовами високого рівня, і навіть для операційної системи (ОС).
Однак, оскільки в асемблері під даними розуміють лише послідовність байтів, важливо розуміти, як мова високого рівня, з якою ми взаємодіємо, керує памʼяттю.
Мову C часто обирають як мову високого рівня для взаємодії з кодом асемблера. Вона полегшує взаємодію з ОС, пропонуючи високорівневі обгортки для багатьох важливих завдань, як-от виведення на екран, читання з клавіатури чи виділення динамічної памʼяті.
У цьому треку тести написано мовою C, тож важливо дати загальний огляд цієї мови.
Будь-який із наведених нижче типів можна кваліфікувати за допомогою const.
Це робить їх доступними лише для читання.
У C є багато примітивних типів, і їхній розмір у байтах може відрізнятися. Деякі з них зібрано в наступній таблиці разом з їхнім типовим розміром у системі x86-64:
| тип | кількість байтів | ціле/з плаваючою комою |
|---|---|---|
| _Bool | 1 | ціле |
| char | 1 | ціле |
| short | 2 | ціле |
| int | 4 | ціле |
| long | 4 або 8 | ціле |
| long long | 8 | ціле |
| float | 4 | з плаваючою комою |
| double | 8 | з плаваючою комою |
Кожен із цих цілочисельних типів може бути зі знаком (це типовий варіант) або без знака.
Якщо тип без знака, це потрібно вказати явно, наприклад: unsigned int.
Існують псевдоніми для цілочисельних типів, визначені, щоб дати точний контроль над їхнім розміром:
| псевдонім | кількість байтів |
|---|---|
| int8_t | 1 |
| int16_t | 2 |
| int32_t | 4 |
| int64_t | 8 |
Ці псевдоніми можна доповнити префіксом u, щоб позначити тип без знака, наприклад: uint64_t.
Інші варті уваги псевдоніми:
| псевдонім | тип |
|---|---|
| bool | _Bool |
| size_t | зазвичай ціле число без знака розміром 8 байтів |
_Bool, або його псевдонім bool, особливий тим, що хоч він займає цілий байт, він може набувати лише одного з двох значень: true (1) та false (0).
Значення цих примітивних типів передають у функції та повертають із них за звичайними правилами для цілих чисел і чисел з плаваючою комою.
Перелік - це тип, який може набувати одного з кількох явно вказаних елементів:
enum example {
example_1,
example_2,
example_3,
example_4
};
У C переліки неявно перетворюються на цілочисельний тип, зазвичай int, тобто 4-байтове ціле число.
Типово перший можливий елемент переліку перетворюється на 0, а всі наступні елементи - на наступне ціле число.
Отже, у визначеному вище enum example елемент example_1 мав би значення 0, example_2 - значення 1, і так далі.
Можна вказати значення для будь-якого елемента переліку, і тоді типово наступний елемент матиме наступне ціле число як значення:
enum example_2 {
example_1 = 4,
example_2 = 8,
example_3
}; // example_3 is equal to 9
У C адресу памʼяті позначають вказівником на тип, використовуючи оператор *.
Так, наприклад, int64_t * указує на ділянку памʼяті для одного або кількох int64_t.
Адреси, як завжди, розглядають як 8-байтові цілі числа.
Масиви передають у функції та повертають із них як вказівники на їхній перший елемент.
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
Кожен елемент масиву має розмір типу елементів цього масиву.
Наприклад, визначений вище example_arr має 3 елементи по 8 байтів кожен, тобто 24 байти загалом.
Оскільки масиви не зберігають свою довжину, зазвичай потрібно окремо вказувати кількість елементів, якщо кінець не позначає сторожове значення.
Рядки тексту (англ. string) у C - це масив char, тобто 1-байтовий тип.
Більшість рядків тексту складається з ASCII-символів і завершується NUL-байтом, тож вони закінчуються, коли трапляється байт зі значенням 0.
Це означає, що довжину рядка тексту зазвичай не потрібно передавати окремим аргументом.
Наш друг Чендлер планує відвідати екзотичні країни в різних куточках світу. На жаль, із математикою в Чендлера не все гаразд. Він дуже хвилюється, що під час подорожі його обдурять в обмінниках, і хоче, щоб ми зробили для нього валютний калькулятор.
Ось його вимоги до застосунку.
Функції в цій вправі оголошено у вихідному файлі мовою C із наданими сигнатурами. Наш код мовою асемблера x86-64 має відповідати цим специфікаціям.
Ми створили enum currency_t, щоб вести облік різних валют, якими Чендлер користується у своїх подорожах:
enum currency_t {
GBP,
EUR,
JPY,
AUD,
BRL,
CNY,
CAD,
INR
};
Іноді нам потрібно вивести ці валюти на екран, щоб перевірити, чи все правильно.
Створімо функцію stringify_currency:
void stringify_currency(char *buffer, enum currency_t currency);
Ця функція не повертає значення: вона має записати рядок тексту (англ. string) у стилі C за адресою, указаною в buffer.
Цей рядок тексту має візуально показувати, який різновид валюти передано у функцію:
char buffer[4];
stringify_currency(buffer, CAD);
// => "CAD"
Створімо функцію exchange_rate:
double exchange_rate(enum currency_t domestic_currency, enum currency_t foreign_currency, const double *value_in_US_dollars);
Аргумент domestic_currency позначає місцеву валюту Чендлера, яку потрібно обміняти на foreign_currency.
Аргумент value_in_US_dollars указує на масив double, що містить вартість однієї одиниці кожної валюти в доларах США.
Кожна валюта відповідає індексу в цьому масиві.
Тож якщо EUR є допустимим елементом enum currency_t, тоді value_in_US_dollars[EUR] дає вартість 1 EUR у доларах США.
Ця функція має повернути вартість однієї одиниці іноземної валюти у внутрішній валюті.
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
Створімо функцію get_value_of_bills:
uint64_t get_value_of_bills(unsigned long long denomination, unsigned short number_of_bills);
Аргумент denomination задає вартість однієї купюри, а number_of_bills - загальну кількість купюр.
Цей обмінний пункт працює лише з готівкою певного номіналу. Отримана сума має бути кратною вартості однієї «купюри», тобто одиниці, тож може залишитися дробова частина або остача. Наша функція має повернути лише загальну вартість купюр (без дробової частини), які видасть обмінний пункт. На жаль, залишок/решту обмінний пункт залишає собі як приємний бонус.
get_value_of_bills(5, 128);
// => 640
Створімо функцію get_number_of_bills:
unsigned int get_number_of_bills(float amount, unsigned long long denomination);
Ця функція має повернути кількість купюр, які можна отримати в межах заданої суми. Іншими словами: скільки цілих купюр вміщається у початкову суму? Памʼятаймо: отримати можна лише цілі купюри, а не їхні частини, тож ділімо відповідно. Фактично ми округлюємо вниз до найближчої цілої купюри/номіналу.
get_number_of_bills(127.5, 5);
// => 25
Створімо функцію exchangeable_value:
uint32_t exchangeable_value(float budget, double exchange_rate, uint8_t spread, unsigned long long denomination);
Параметр spread задає відсоток, який утримується як комісія за обмін, записаний цілим числом.
Його потрібно перетворити на десяткове число, поділивши на 100.
Звернімо увагу: спред додається до обмінного курсу, тож спред 10 дає курс на 10% вищий.
Ця функція має повернути максимальну вартість нової валюти після обчислення обмінного курсу, скоригованого на спред. Памʼятаймо, що номінал валюти - ціле число, і його не можна дробити.
exchangeable_value(127.25, 0,873350884, 10, 20);
// => 120
exchangeable_value(127.25, 0,873350884, 10, 5);
// => 130
Зареєструйтеся на Exercism, щоб вивчати й опановувати x86-64 Assembly, а також 22 концепції130 вправ та справжнє наставництво від людей, і все це безкоштовно.