Sebesség kell

Sebesség kell

Tanulófeladat

Bevezetés

A C nyelvvel való együttműködés: structok

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

Igazítás

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.

Hívási konvenció

Á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ó:

  • Az egész értékeket, beleértve a memóriacímeket és az enumokat is, általános célú regiszterekben (GPR) adjuk át.
  • A lebegőpontos értékeket 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
Caution

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.

Utasítások

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.

Note

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.

1. Hozz létre egy távirányítós autót

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[]);

2. Hozz létre egy új pályát

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);

3. Hozz létre egy versenyeseményt

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);

4. Erősítsd meg egy autó részvételét a versenyen

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);

5. Hozz létre egy tornát

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);
Szerkesztés GitHubon A hivatkozás új ablakban vagy lapon nyílik meg
x86-64 Assembly Exercism

Készen állsz elkezdeni a(z) Sebesség kell feladatot?

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.