• Nem Talált Eredményt

3. Alapműveletek és kifejezések

3.7. Bitműveletek

c = (1 > 2 ? 4 : 7) * (2 < 3 ? 4 : 7) ; //28

3.7. Bitműveletek

Régebben a számítógépek igen kevés memóriával rendelkeztek, így nagyon értékesek voltak azok a megoldások, amelyek a legkisebb címezhető egységen, a bájton belül is lehetővé tették több adat tárolását és feldolgozását. Bitműveletek segítségével egy bájtban akár 8 darab logikai értéket is elhelyezhetünk.

Napjainkban ez a szempont csak igen ritkán érvényesül, és inkább a program érthetőségét tartjuk szem előtt.

Van mégis egy terület, ahol a bitműveleteknek napjainkban is hasznát vehetjük, nevezetesen a különböző hardverelemek, mikrovezérlők programozása. A C++ nyelv tartalmaz hat operátort, amelyekkel különböző bitenkénti műveleteket végezhetünk előjeles és előjel nélküli egész adatokon.

3.7.1. Bitenkénti logikai műveletek

A műveletek első csoportja, a bitenkénti logikai műveletek, lehetővé teszik, hogy biteket teszteljünk, töröljünk vagy beállítsunk:

Operátor Művelet

~ 1-es komplemens, bitenkénti tagadás

& bitenkénti ÉS

| bitenkénti VAGY

^ bitenkénti kizáró VAGY

A bitenkénti logikai műveletek működésének leírását az alábbi táblázat tartalmazza, ahol a 0 és az 1 számjegyek a törölt, illetve a beállított bitállapotot jelölik.

a b a & b a | b a ^ b ~a

0 0 0 0 0 1

0 1 0 1 1 1

1 0 0 1 1 0

1 1 1 1 0 0

A számítógép hardverelemeinek alacsony szintű vezérlése általában bizonyos bitek beállítását, törlését, illetve kapcsolását igényli. Ezeket a műveleteket összefoglaló néven „maszkolásnak” nevezzük, mivel minden egyes művelethez megfelelő bitmaszkot kell készítenünk, amellyel aztán logikai kapcsolatba hozva a megváltoztatni kívánt értéket, végbemegy a kívánt bitművelet.

Mielőtt sorra vennénk a szokásos bitműveleteket, meg kell ismerkednünk az egész adatelemek bitjeinek sorszámozásával. A bitek sorszámozása a bájton a legkisebb helyértékű bittől indulva 0-val kezdődik, és balra haladva növekszik. Több bájtból álló egészek esetén azonban tisztáznunk kell a számítógép processzorában alkalmazott bájtsorrendet is.

A Motorola 68000, SPARC, PowerPC stb. processzorok által támogatott „nagy a végén” (big-endian) bájtsorrend esetén a legnagyobb helyiértékű bájt (MSB) a memóriában a legalacsonyabb címen tárolódik, míg az eggyel kisebb helyiértékű a következő címen és így tovább.

Ezzel szemben a legelterjedtebb Intel x86 alapú processzorcsalád tagjai a „kicsi a végén” (little-endian) bájtsorrendet használják, amely szerint legkisebb helyiértékű bájt (LSB) helyezkedik el a legalacsonyabb címen a memóriában.

A hosszú bitsorozatok elkerülése érdekében a példáinkban unsignedshort int típusú adatelemeket használunk.

Nézzük meg ezen adatok felépítését és tárolását mindkét bájtsorrend szerint! A tárolt adat legyen 2012, ami hexadecimálisan 0x07DC!

big-endian bájtsorrend:

little-endian bájtsorrend:

(A példákban a memóriacímek balról jobbra növekednek.) Az ábrán jól látható, hogy a hexadecimális konstans értékek megadásakor az első, „nagy a végén” formát használjuk, ami megfelel a 16-os számrendszer matematikai értelmezésének. Ez nem okoz gondot, hisz a memóriában való tárolás a fordítóprogram feladata.

Ha azonban az egész változókat bájtonként kívánjuk feldolgozni, ismernünk kell a bájtsorrendet. Könyvünk további részeiben a második, a „kicsi a végén” forma szerint készítjük el a példaprogramjainkat, amelyek azonban az elmondottak alapján az első tárolási módszerre is adaptálhatók.

Az alábbi példákban az unsigned short int típusú 2525 szám 4. és 13. bitjeit kezeljük:

unsigned short int x = 2525; // 0x09dd

Művelet Maszk C++ utasítás Eredmény

Bitek beállítása 0010 0000 0001 0000 x = x | 0x2010; 0x29dd

Bitek törlése 1101 1111 1110 1111 x = x & 0xdfef; 0x09cd

Bitek negálása (kapcsolása)

0010 0000 0001 0000 x = x ^ 0x2010;

x = x ^ 0x2010;

0x29cd (10701) 0x09dd (2525)

Az összes bit negálása 1111 1111 1111 1111 x = x ^ 0xFFFF; 0xf622

Az összes bit negálása x = ~x; 0xf622

Felhívjuk a figyelmet a kizáró vagy operátor (^) érdekes viselkedésére. Ha ugyanazzal a maszkkal kétszer végezzük el a kizáró vagy műveletet, akkor visszakapjuk az eredeti értéket, esetünkben a 2525-öt. Ezt a működést felhasználhatjuk két egész változó értékének segédváltozó nélküli felcserélésére:

int m = 2, n = 7;

m = m ^ n;

n = m ^ n;

m = m ^ n;

Nehezen kideríthető programhibához vezet, ha a programunkban összekeverjük a feltételekben használt logikai műveleti jeleket (!, &&, ||) a bitenkénti operátorokkal (~, &, |).

3.7.2. Biteltoló műveletek

A bitműveletek másik csoportjába, a biteltoló (shift) operátorok tartoznak. Az eltolás balra (<<) és jobbra (>>) egyaránt elvégezhető. Az eltolás során a bal oldali operandus bitjei annyiszor lépnek balra (jobbra), amennyi a jobb oldali operandus értéke.

Balra eltolás esetén a felszabaduló bitpozíciókba 0-ás bitek kerülnek, míg a kilépő bitek elvesznek. A jobbra eltolás azonban figyelembe veszi, hogy a szám előjeles vagy sem. Előjel nélküli (unsigned) típusok esetén balról 0-ás bit lép be, míg előjeles (signed) számoknál 1-es bit. Ez azt jelenti, hogy a jobbra való biteltolás előjelmegőrző.

short int x;

Értékadás Bináris érték Művelet Eredmény decimális (hexadecimális)

bináris

x = 2525; 0000 1001 1101 1101 x = x << 2; 10100 (0x2774)

0010 0111 0111 0100

x = 2525; 0000 1001 1101 1101 x = x >> 3; 315 (0x013b)

0000 0001 0011 1011

x = -2525; 1111 0110 0010 0011 x = x >> 3; -316 (0xfec4)

1111 1110 1100 0100 Az eredményeket megvizsgálva láthatjuk, hogy az 2 bittel való balra eltolás során az x változó értéke négyszeresére (22) nőtt, míg három lépéssel jobbra eltolva, x értéke nyolcad (23) részére csökkent. Általánosan is megfogalmazható, hogy valamely egész szám bitjeinek balra tolása n lépéssel a szám 2n értékkel való megszorzását eredményezi. Az m bittel való jobbra eltolás pedig 2m értékkel elvégzett egész osztásnak felel meg.

Megjegyezzük, hogy egy egész szám 2n-nel való szorzásának/osztásának ez a leggyorsabb módja.

Az alábbi példában a beolvasott 16-bites egész számot két bájtra bontjuk:

short int num;

unsigned char lo, hi;

// A szám beolvasása

cout<<"\nKerek egy egesz szamot [-32768,32767] : ";

cin>>num;

// Az alsó bájt meghatározása maszkolással lo=num & 0x00FFU;

// Az felső byte meghatározása biteltolással hi=num >> 8;

Az utolsó példában felcseréljük a 4-bájtos, int típusú változó bájtsorrendjét:

int n =0x12345678U;

n = (n >> 24) | // az első bájtot a végére mozgatjuk, ((n << 8) & 0x00FF0000U) | // a 2. bájtot a 3. bájtba, ((n >> 8) & 0x0000FF00U) | // a 3. bájtot a 2. bájtba, (n << 24); // az utolsó bájtot pedig az elejére.

cout <<hex << n <<endl; // 78563412

3.7.3. Bitműveletek az összetett értékadásban

A C++ mind az öt kétoperandusú bitművelethez összetett értékadás művelet is tartozik, melyekkel a változók értéke könnyebben módosítható.

Operátor Műveleti jel Használat Művelet

Balra eltoló értékadás <<= x <<= y x bitjeinek eltolása balra y

bittel,

Operátor Műveleti jel Használat Művelet

Jobbra eltoló értékadás >>= x >>= y x bitjeinek eltolása jobbra y bittel,

Bitenkénti VAGY

értékadás

|= x |= y x új értéke: x | y,

Bitenkénti ÉS értékadás &= x &= y x új értéke: x & y,

Bitenkénti kizáró VAGY értékadás

^= x ^= y x új értéke: x ^ y,

Fontos megjegyeznünk, hogy a bitműveletek eredményének típusa legalább int vagy az int-nél nagyobb egész típus, a bal oldali operandus típusától függően. Biteltolás esetén a lépések számát ugyan tetszőlegesen megadhatjuk, azonban a fordító a típus bitméretével képzett maradékát alkalmazza az eltoláshoz. Például a 32-bites int típusú változók esetében az alábbiakat tapasztalhatjuk:

unsigned z;

z = 0xFFFFFFFF, z <<= 31; // z ⇒ 80000000 z = 0xFFFFFFFF, z <<= 32; // z ⇒ ffffffff z = 0xFFFFFFFF, z <<= 33; // z ⇒ fffffffe