Standart C Programlama Dili


3.5. Bitsel İşleçler

İşleçler C’nin önemli bir konusudur. Ancak, hepsinin birlikte anlatılacakları tek bir yer yoktur. En iyisi gerek duyulduğunda onları anlatmaktır. Bu bölümde, göstergelerle beraber kullanıldıkları için, iki tekli işleç (& ve *) anlatılmıştı.

Bitsel işleçler başka şeylerle pek ilgisi olmayan bir grup oluşturduklarından, hepsini bu kısımda anlatmaya karar verdik. İşleçlerin azalan önceliklerine göre anlatılmaları açısından bu kısım Kısım 2.2’ye benzemektedir. Bire tümler ve atama işleçleri sağdan sola doğru, diğerleri soldan sağa doğru birleşirler. Bu kısımda anlatılan tüm işleçler sadece (çeşitli boylarda olabilen) tamsayı işlenenleri kabul ederler ve işlenenler üzerinde işlem yapmadan önce Kısım 1.6’da anlatılan tip dönüşümlerini gerçekleştirirler. Atama ile ilgili olanlar hariç, eğer işlenenler değişmez ifadeler ise, değişmez ifadeler oluştururlar. Bu işleçlerin isimleri ve kullanılan simgeler şöyledir:

İşleçİsim
~bire tümler
<<sola kaydırma
>>sağa kaydırma
&bitsel VE
^bitsel dışlayan VEYA
|bitsel VEYA
<<=sola kaydırma ve atama
>>=sağa kaydırma ve atama
&=bitsel VE ve atama
^=bitsel dışlayan VEYA ve atama
|=bitsel VEYA ve atama

Detaya girmeden önce, bitsel işleçlerin mantıksal olarak ne yaptığını gösteren bir çizelge sunalım. Aşağıda y ve z tek bitlerdir:

y z ~y y&z y^z y|z
001000
011011
100011
110101

Bitsel bir işleç işlenen(ler)in her bitine yukarıda anlatılan şekilde uygulanır.

Bire Tümler İşleci ~

~ işleci işleneninin 1 olan bitlerini 0’a, 0 olan bitlerini de 1’e çevirir. Sonucun tipi işlenenin tipiyle aynıdır.

Kaydırma İşleçleri << Ve >>

Bir kaydırma ifadesi

ifade_1 << ifade_2

veya

ifade_1 >> ifade_2

şeklindedir. Normalde ifade_1 bir değişken veya dizi elemanıdır. << işleci durumunda, tip dönüşümlerinden sonra, ifade_2 int’e çevrilir ve ilk işlenenin bitleri ifade_2’nin değeri kadar sola kaydırılır. İşlenenin solundan “düşen” bitler kaybedilir. Sola kayan bitlerden arta kalan sağdaki boş yerler sıfırla doldurulur. Sonucun tipi soldaki işlenenin tipidir.

>> işleci yukarıda anlatılanların sağa doğru olan şeklini yerine getirir, tek bir farkla: Eğer soldaki ifade signed (işaretli) ve olumsuz ise boş kalan yerler, 0 yerine, 1 (işaret) bitiyle doldurulabilir. Buna aritmetik kaydırma denir ve sistemlerin çoğu bunu yapar. Eğer ilk ifade unsigned (işaretsiz) ise solda boşalan bitlerin sıfırla doldurulacağı garanti edilir ve bir mantıksal kaydırma söz konusudur.

Bitsel VE İşleci &

İkili & işleci işlenenlerinin bitlerinin VEsini verir. Yani eğer her iki işlenenin i’inci bitleri 1 ise, sonucun i’inci biti de 1 olur. Aksi takdirde 0’dır. Tabii ki,

(a & b) & c

ve

a & (b & c)

aynı değerlere sahiptirler (yani & işlecinin birleşme özelliği vardır). C bu gerçeği kullanır ve derleyici, daha verimli olacağı gerekçesiyle, parantez içine alınmış olsa dahi, böyle ifadeleri (yani, * ve + gibi birleşme ve değişme özelliği gösteren işleçleri içeren ifadeleri) istediği şekilde yeniden düzenleyebilir.

Bitsel Dışlayan VEYA İşleci ^

^ işleci işlenenlerinin dışlayan VEYAsını verir. Yani eğer işlenenlerin i’inci bitleri farklı ise, sonucun i’inci biti 1 olur. Aksi takdirde 0’dır. ^ birleşme özelliği gösteren bir işleçtir ve bu işleci içeren ifadeler, önceki altkısımda anlatıldığı gibi, yeniden düzenlenebilir.

Bitsel VEYA İşleci |

| işleci işlenenlerinin kapsayan VEYAsını verir. Yani eğer her iki işlenenin i’inci bitleri 0 ise, sonucun i’inci biti de 0 olur. Aksi takdirde 1’dir. | da birleşme özelliği gösteren bir işleçtir ve yeniden düzenleme söz konusu olabilir.

Atama İşleçleri <<=, >>=, &=, ^= Ve |=

Yukarıda anlatılan işleçler belirli ifadeler oluşturmalarına rağmen, işlenenlerinin değerlerini değiştirmezler. Şimdi anlatacağımız atama işleçleri ise işlemin sonucunu soldaki işlenenlerine aktarırlar.

Bölüm 1’deki +=, -=, *= vs işleçlerini anımsayın. Her bir ikili bitsel işlecin karşılığında bir atama işleci mevcuttur, << için <<=, >> için >>=, & için &=, ^ için ^= ve | için |=. Bu işleçlerin işlevselliği diğer atama işleçlerine benzer.

Bitsel İşleçler—Örnekler

short, long gibi tiplerin uzunlukları derleyiciden derleyiciye değiştiğine göre, buradaki örnekler sadece bizim 64 bitlik sistemimiz için geçerlidir. Bu sistemde short 2 bayt, int 4 bayt, long 8 bayttır. Aşağıdaki bütün örneklerde ifadenin değeri sekizli tabanda verilmiştir. Sonucun tipi de ayrıca belirtilmiştir. Bütün değişkenlerin ilk değeri 5’tir. Değişken tanımları şöyledir:

/* signed */ char c = 5;   short s = 5;   int i = 5;   long l = 5;
İfadeDeğerTip
~c037777777772int
~s037777777772int
~i037777777772int
~l01777777777777777777772long
c << 3050int
s >> 201int
i << l0240int
c & s05int
-i & ~l01777777777777777777772long
c ^ 306int
~s ^ 2037777777770int
i ^ l & i0long
~c | 3037777777773int
s | s>>107int
i | l05long
c <<= s0240char
l >>= i0long
s &= -i01short
i ^= l+103int
c |= 16025char

Bir Örnek—Sayı Paketleme

Bitsel işleçlerin kullanımını göstermek için, üç sayıyı 16 bitlik kısa bir tamsayı içine “paketleyen”, daha sonra ise “açan” aşağıdaki programı inceleyelim.

 1. #include <stdio.h>
 2. #define GUN_UZ     5
 3. #define AY_UZ      4
 4. #define TABAN_YIL  1950
 5. #define TAVAN_YIL  (TABAN_YIL+(1<<16-GUN_UZ-AY_UZ)-1)
 6. int main (void)
 7. {
 8.   unsigned const ay_ortu  = ~(~0<<AY_UZ);
 9.   unsigned const gun_ortu = ~(~0<<GUN_UZ);
10.   unsigned yil, ay, gun;
11.   unsigned short tarih;
12.
13.   do {
14.     scanf("%u%u%u", &yil, &ay, &gun);
15.   } while (yil<TABAN_YIL || yil>TAVAN_YIL ||
16.            ay<1 || ay>12 || gun<1 || gun>31);
17.   yil -= TABAN_YIL;
18.   tarih = (yil<<AY_UZ|ay)<<GUN_UZ | gun;
19.   printf("Paketleme tamamlandi: %ho\n", tarih);
20.
21.   yil = ay = gun = 0;  /* degiskenleri "temizle" */
22.   gun = tarih & gun_ortu;
23.   ay = (tarih>>GUN_UZ) & ay_ortu;
24.   yil = tarih>>(GUN_UZ+AY_UZ);
25.   printf("Acma tamamlandi.\nTarih: %u %u %u\n",
26.       yil+TABAN_YIL, ay, gun);
27.   return 0;
28. } /* main */

Program başladığında 1950 ile 2077 yılları arasında bir tarih kabul eder. (Satır 13-16.) Sadece bazı basit kontroller yapılmıştır; 13’üncü ayın 54’üncü gününü giremezsiniz, ancak Şubat’ın 31’ini girebilirsiniz. Sadece 1950 yılından beri geçen yılları sakladığımız için, Satır 17’deki -= işlemi yapılır. Paketleme Satır 18’de yapılır: yil içindeki geçen yıllar AY_UZ (yani 4) bit sola kaydırılır, ay yeni boşalan yere VEYAlanır, sonra bunun tamamı GUN_UZ (5) bit sola kaydırılır ve gun içeriye VEYAlanır. Paketleme sonunda, tarihin en solundaki 7 bitinde geçen yıllar, en sağdaki 5 bitte gün ve geri kalanında ay bulunur. Bizim sistemimizde short bir tamsayı 16 bit kapladığına göre, tarihteki bütün bitler kullanılmış olur. do deyimindeki erim (yayılma aralığı) kontrolleri, bu sayıların hiçbirinin kendileri için ayrılan miktardan daha fazla bir yer isteğinde bulunmayacaklarını sağlamak içindir.

Satır 21 açma işleminin gerçekten çalışıp çalışmadığı kuşkusunu bertaraf etmek içindir ve programın davranışında herhangi bir değişikliğe yol açmadan çıkarılabilir.

Satır 22 tarihin gün bitlerini 1’le, geri kalanını da 0’la VEleyerek günü alır. Buna, belli nedenden dolayı, “örtme” denir. Satır 9’da ilklenen, gun_ortu değişkeni gerekli bit kalıbını içerir. (~0’ın bütün bitleri 1’dir.) Satır 23, önce ayın en sağdaki 4 bite geleceği şekilde tarihi kaydırır, sonra da ay dışındaki bitleri örterek ayı elde eder. Satır 24’te, tarih, sonuçta geçen yılların elde edileceği şekilde, sağa kaydırılır. tarih unsigned olduğuna göre en solda hep 0 vardır. Program yeni açılan tarihi yazarak sona erer.

Bu arada, daha ileriki bir bölümde aynı paketlemeyi yapacak başka bir yöntem göreceğiz.