Ranije smo pričali o logičkim operatorima.

Logički operatori (kao &&, ||) su takvi da rade sa celokupnim brojevima između kojih se nalaze, bez obzira od koliko bitova se sastoje. Operacije koje se vrše mogu da detektuju vrednosti:

  • 0 (kada su svi bitovi 0) što znači "false" - netačno
  • različito od 0 (kada je bar jedan bit postavljen na 1), što znači "true" - tačno

Znači, ako pogledate kod koji je dat u nastavku, promenljivoj j će biti dodeljena 1 ako i nije nula, a u suprotnom će biti 0 (zašto?).

int i,j; 
j = !!i;

Postoje 4 operatora sa kojima se mogu raditi operacije sa pojedinačnim bitovima. Termin koji je na engleskom za te operatore je  "bitwise operators".

Operatori za rad sa bitovima::

  • & (ampersand) - logičko I na nivou bita (engl. bitwise conjunction)
  • | (bar) -  logičko ILI na nivou bita (engl.bitwise disjunction)
  • ~ (tilde) -  negacija na nivou bita (engl.bitwise negation)
  • ^ (caret) -  isključivo ILI na nivou bita , drugi naziv je ekskluzivno ILI na nivou bita (engl.bitwise exclusive or)

Evo kako to funkcioniše:

  • & zahteva tačno dve jedinice da bi se dobilo "1" kao rezultat
  • zahteva bar jedno "1" da bi se dobilo "1" kao rezultat
  • ^ zahteva samo jednu "1" da bi se dobilo  "1" kao rezultat
  • ~ (unarni operator) zahteva "0" da bi se dobilo  "1" kao rezultat

Napomena: argumenti za ove operatore moraju da budu celobrojni (bez obzira da li je int, long, short ili char); ne sme da se koristi float.

Razlika između operacija sa logičkim i sa operatorima na nivou bita je jeko bitna: za logičke operatore nije bitno šta je u kom bitu vrednosti sa kojima rade, potrebna im je samo krajnja celobrojna vrednost sa kojom rade.

Operatori koi rade na nivou bita rade sa svakim bitom zasebno.

Ako pretpostavimo da promenljiva tipa int u memoriji zauzima 32 bita, u nastavku ćemo prikazati primere u kojima se jasno vidi razlika između logičkih operacija i operacija na nivou bita.

Deklarisaćemo dve promenljive:

int i = 15, j = 22;

Ako pretpostavimo da se int promenljive smeštaju  u memoriju u 32 bita, ove promenljive će u memoriji biti sačuvane kao:

i: 00000000000000000000000000001111
j: 00000000000000000000000000010110

Ako se radi sledeća operacija:
int log = i && j;

u pitanju je logičko I. Na koji način će se doći do rezultata? Obe promenljive nisu jednake nuli, tako da će se u logičkoj operaciji računati kao "true".

Ako se podsetimo kako radi operator && , možemo da zaključimo da će rezultat prethodne operacije biti  "true" i da će to biti ceo broj jednak 1. To znači da će promenljiva log iz prethodnog primera, bit po bit, izgledati ovako:

log: 00000000000000000000000000000001

A u sledećem primeru ćemo izvršiti operaciju na nivou bita:

int bit = i & j;

Operator & će da vrši operaciju nad svakim parom bitova pojedinačno, tako da će rezultat biti:

i: 00000000000000000000000000001111
j: 00000000000000000000000000010110

& ---------------------------------------------------------

bit: 00000000000000000000000000000110

Celobrojna vrednost ovih bitova je 6.

Sledeći primer prikazuje kako se vrši negacija. Prvo logička negacija:

int logneg = !i;

Promenljiva logneg će biti postavljena na 0 tako da će svi bitovi u njoj biti 0

logneg: 00000000000000000000000000000000

Negacija na novou bita :

int bitneg = ~i;

Možda ćete se iznenaditi, ali je ono što se dobije celobrojna vrednost -16:

bitneg: 11111111111111111111111111110000

Zašto je to tako je za sada komplikovano za objašnjavanje, ali ćete to učiti kasnije.

Svaki od prethodno navedenih binarnih operatora može da se koristi u skraćenom obliku:

x=x&y   ili      x &= y

x=x|y    ili      x |= y

x=x^y   ili      x ^= y

Zbog čega uopšte koristimo operatore koji rade samo sa bitovima?

Zašto će nam ovo?

Zamislite da radite u velikom timu koji radi na razvoju važnog dela operativnog sistema. Data vam je promenljiva koja je deklarisana na sledeći način:

int FlagRegister;

U toj promenljivoj se nalaze informacije o raznim stanjima sistema i operacijama koje su rađene u njemu. Svaki bit u toj promenljivoj čuva jednu vrednost  da/ne.

Vama kao programeru je rešeno da je samo jedan od tih bitova vaš - bit broj 3 (bitovi imaju svoje brojeve, počinju da se broje od 0 što je bit najniže vrednosti pa na dalje, a najveći je broj 31 u našem slučaju) Preostali bitovi su za vas zabranjeni, jer su predviđeni da se u njima nalaze neki drugi  podaci.U nastavku je vaš bit označen slovom “x”:

FlagRegister: 0000000000000000000000000000x000

Zadatak:

proveriti koje je stanje vašeg bita - tj. želite da pronađete koju vrednost iima vaš bit. Ako biste celu promenljivu poredili sa nulom to vam ništa ne bi pomoglo, zbog toga što se na mestu ostalih bitova može nalaziti bilo šta. Ono što može da se iskoristi je operacija na nivou bita, i to logičko I na nivou bita:

Pošto znamo da je

x&1=x
x&0=0

ako primenimo & operaciju nad promenljivom FlagRegister sa sledećom vrednošću

00000000000000000000000000001000

(obratite pažnju da je na poziciji vašeg bita u "1" ) kao rezultat ćemo dobiti:

00000000000000000000000000001000 ako je vaš bit imao vrednost "1"

00000000000000000000000000000000 ako je vaš bit imao vrednost "0"

Niz nula i jedinica koji nam pomažu da izdvojimo vrednost ili da promenimo samo pojedine bitove načivamo bitmaska. U nastavku ćemo napraviti bitmasku za detektovanje stanja vašeg bita. Znači da treba da prikaže stanje trećeg bita. Taj bit ima težinu 23 = 8. Odgovarajuću masku kreiramo pomoću sledeće deklaracije:

int TheMask = 8;

Takođe, možemo da napravimo niz instrukcija u zavisnosti od stanja vašeg bita:

if(FlagRegister & TheMask) // ako je moj bit postavljen na 1
{
/* moj bit ima vrednost 1 i tada radim nesto (bit je setovan) */
}
else
{
/* moj bit ima vrednost 0 i tada radim nesto drugo (bit je resetovan ) */
}

Ako kažemo da želimo da resetujemo bit - to znači da želimo da tom bitu dodelimo vrednost 0, dok svi ostali bitovi moraju ostati nepromenjeni, u tom slučaju opet možemo da koristimo operaciju logičko I na nivou bita, ali je sada u pitanju potpuno drugačija maska:


1111111111111111111111111111110111

Ova maska je dobijena kao rezultat negacije svih bitova promenljive TheMask. Resetovanje bita je jednostavno i radi se na jedan od sledećih načina (odaberite onaj koi vam se više sviđa):

FlagRegister = FlagRegister & ~TheMask;

FlagRegister &= ~TheMask;

Ako želimo da setujemo bit - želimo da tom bitu dodelimo vrednost "jedan" dok svi ostali bitovi moraju ostati nepromenjeni. U ovom slučaju koristimo sledeću osobinu operacije logičkog ILI nad bitovima:


x|1=1

x|0=x

Setovaćemo taj bit pomoću sledeće instrukcije:

FlagRegister = FlagRegister | TheMask;

FlagRegister |= TheMask;

Ako želite da negirate vaš bit -  želite da zamenite "nulu" sa "jedinicom", a "jedinicu" da zamenite "nulom". U ovom slučaju koristimo sledeću osobinu koju ima operator xor (isključivo ili):


x ^ 1 = !x

x ^ 0 = x

U tom slučaju, vaš bit ćete negirati pomoću sledeće operacije:

FlagRegister = FlagRegister ^ TheMask;

FlagRegister ^= TheMask;

 

Šift - pomeranje bitova

U programskom jeziku "C" postoji još jedna operacija koja se radi sa pojedinačnim bitovima: pomeranje (šiftovanje). Ovo može da se radi samo sa celobrojnim promenljivima, što znači da ne smete da koristite float! Ovu vrstu operacije i inače nesvesno koristite dosta često. Kako množimo neki broj sa 10?

12345 ∙ 10 = 123450

Kao što se bidi iz primera, množenje sa 10 je u stvari pomeranje (šiftovanje) svih cifara u levo i popunjavanje preostalih mesta sa nulama. Deljenje sa 10?

12340 ÷10 = 1234

Deljenje sa 10 nije ništa viiše nego pomeranje svih cifara u desno..

Istu vrstu operacija rade u računari, samo sa jednom razlikom: pošto je baza za binarne brojeve 2 (a ne 10), pomeranje neke vrednosti za jedan bit u levo odgovara množenju sa 2, a pomeranje za jedan bit u desno odgovara deljenju sa 2 (u tom slučaju je bit koji je bio poslednji sa desne strane izgubljen)


Vrednost << BrojBitovaZaKojiSePomera

Vrednost >> BrojBitovaZaKojiSePomera


Operatori kojima se vrši pomeranje bitova u programsko jeziku "C" su parovi znakova <<  i   >> odakle se jasno vidi u kojem pravcu se vrši pomeranje. Levi operand je kod ovih operatora celobrojna vrednost čiji bitovi se pomeraju, a desni kazuje za koliko mesta će se pomeriti. Iz ovoga bi trebalo da je jasno da ova operacija nije komutativna.

Prioritet ovih operatora je dosta velik. Tabela prioriteta je data na kraju ovog fajla.


Deklarisat ćemo promenljive:

int Signed = -8, VarS;

unsigned Unsigned = 6, VarU;

Obratite pažnju kako su izvršene sledeće operacije:

/* ekvivalentno deljenju sa 2 -> VarS == -4 */
VarS = Signed >> 1;

/* ekvivalentno množenju sa 4 -> VarS == -32 */
VarS = Signed << 2;

/* ekvivalentno deljenju sa 4 -> VarU == 1 */
VarU = Unsigned >> 2;

/* ekvivalentno množenju sa 2 -> VarU == 12 */
VarU = Unsigned << 1;

Oba operatora mogu da se koriste u skraćenom obliku:

Signed >>= 1; /* division by 2 */
Unsigned <<= 1; /* multiplication by 2 */


Tabela operatora sa njihovim prioritetima:

!   ~   ++   --   +   -

unarni

*   /   %
+    -

binarni

<<   >>
<   <=   >    >=
==   !=
&
|
&&
||
=    +=  -=  *=   /=  %=  &= ^=   |=  >>=  <<=

 

Last modified: Tuesday, 30 March 2021, 7:08 PM