Мовою C структура - це складений тип, утворений з різних полів. Кожне поле має власний тип і розмір.
Наприклад, ось так визначається тип, утворений із трьох цілих чисел: двох розміром 4 байти й одного розміром 8 байтів:
struct example {
int32_t first_four_byte_integer;
uint32_t second_four_byte_integer;
uint64_t eight_byte_integer;
};
Структура може також містити інші структури:
struct example_2 {
struct example first;
struct example second;
};
Якщо одне з полів структури є масивом, усі байти, виділені під цей масив, входять до складу структури. Якщо ж поле є лише вказівником на масив, то сам масив треба зберігати деінде:
struct with_array {
char string[20];
};
// all 20 bytes of string are inside struct with_array
struct with_pointer {
const char *string;
};
// only the 8-byte pointer to the string is inside struct with_pointer
Вирівнювання типу - це невідʼємний степінь двійки (1, 2, 4, 8, ...), що задає вимогу до його адреси. Кожен обʼєкт цього типу має розташовуватися за адресою, кратною його вирівнюванню.
Для примітивного типу вирівнювання зазвичай дорівнює його розміру, тож, наприклад, int32_t зазвичай має вирівнювання 4 байти.
Це означає, що int32_t потрібно розміщувати на межах, кратних 4 байтам.
Вирівнювання структури, однак, дорівнює максимальному вирівнюванню серед її полів.
Так, наприклад, struct example, визначена вище, мала б вирівнювання 8 байтів, бо саме таке вирівнювання має uint64_t.
Якщо вирівнювання поля структури менше за вирівнювання самої структури, після цього поля можуть вставлятися заповнювальні байти. Це гарантує, що наступні поля й структура загалом відповідають вимогам вирівнювання. На практиці це означає, що хоч ці заповнювальні байти й додаються до розміру структури, до її значення вони не входять:
struct example_3 {
char one_byte; // alignment of 1
int four_byte; // alignment of 4
};
// example_3 has alignment of 4 (the same as int)
// it occupies 8 bytes in space:
// 1 byte for field 'one_byte'
// 3 padding bytes (with undefined value)
// 4 bytes for field 'four_byte'
Зауважмо, що в структурі, визначеній вище, поле 'one_byte' має вирівнювання 1 і може розташовуватися за будь-якою адресою. Оскільки поле 'four_byte' має вирівнювання 4, поля в структурі теж мають дотримуватися меж, кратних 4 байтам. Це означає, що між будь-якими двома полями структури має бути кількість байтів, кратна 4. Позаяк 'one_byte' займає лише 1 байт, після нього вставляють 3 заповнювальні байти, щоб дотриматися цієї межі.
Сусідні поля групують, коли це можливо, щоб зменшити потрібну кількість заповнювальних байтів:
struct two_padding {
char one; // alignment of 1
char two; // alignment of 1
int three; // alignment of 4
};
// struct two_padding has alignment of 4 (the same as int)
// it occupies 8 bytes in space:
// 1 byte for field 'one'
// 1 byte for field 'two'
// 2 padding bytes
// 4 bytes for field 'three'
Це групування також має враховувати вирівнювання кожного поля:
struct one_padding {
char one; // alignment of 1
int16_t two; // alignment of 2
int three; // alignment of 4
};
// struct one_padding has alignment of 4 (the same as int)
// it occupies 8 bytes in space:
// 1 byte for field 'one'
// 1 padding byte
// 2 bytes for field 'two'
// 4 bytes for field 'three'
// Since 'two' has alignment of 2, it must be placed in a 2-byte boundary
// So, there must be 1 padding byte between 'one' and 'two'
Це означає, що зміна порядку полів у структурі іноді може призвести до різниці в її розмірі:
struct example_4 {
int64_t one;
short two;
int32_t three;
};
// example_4 has alignment of 8 (the same as int64_t)
// it occupies 16 bytes in space:
// 8 bytes for field 'one'
// 2 bytes for field 'two'
// 2 padding bytes
// 4 bytes for field 'three'
// two' and 'three' are adjacent and can be grouped to sum up 8 bytes with only 2 bytes of padding
struct example_5 {
int16_t one;
int64_t two;
unsigned int three;
};
// example_5 also has alignment of 8 (the same as int64_t)
// however, it occupies 24 bytes in space:
// 2 bytes for field 'one'
// 6 bytes for padding
// 8 bytes for field 'two'
// 4 bytes for field 'three'
// 4 padding bytes
// 'one' can't be grouped with 'two' because their sum would be higher than the struct's alignment
// 'two' and 'three' also can't be grouped, for the same reason
// so padding bytes must be inserted in full after each field with less than 8 bytes in alignment
Зауважмо, що вирівнювання масиву таке саме, як вирівнювання його базового типу:
struct array_example {
char string[10]; // alignment of 1
};
// array_example has alignment of 1 (it can be placed at any address)
// it occupies 10 bytes in space, one for each char in 'string'
Елементи масиву можна групувати так само, як якби вони не були частиною масиву:
struct group_array {
int16_t arr[3];
int8_t one_byte;
int32_t four_byte;
};
// group_array has alignment of 4 because this is the alignment of 'four_byte'
// it occupies 12 bytes in space:
// 4 bytes for arr[0] and arr[1]
// 2 bytes for arr[2]
// 1 byte for 'one_byte'
// 1 padding byte (arr[2] is grouped with 'one_byte')
// 4 bytes for 'four_byte'
Важливо враховувати всі можливі заповнювальні байти, коли ми звертаємося до структури в коді на асемблері.
Загалом значення передають у функції та повертають із них звичайним чином:
xmm.Структури ж - це окремий випадок. Як правило, усі поля структури займають суміжний простір у памʼяті, і їх варто розміщувати в одному регістрі, коли це можливо:
struct single_type {
int16_t first;
int16_t second;
int16_t third;
int16_t fourth;
};
// this struct occupies 8 bytes
// it is entirely passed in the same GPR register:
// the field 'first' is passed on the lowest 2 bytes
// each subsequent field is passed in the following 2 bytes
Якщо структура має поля з цілими значеннями, а також поля зі значеннями з плаваючою комою, кожні послідовні 8 байтів у структурі слід класифікувати окремо:
struct mixed_types {
double first;
int second;
int third;
};
// this struct occupies 16 bytes
// the field 'first' is passed in the first 8 bytes of a xmm register
// the fields 'second' and 'third' are passed in the same GPR register:
// the field 'second' is passed on the lowest 4 bytes
// the field 'third' is passed in the next 4 bytes
Якщо цілі значення та значення з плаваючою комою потрапляють в одні й ті самі 8 байтів, тип вважається цілочисельним, і весь восьмибайтовий блок (разом зі значенням з плаваючою комою) розміщується в GPR:
struct mixed_types {
int32_t integer;
float floating_point;
};
// this struct occupies 8 bytes
// it is entirely passed in the same GPR register:
// the field 'integer' is passed on the lowest 4 bytes
// the field 'floating_point' is passed on the next 4 bytes
Коли ми перетворюємо значення з плаваючою комою на ціле за допомогою cvtsi2ss або cvtsi2sd, це змінює організацію байтів, що лежать в основі.
Щоб перемістити байти як є з регістра xmm у GPR, не змінюючи бітового представлення, можна скористатися movd (для 32-бітних значень) і movq (для 64-бітних значень).
Однак структури розміром понад 16 байтів зазвичай розміщують у памʼяті, незалежно від типу кожного восьмибайтового блоку:
struct big_struct {
uint8_t first;
double second;
int64_t third;
};
// this struct occupies 24 bytes, including 7 padding bytes between 'first' and 'second'
// it is entirely passed in memory
Коли структуру передають як аргумент у функцію, використовується стільки регістрів, скільки потрібно. Якщо ж її розміщують у памʼяті, натомість використовується стек.
Схожі правила діють і для структур, які функція повертає.
Зазвичай вони повертаються спершу в rax і rdx (якщо значення цілочисельне) або в xmm0 і xmm1 (якщо значення з плаваючою комою).
Якщо ж структуру розміщують у памʼяті, той, хто викликає функцію, має зарезервувати для неї місце й передати адресу цього місця в памʼяті як неявний перший аргумент функції (у rdi).
Тоді функція зберігає структуру в цьому місці й повертає її адресу в rax.
У цій вправі ми організовуємо гонки між різними типами радіокерованих машин. Кожна машина має власні характеристики й бере участь у багатьох різних трасах у гонках протягом сезону турніру.
Функції в цій вправі оголошено у вихідному файлі C із наданими сигнатурами. Код мовою асемблера x86-64 має відповідати цим специфікаціям.
Уже визначено структуру car_t, яка представляє радіокеровану машину:
struct car_t {
char name[10];
int16_t speed;
float battery;
};
Щоб мати змогу створити радіокеровану машину, визначте функцію new_car мовою асемблера x86-64.
Поле battery завжди починається зі значення 100.0.
struct car_t new_car(short speed, const char name[]);
Траси представлено структурою track_t:
struct track_t {
enum surface_t surface;
size_t distance;
};
enum surface_t зберігає різні типи покриття для траси:
enum surface_t {
ASPHALT,
SAND,
ICE,
CLAY
};
Щоб мати змогу створити нову трасу, визначте функцію new_track мовою асемблера x86-64.
struct track_t new_track(enum surface_t surface, size_t distance);
У кожній гонці беруть участь до 6 машин, які проїжджають визначену кількість кіл трасою.
Гонки представлено структурою race_t:
struct race_t {
struct track_t track;
uint8_t num_of_laps;
struct car_t cars[6];
uint8_t num_of_running_cars;
};
Визначте функцію new_race мовою асемблера x86-64.
Спочатку для гонки немає підтверджених машин-учасниць:
struct race_t new_race(struct track_t track, uint8_t num_of_laps);
Коли власник машини просить, щоб вона взяла участь у гонці, її участь підтверджують, лише якщо список ще не заповнений.
Визначте функцію add_participant мовою асемблера x86-64.
Якщо в гонці є місце ще для одного учасника, машину додають до списку, оновлюючи лічильник, і її участь підтверджують.
Інакше організатори гонки повинні повідомити власника машини, що цього разу вона не може взяти участь.
bool add_participant(struct race_t *race, struct car_t car);
Гонки, заплановані на сезон, зберігають у структурі tournament_t:
struct tournament_t {
struct race_t races[20];
size_t num_of_races;
};
Визначте функцію add_race мовою асемблера x86, щоб додати гонку до масиву турніру й також оновити його лічильник.
Можна вважати, що в масиві races завжди буде достатньо місця для нової гонки.
void add_race(struct tournament_t *tournament, struct race_t race);
Зареєструйтеся на Exercism, щоб вивчати й опановувати x86-64 Assembly, а також 22 концепції130 вправ та справжнє наставництво від людей, і все це безкоштовно.