Ha a számrendszereket C-fejlesztőként már biztosan használod, ez az alfejezet nagyrészt ismétlés. A nyolc kérdést papíron, segédeszköz nélkül, legfeljebb tíz perc alatt válaszold meg, majd a megoldókulcs és az átugrási út alapján döntsd el, mit olvass el.
0x3C decimálisan, és mi a 8 bites bináris
alakja?0xF6 bitminta értéke előjel nélküli,
illetve kettes komplemens olvasatban?0x80 16 bitre kiterjesztve
nullakiterjesztéssel, illetve előjelkiterjesztéssel?200 + 100?100 + 100 eredményének bitmintája, és
mi ennek az előjeles olvasata?int x = INT_MAX; és
unsigned u = UINT_MAX; mellett mi a x + 1,
illetve az u + 1 státusza?Megoldókulcs és átugrási út:
| # | Helyes válasz | Ha hibáztál, olvasd el |
|---|---|---|
| 1 | 60; 0b0011_1100 |
Átváltás bináris, hexadecimális és decimális alak között |
| 2 | 4 bit; 8 jegy | Méretek és helyi értékek; Hexadecimális jelölés a gyakorlatban |
| 3 | 246, illetve −10 | A kettes komplemens előjeles olvasata |
| 4 | 0xFFFFFFFF |
A kettes komplemens előjeles olvasata |
| 5 | 0x0080, illetve 0xFF80 |
Nullakiterjesztés és előjelkiterjesztés |
| 6 | 44 (300 mod 256: körbefordulás) | Túlcsordulás és körbefordulás: levezetés 8 bitre |
| 7 | 0xC8, előjelesen −56 (előjeles túlcsordulás) |
Túlcsordulás és körbefordulás: levezetés 8 bitre |
| 8 | x + 1 definiálatlan viselkedés (előjeles túlcsordulás);
u + 1 definiáltan 0 |
A C szemantikája külön szabályrendszer |
Egy bináris fájlban 0xFF bájtot találsz. Ez 255, −1,
nyolc bekapcsolt jelzőbit, vagy egy gépi utasítás része? Önmagában egyik
értelmezés sem kötelező. A jelentést az adatformátum, a művelet és a
választott bitszélesség határozza meg.
Ez gyakorlati probléma: egy hibás előjelkiterjesztésből rossz tömbindex, egy túlcsorduló méretszámításból túl kicsi memóriaterület lehet. Ebben az alfejezetben egész számok bitmintája és számértéke között alakítunk át. Levezetjük a kettes komplemens ábrázolást és a véges szélességű összeadás korlátait.
Profil: I. rész, AArch64, A64 ISA, ARMv8-A; little-endian bájtsorrend. A matematikai példák gyakran 8 bitesek, mert így minden bit áttekinthető. Ez nem jelenti azt, hogy az A64 általános célú regiszterei 8 bitesek.
A bit egy bináris számjegy: értéke 0 vagy 1. Az alábbi méretneveket különböztetjük meg:
| Elnevezés | Méret | Megjegyzés |
|---|---|---|
| bit | 1 bit | Két lehetséges érték |
| félbájt (nibble) | 4 bit | Egy hexadecimális számjegy |
| bájt (byte) | 8 bit | A könyv ARM-célprofiljain |
| szó (word) | 32 bit | Az itt használt Arm-elnevezés |
| dupla szó (doubleword) | 64 bit | Két 32 bites szó |
A „szó” nem minden architektúrán jelent ugyanannyit. A64
ISA-környezetben az általános célú Wn regiszternézet 32
bites, az Xn nézet 64 bites. Ettől az Arm
terminológiájában a word még 32 bit marad. A C int
vagy long típusának méretét pedig a megvalósítás és az ABI
szabályai határozzák meg, nem a szó hétköznapi jelentése.
Egy bites, előjel nélküli (unsigned) szám értéke:
Itt a bitszélesség bitben, a jobbról számozott bithely, pedig 0 vagy 1; dimenzió nélküli egész szám. A legkisebb helyi értékű bit (least significant bit, LSB) indexe 0, a legnagyobbé (most significant bit, MSB) .
Például, 8 bites előjel nélküli olvasatban:
A számjegyek számától eltérő fogalom az érték: a 0b101
és a 0b00000101 előjel nélküli értéke egyaránt 5, de a
második jelölés egy 8 bites mezőt szemléltet.
A hexadecimális (hexadecimal) számrendszer alapja 16. Számjegyei 0–9, majd A–F; utóbbiak rendre a 10–15 értéket jelölik. Mivel , minden hexadecimális számjegy pontosan négy bitnek felel meg.
| Hexadecimális számjegy | Négybites alak | Előjel nélküli decimális érték |
|---|---|---|
0x2 |
0b0010 |
2 |
0x7 |
0b0111 |
7 |
0xA |
0b1010 |
10 |
0xD |
0b1101 |
13 |
0xF |
0b1111 |
15 |
Binárisból hexadecimálisra váltáskor jobbról négyes csoportokat képzünk:
0b0010_1101 → 0x2D;0b1101_0011 → 0xD3.Az aláhúzás itt csak olvasást segítő csoportosítás, nem a labor C-kódjának szintaxisa.
Hexadecimálisból decimálisra ugyanúgy helyi értékeket összegzünk:
Decimálisból binárisra ismételt, maradékos osztás használható:
| Osztandó | 2-vel osztás hányadosa | Maradék |
|---|---|---|
| 45 | 22 | 1 |
| 22 | 11 | 0 |
| 11 | 5 | 1 |
| 5 | 2 | 1 |
| 2 | 1 | 0 |
| 1 | 0 | 1 |
A maradékokat alulról felfelé olvasva
0b101101 adódik. Nyolc bitre nullákkal kiegészítve:
0b00101101.
Hexadecimális átváltáshoz 16-tal osztunk: 45 hányadosa 2, maradéka
13, ezért az eredmény 0x2D. Ezek az eljárások tetszőleges
nemnegatív egészre működnek; véges mezőbe tárolás előtt külön
ellenőrizni kell, hogy az érték belefér-e.
Ugyanannak az bites mintának a kettes komplemens (two’s complement) előjeles értéke:
az előjel nélküli, az előjeles, dimenzió nélküli érték; a bitszélesség. Ezzel egyenértékű, ha a legfelső bit helyi értékét negatívnak vesszük:
Nyolc biten ezért a legfelső bit súlya −128, nem +128.
| 8 bites minta | Előjel nélküli érték | Kettes komplemens előjeles érték |
|---|---|---|
0x00 = 0b00000000 |
0 | 0 |
0x7F = 0b01111111 |
127 | 127 |
0x80 = 0b10000000 |
128 | −128 |
0xD3 = 0b11010011 |
211 | −45 |
0xFF = 0b11111111 |
255 | −1 |
Az ábrázolható tartományok:
| Bitszélesség | Előjel nélküli tartomány | Kettes komplemens előjeles tartomány |
|---|---|---|
| bit | ||
| 8 bit | 0…255 | −128…127 |
| 16 bit | 0…65535 | −32768…32767 |
| 32 bit | 0…4294967295 | −2147483648…2147483647 |
Negatív szám kódolásakor előbb bitszélességet választunk. A −45 érték 8 bites mintája:
Ugyanezt adja a pozitív érték bitjeinek megfordítása és 1 hozzáadása:
0b00101101;0b11010010;0b11010011.Levezetés: az bites megfordítás értéket ad. Ehhez 1-et hozzáadva keletkezik, amelynek kettes komplemens olvasata .
A -0x2D matematikailag −45; a 0xD3
önmagában egy pozitív hexadecimális számjelölés. Utóbbi csak 8
bites kettes komplemens mintaként értelmezve jelenti
ugyanazt.
A tartomány aszimmetrikus: −128 ábrázolható 8 biten, +128 előjelesen nem. Ezért a legkisebb negatív érték negálása ugyanazon a bitszélességen nem adhat helyes, ábrázolható pozitív eredményt.
Szélesebb mezőre váltáskor meg kell választani, mivel töltjük ki az új felső biteket.
| Kiinduló 8 bites minta | 16 bites eredmény | Művelet | Megőrzött érték |
|---|---|---|---|
0x2D |
0x002D |
Mindkettő | 45 |
0xD3 |
0x00D3 |
Nullakiterjesztés | Előjel nélküli 211 |
0xD3 |
0xFFD3 |
Előjelkiterjesztés | Előjeles −45 |
Levezetés: negatív szám esetén az bites előjel nélküli minta , előjeles értéke . Ha bitre terjesztünk előjellel, az új felső egyesek értéke . Az új előjeles olvasat tehát:
Az érték változatlan. A kiterjesztés módját tehát nem a hexadecimális számjegyek „kinézete”, hanem a kívánt értelmezés dönti el.
A túlcsordulás (overflow) azt jelenti, hogy a pontos matematikai eredmény kívül esik a választott értéktartományon. A körbefordulás (wraparound) a véges szélességű, modulo szerinti eredményképzés.
Ha egy összeadásból csak az alsó bitet tartjuk meg, akkor:
Itt és az operandusminták előjel nélküli értéke, a tárolt minta előjel nélküli értéke; valamennyi dimenzió nélküli egész.
Nyolc biten az eredményt 256-tal vett maradékra korlátozzuk.
| Operandusminták | Pontos előjel nélküli összeg | Pontos előjeles összeg | Alsó 8 bit | Előjel nélküli túlcsordulás | Előjeles túlcsordulás |
|---|---|---|---|---|---|
0xFF + 0x01 |
256 | −1 + 1 = 0 | 0x00 |
Igen | Nem |
0x7F + 0x01 |
128 | 127 + 1 = 128 | 0x80 |
Nem | Igen |
0x80 + 0xFF |
383 | −128 − 1 = −129 | 0x7F |
Igen | Igen |
Az első sorban az előjel nélküli összeg kilenc bitet igényel:
0b1_00000000. A kilencedik bit elhagyása után nulla
marad.
A második sorban nincs kilencedik bit, mégis előjeles túlcsordulás történik: két nemnegatív operandusból negatívnak olvasott eredmény keletkezik.
Ebből következik az előjeles összeadás szabálya:
Utóbbi oka, hogy a negatív operandus csökkenti a pozitív értéket, és fordítva: az összeg nem kerülhet az operandusok által megengedett szélsőértékeken túlra.
Kivonásnál az előjeles túlcsordulás akkor lehetséges, ha az operandusok előjele különböző, és az eredmény előjele eltér a kisebbítendőétől. Például 8 biten nem ábrázolható előjelesen.
A gépi alsó-bites eredményből nem következik, hogy minden hasonló C-kifejezés szabályos:
int típusban végzi el.Így két uint8_t összeadása az AArch64 laborprofilban nem
automatikusan „8 bites összeadás”. A 255 + 1 összeg int
típusban 256; a későbbi uint8_t konverzió eredménye lesz
0.
A hexadecimális alak tömör, de nem jelöl önmagában adattípust.
| Felhasználás | Példa | Helyes olvasási kérdés |
|---|---|---|
| Bájtcímhez viszonyított eltolás | 0x20 bájt |
32 bájttal arrébb? |
| 8 bites bitmaszk (bit mask) | 0xF0 |
A felső négy bitet jelöli? |
| A64 ISA utasításkódolás | 0xD503201F |
Ez az A64 NOP kódolása? |
A címeltolás itt nem konkrét memóriacím: csak már érvényes objektum határain belül alkalmazható. A maszkot nem előjeles számként, az utasításkódot pedig nem aritmetikai adatként értelmezzük.
A szám leírása és memóriabeli bájtsorrendje is különbözik. A 32 bites
0x12345678 minta little-endian tárolásakor növekvő
bájteltolás mellett 0x78, 0x56,
0x34, 0x12 következik. Ez nem változtatja meg
a szám helyi értékeit. A részletes tárgyalás: Bájtsorrend: endianness és miért releváns a
határon.
Munkakönyvtár: ~/arm-labor/a64/a002/ —
az alfejezet minden laborfájlja ide kerül, és minden parancs innen
futtatandó (a könyvtárrendet a Labor-alapkörnyezet alfejezet írja le).
mkdir -p ~/arm-labor/a64/a002
cd ~/arm-labor/a64/a002A program tudatosan szélesebb típusokkal számítja ki a pontos összegeket, majd külön képezi a 8 bites eredményt. Így nem idéz elő előjeles C-túlcsordulást.
A lab_signed8 nem támaszkodik tartományon kívüli,
előjeles típusra történő konverzióra. A labor C17-et használ; a bináris
0b alakot kiírja, a forrás konstansait azonban
hexadecimálisan vagy decimálisan adja meg.
Fájl: lab-bits.c. C17, AArch64 célprofil;
little-endian.
#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
static int32_t lab_signed8(uint8_t x)
{
int32_t u = x;
return u < 128 ? u : u - 256;
}
static void lab_show8(uint8_t x)
{
printf("0b");
for (int bit = 7; bit >= 0; --bit)
putchar((((unsigned)x >> bit) & 1u) ? '1' : '0');
printf(" hex=0x%02x unsigned=%u signed=%" PRId32 "\n",
(unsigned)x, (unsigned)x, lab_signed8(x));
}
static int lab_add8(uint8_t a, uint8_t b, uint8_t expected,
int expected_uo, int expected_so)
{
unsigned wide_u = (unsigned)a + (unsigned)b;
int32_t wide_s = lab_signed8(a) + lab_signed8(b);
uint8_t result = (uint8_t)wide_u;
int uo = wide_u > 255u;
int so = wide_s < -128 || wide_s > 127;
printf("add 0x%02x + 0x%02x -> 0x%02x UO=%d SO=%d\n",
(unsigned)a, (unsigned)b, (unsigned)result, uo, so);
return result == expected
&& uo == expected_uo
&& so == expected_so;
}
int main(void)
{
lab_show8(0x2d);
lab_show8(0xd3);
lab_show8(0xff);
uint8_t small = 0xd3;
uint32_t zero = small;
int32_t sign = lab_signed8(small);
printf("zero=0x%08" PRIx32 " sign=0x%08" PRIx32 "\n",
zero, (uint32_t)sign);
int ok = zero == 211u && sign == -45;
ok &= lab_add8(0xff, 0x01, 0x00, 1, 0);
ok &= lab_add8(0x7f, 0x01, 0x80, 0, 1);
ok &= lab_add8(0x80, 0xff, 0x7f, 1, 1);
puts(ok ? "PASS" : "FAIL");
return ok ? 0 : 1;
}A negatív sign érték uint32_t típusra
alakítása definiált, modulo
szerinti konverzió; ezért alkalmas a kívánt 32 bites minta hexadecimális
megjelenítésére.
Cél: qemu-user, qemu-aarch64; host: Ubuntu 24.04
x86_64. A parancsok a cross-eszközláncot és a statikus
linkeléshez szükséges céloldali C-könyvtárat feltételezik. Telepítésük
később, a Labor-alapkörnyezet: eszközlánc telepítése
és verifikáció alfejezetben szerepel.
# C -> A64 ISA assembly; explicit little-endian célprofil.
aarch64-linux-gnu-gcc -std=c17 -O0 -g \
-Wall -Wextra -Wconversion \
-march=armv8-a -mlittle-endian \
-S lab-bits.c -o lab-bits.s
# A64 ISA assembly -> objektum.
aarch64-linux-gnu-as -EL -march=armv8-a \
lab-bits.s -o lab-bits.o
# Statikus AArch64 Linux-program.
aarch64-linux-gnu-gcc -static lab-bits.o -o lab-bits
# Futtatás: qemu-user.
qemu-aarch64 ./lab-bits
printf 'exit=%d\n' "$?"Várt, levezetett eredmények; a kód itt nem volt futtatva:
| Ellenőrzés | Várt adat |
|---|---|
0x2D |
0b00101101, előjel nélkül 45, előjelesen 45 |
0xD3 |
0b11010011, előjel nélkül 211, előjelesen −45 |
0xFF |
0b11111111, előjel nélkül 255, előjelesen −1 |
| Kiterjesztések | zero=0x000000d3 sign=0xffffffd3 |
| Első összeadás | 0x00, UO=1, SO=0 |
| Második összeadás | 0x80, UO=0, SO=1 |
| Harmadik összeadás | 0x7F, UO=1, SO=1 |
| Önteszt és kilépés | PASS, majd exit=0 |
Az UO és SO a program által kiszámított, 8
bites előjel nélküli, illetve előjeles túlcsordulási feltétel.
Nem a processzor állapotjelzőinek kiolvasása.
| Tünet | Hipotézis | Ellenpróba |
|---|---|---|
| A várt −45 helyett 211 jelenik meg | Rossz előjeles értelmezés | A 0xD3 mintából vond ki a 256-ot |
Szélesítés után 0x00D3 látható |
Nullakiterjesztést alkalmaztál | Vizsgáld meg a forrás típusát és a kívánt értéket |
| „Nincs kilencedik bit, tehát nincs túlcsordulás” | Összekevered a két értéktartományt | Próbáld ki a 0x7F + 0x01 esetet |
| A fordító nem 8 bites összeadást készít | Figyelmen kívül maradt az egészpromóció | Vizsgáld meg a generált kódot |
Cél: qemu-user programjának statikus vizsgálata, A64 ISA disassembly:
aarch64-linux-gnu-objdump -d --disassemble=lab_add8 ./lab-bitsEz a parancs nem futtatja a programot. Egyelőre azt figyeld meg, hogy a C-szintű bájtos adattípus nem követel minden köztes művelethez bájtszélességű utasítást.
Hibakereséskor előbb rögzítsd a bitmintát, a bitszélességet és a kívánt olvasatot. Ezután csak egy értelmezést vagy konverziót módosíts, majd ismételd meg az öntesztet. Pusztán más kiírási formátum választása nem javítja meg a hibás számítást.
(magyarázó, alapszint — cél: megértés-ellenőrzés) Alakítsd át a 173 előjel nélküli értéket 8 bites bináris és hexadecimális alakra! Mi ugyanezen minta előjeles értéke? Add meg a −83 érték 8 bites kódját és 16 bites előjelkiterjesztését!
(implementációs, középszint — cél: rutinépítés)
Egészítsd ki a labort a 0xC8 + 0x64 és a
0x9C + 0xCE esetekkel! Előbb kézzel határozd meg az
eredménymintát és a két túlcsordulási feltételt.
(hibakeresési, középszint — cél:
megértés-ellenőrzés) A lab_signed8 törzsét
ideiglenesen cseréld return (int32_t)x; sorra. Mely
eredmények romlanak el? Fordítsd újra ugyanazokkal a parancsokkal,
vizsgáld meg a kimenetet és az objdump eredményét, majd
egyetlen javítással állítsd helyre a programot.
1. megoldás — Segítség. Bontsd 173-at kettőhatványok összegére (128+…), majd a kapott 8 bites mintát olvasd le a kettes komplemens képlet szerint: ha a legfelső bit 1, vond ki 256-ot.
1. megoldás — Teljes megoldás.
,
ezért a minta 0b10101101, azaz 0xAD. Nyolc
bites kettes komplemens olvasata
.
A −83 kódja tehát 0xAD, 16 bites előjelkiterjesztése
0xFFAD. A 0x00AD nem −83-at, hanem 173-at
jelentene.
2. megoldás — Segítség. Számítsd ki mindkét esetben külön az előjel nélküli és az előjeles pontos összeget, majd vesd össze a 8 bitbe még beférő tartománnyal (0–255, illetve −128–127) — a túlcsordulás pontosan akkor áll fenn, ha a pontos érték kilóg ebből.
2. megoldás — Teljes megoldás. Az első esetben az
előjel nélküli összeg
,
maradéka 44, vagyis 0x2C: UO=1. Előjelesen
,
ezért SO=0.
A második esetben
,
maradéka 106, vagyis 0x6A: UO=1. Előjelesen
,
ami kisebb −128-nál, ezért SO=1. A main
öntesztje így bővíthető:
/* C17, AArch64 célprofil; beszúrás a végső puts elé. */
ok &= lab_add8(0xc8, 0x64, 0x2c, 1, 0);
ok &= lab_add8(0x9c, 0xce, 0x6a, 1, 1);3. megoldás — Segítség. A (int32_t)x
egyszerű konverzió nem vizsgálja, hogy a 8 bites minta legfelső bitje
1-e — gondold végig, melyik bemenet (0x2d vagy
0xd3/0xff) ad emiatt eltérő eredményt az
eredeti függvényhez képest.
3. megoldás — Teljes megoldás. A hibás változat a
uint8_t numerikus értékét őrzi meg: 0xD3
esetén 211-et, 0xFF esetén 255-öt ad. Emiatt az előjeles
kiírás, a kiterjesztés és több túlcsordulási ellenőrzés hibás. Várt
eredmény: FAIL, kilépési állapot 1. Az objdump
a megváltozott megvalósítást mutatja, de az értelmezési hiba oka már a
típusokból következik. A javítás a 128-tól kezdődő mintákból 256
kivonásának visszaállítása.
A bitszélesség és az értelmezés együtt határozza meg egy bitminta számértékét. A hexadecimális jelölés négybites csoportokat tömörít, de önmagában nem mond semmit az előjelről vagy az adat szerepéről. Kettes komplemensben a legfelső bit negatív helyi értékű; ezért a pozitív és negatív tartomány nem szimmetrikus. Szélesítéskor a nullakiterjesztés az előjel nélküli, az előjelkiterjesztés az előjeles értéket őrzi meg. A labor szélesebb köztes típusokkal választotta külön a pontos összeget és a tárolt mintát. A gépi körbefordulásból nem következik az előjeles C-túlcsordulás megengedettsége.
A linkek és a dokumentumverziók közös állapota az Olvasói útmutató forrásállapot-táblájában van; itt csak az alfejezet saját megállapításai szerepelnek.
developer.arm.com → support.arm.com
átirányítás megerősített); a JavaScript-alapú megjelenítés miatt a
pontos fejezetszám automatikus lekéréssel nem volt kinyerhető.master ághoz tartozik, nem egy konkrét kiadott
QEMU-verzióhoz; az Ubuntu 24.04 csomagolt verziója ettől eltérhet.Ellenőrzési hiányok, amelyek a fenti ellenőrzés után is ténylegesen fennmaradnak: a helyi cross-libc statikus linkelhetősége, a ténylegesen telepített eszközláncverziók (gcc, binutils, QEMU), és a bemutatott laborkimenet tényleges, lefuttatott reprodukálása — ezek egyike sem történt meg ebben a munkafolyamatban.