A C nyelvben a struct egy összetett típus, amely különböző mezőkből áll. Minden mezőnek saját típusa és mérete van.
Például a következő egy három egész számból álló típust definiál, kettő 4 bájtosból és egy 8 bájtosból:
struct example {
int32_t first_four_byte_integer;
uint32_t second_four_byte_integer;
uint64_t eight_byte_integer;
};
Egy struct tartalmazhat más structokat is:
struct example_2 {
struct example first;
struct example second;
};
Ha a struct egyik mezője tömb, akkor a tömb számára lefoglalt összes bájt a struct része. Ha viszont a mező csak egy mutató a tömbre, akkor a tömböt máshol kell tárolni:
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
Egy típus igazítása egy nemnegatív kettőhatvány (1, 2, 4, 8, ...), amely a címével szemben támasztott követelményt fejezi ki. Az adott típus minden objektumának olyan címen kell elhelyezkednie, amely az igazításának többszöröse.
Egy primitív típus esetében az igazítás általában megegyezik a méretével, így például egy int32_t igazítása jellemzően 4 bájt.
Ez azt jelenti, hogy az int32_t-t 4 bájtos határokon kell lefoglalni.
Egy struct igazítása viszont a mezői közül a legnagyobb igazítás.
Így például a fent definiált struct example igazítása 8 bájt lenne, mivel ez a uint64_t igazítása.
Ha egy struct mezőjének igazítása kisebb, mint a structé, akkor a mező után kitöltő bájtok szúrhatók be. Ez biztosítja, hogy a következő mezők és maga a struct is megfeleljen az igazítási követelményeknek. A gyakorlatban ez azt jelenti, hogy bár ezek a kitöltő bájtok hozzájárulnak a struct méretéhez, a struct értékében nem játszanak szerepet:
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'
Vegyük észre, hogy a fent definiált structban a 'one_byte' mező igazítása 1, és bármely címen elhelyezhető. Mivel a 'four_byte' mező igazítása 4, a struct mezőinek is tiszteletben kell tartaniuk a 4 bájtos határokat. Ez azt jelenti, hogy a struct bármely két mezője között a bájtok számának 4 többszörösének kell lennie. Mivel a 'one_byte' csak 1 bájtot foglal el, ezért utána 3 kitöltő bájtot szúrunk be, hogy betartsuk ezt a határt.
A szomszédos mezőket lehetőség szerint csoportosítjuk, hogy a lehető legkevesebb kitöltő bájt legyen szükséges:
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'
Ennek a csoportosításnak is tiszteletben kell tartania az egyes mezők igazítását:
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'
Ez azt jelenti, hogy a struct mezőinek átrendezése néha a méretének megváltozásához vezethet:
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
Vegyük észre, hogy egy tömb igazítása megegyezik az alapul szolgáló típusa igazításával:
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'
A tömb elemei ugyanúgy csoportosíthatók, mintha nem egy tömb részei lennének:
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'
Fontos számításba venni a lehetséges kitöltő bájtokat, amikor assemblyből érsz el egy structot.
Általánosságban az értékek úgy adódnak át a függvényeknek, és úgy térnek vissza belőlük, ahogy az elvárható:
xmm regiszterekben adjuk át.A structok viszont különleges esetet jelentenek. Ökölszabályként a struct összes mezője szomszédos helyet foglal el a memóriában, és lehetőség szerint egyetlen regiszterbe kell kerülniük:
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
Ha egy structnak vannak egész értékű mezői és lebegőpontos értékű mezői is, akkor a struct minden egymást követő 8 bájtját külön kell osztályozni:
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
Ha egészek és lebegőpontos értékek ugyanabban a 8 bájtban vannak, akkor a típus egész, és a teljes 8 bájt (a lebegőpontos értékkel együtt) egy GPR-be kerül:
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
Amikor cvtsi2ss vagy cvtsi2sd használatával lebegőpontos értékből egésszé alakítasz, az megváltoztatja a mögöttes bájtok elrendezését.
A movd (32 bites értékekhez) és a movq (64 bites értékekhez) segítségével nyers bájtokat mozgathatsz egy xmm regiszterből egy GPR-be anélkül, hogy megváltozna a bitreprezentáció.
A 16 bájtnál nagyobb méretű structok viszont általában a memóriába kerülnek, függetlenül az egyes 8 bájtok típusától:
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
Amikor egy structot argumentumként adunk át egy függvénynek, annyi regisztert használ, amennyi szükséges. Ha a memóriába kerül, akkor helyette a vermet használjuk.
Hasonló szabályok vonatkoznak a függvényből visszaadott structokra is.
Ezeket általában először a rax és rdx (egész érték esetén), illetve az xmm0 és xmm1 (lebegőpontos érték esetén) regiszterekben adjuk vissza.
Ha azonban a memóriába kerül, akkor a hívónak helyet kell fenntartania számára, és ennek a memóriahelynek a címét implicit első argumentumként kell átadnia a függvénynek (rdi).
A függvény ezután ebben a helyben tárolja a structot, és a címét a rax-ban adja vissza.
Ebben a feladatban különféle típusú távirányítós autók közötti versenyeket fogsz megszervezni. Minden autónak megvannak a maga jellemzői, és a torna szezonja során számos különböző pályán fut versenyeken.
A feladat függvényei egy C forrásfájlban vannak deklarálva a megadott szignatúrákkal. Az x86-64-assembly kódodnak követnie kell ezeket az előírásokat.
Definiáltál egy car_t struktúrát, amely egy távirányítós autót reprezentál:
struct car_t {
char name[10];
int16_t speed;
float battery;
};
A new_car függvény x86-64 assemblyben való definiálásával tedd lehetővé egy távirányítós autó létrehozását.
A battery mező mindig a 100.0 értékkel kezdődik.
struct car_t new_car(short speed, const char name[]);
A pályákat egy track_t struktúra reprezentálja:
struct track_t {
enum surface_t surface;
size_t distance;
};
Az enum surface_t a pálya különböző burkolatait tartja nyilván:
enum surface_t {
ASPHALT,
SAND,
ICE,
CLAY
};
A new_track függvény x86-64 assemblyben való definiálásával tedd lehetővé egy új pálya létrehozását.
struct track_t new_track(enum surface_t surface, size_t distance);
Minden versenyben legfeljebb 6 autó vesz részt, amelyek meghatározott számú kört futnak egy pályán.
A versenyeket egy race_t struktúra reprezentálja:
struct race_t {
struct track_t track;
uint8_t num_of_laps;
struct car_t cars[6];
uint8_t num_of_running_cars;
};
Definiálj egy new_race függvényt x86-64 assemblyben.
A kezdetkor még egyetlen autó részvétele sincs megerősítve a versenyre:
struct race_t new_race(struct track_t track, uint8_t num_of_laps);
Amikor egy autó tulajdonosa kéri, hogy az részt vegyen egy versenyen, a részvétele csak akkor erősíthető meg, ha a lista még nem tele.
Definiálj egy add_participant függvényt x86-64 assemblyben.
Ha van hely még egy résztvevőnek a versenyben, az autó bekerül a listára, a számláló frissül, és a részvétele megerősítést nyer.
Ellenkező esetben a verseny szervezőinek tájékoztatniuk kell az autó tulajdonosát, hogy ezúttal nem tud részt venni.
bool add_participant(struct race_t *race, struct car_t car);
A szezonra ütemezett versenyeket egy tournament_t struktúrában tartod nyilván:
struct tournament_t {
struct race_t races[20];
size_t num_of_races;
};
Definiálj egy add_race függvényt x86-assemblyben, amely hozzáad egy versenyt a torna tömbjéhez, és frissíti a számlálóját is.
Feltételezheted, hogy a races tömbben mindig lesz elég hely az új verseny számára.
void add_race(struct tournament_t *tournament, struct race_t race);
Iratkozz fel az Exercism-re, hogy megtanuld és elsajátítsd a(z) x86-64 Assembly nyelvet 22 fogalom130 feladat segítségével, valódi emberi mentorálással, mindez ingyen.