D.ershane D Programlama Dili Dersleri

bayrak: [flag], bir işlemin veya sonucun geçerli olup olmadığını bildiren bit
bayt: [byte], 8 bitlik tür
bit: [bit], 0 ve 1 değerlerini alabilen en temel bilgi birimi
ifade: [expression], programın değer oluşturan veya yan etki üreten bir bölümü
ikili sayı sistemi: [binary system], iki rakamdan oluşan sayı sistemi
işaretli tür: [signed type], eksi ve artı değer alabilen tür
işaretsiz tür: [unsigned type], yalnızca artı değer alabilen tür
on altılı sayı sistemi: [hexadecimal system], on altı rakamdan oluşan sayı sistemi
yazmaç: [register], mikro işlemcinin en temel iç depolama ve işlem birimi
... bütün sözlük

Bölümler
İngilizce Kaynaklar
Diğer



Bit İşlemleri

Bu bölümde mikro işlemcinin en küçük bilgi birimi olan bitlerle yapılan işlemleri tanıyacağız. Bit işlemleri mikro işlemcinin en temel olanaklarındandır.

Bu işlemler hem alt düzey programcılık açısından bilinmelidir, hem de parametre olarak bayrak alan işlevler için gereklidir. Böyle işlevlere özellikle C kütüphanelerinden uyarlanmış olan D kütüphanelerinde rastlayabilirsiniz.

Verinin en alt düzeyde gerçekleştirilmesi

D gibi bir programlama dili aslında bir soyutlamadır. Program içinde tanımladığımız Öğrenci gibi bir kullanıcı türünün bilgisayarın iç yapısı ile doğrudan bir ilgisi yoktur. Bir programlama dilinin amaçlarından birisi, donanımın anladığı dil ile insanın anladığı dil arasında aracılık yapmaktır.

Yine de, D dili ile ifade ettiğimiz kavramların en alt düzeyde elektronik devre elemanlarına nasıl bağlı olduklarını anlamak önemlidir. Bu konularda başka kaynaklarda çok miktarda bilgi bulabileceğinizi bildiğim için bu başlığı olabildiğince kısa tutacağım.

Transistör

Modern elektronik aletlerin işlem yapma yetenekleri büyük ölçüde transistör denen elektronik devre elemanı ile sağlanır. Transistörün bir özelliği, devrenin başka tarafındaki sinyallerle kontrol edilebilmesidir. Bir anlamda, elektronik devrenin kendi durumundan haberinin olmasını ve kendi durumunu değiştirebilmesini sağlar.

Transistörler hem mikro işlemcinin içinde hem de bilgisayarın ana belleğinde çok büyük sayılarda bulunurlar. Programlama dili aracılığıyla ifade ettiğimiz işlemleri ve verileri en alt düzeyde gerçekleştiren elemanlardır.

Bit

Bilgisayarlarda en küçük bilgi birimi bittir. Bit, en alt düzeyde bir kaç tane transistörün belirli bir düzende bir araya getirilmesi ile gerçekleştirilir. Bit, veri olarak iki farklı değerden birisini depolayabilir: 0 veya 1. Depoladığı veriyi, tekrar değiştirilene kadar veya enerji kaynağı tükenene kadar korur.

Bilgisayarlar, veriye bit düzeyinde doğrudan erişim sağlamazlar. Bunun nedeni; her bitin adreslenebilmesinin bilgisayarın karmaşıklığını ve maliyetini çok arttıracak olması, ve zaten tek bitlik kavramların desteklenmeye değmeyecek kadar nadir olmalarıdır.

Bayt

Bayt, birbirleriyle ilişkilendirilen 8 bitin bir araya gelmesinden oluşur. Bilgisayarlarda adreslenebilen, yani ayrı ayrı erişilebilen en küçük veri bayttır. Bellekten tek seferde en az bir bayt veri okunabilir ve belleğe en az bir bayt veri yazılabilir.

Bu yüzden, yalnızca false ve true diye iki farklı değer alan ve bu yüzden de tek bitlik bilgi taşıyan bool türü bile 1 bayt olarak gerçekleştirilir. Bunu, bool.sizeof değerine bakarak kolayca görebiliriz:

    writeln(bool.stringof, ' ', bool.sizeof, " bayttır");
bool 1 bayttır
Yazmaç

Mikro işlemcinin kendi içinde bulunan depolama ve işlem birimleri yazmaçlardır. Yazmaçlar oldukça kısıtlı ama çok hızlı işlemler sunarlar.

Yazmaçlar her işlemcinin bit genişliğine bağlı olan sayıda bayttan oluşurlar. Örneğin 32 bitlik işlemcilerde yazmaçlar 4 bayttan, 64 bitlik işlemcilerde de 8 bayttan oluşur.

Yazmaç genişliği, mikro işlemcinin en etkin olarak işleyebildiği bilgi miktarını belirler. Örneğin 32 bitlik bir işlemci en etkin olarak int ve uint türlerini, 64 bitlik bir işlemci de long ve ulong türlerini işleyebilir.

Programlama dili aracılığıyla gerçekleştirilen her iş eninde sonunda bir veya daha fazla yazmaç tarafından halledilir.

İkili sayı sistemi

Günlük hayatta kullandığımız onlu sayı sisteminde 10 rakam vardır: 0123456789. İkili sayı sistemlerinde de iki rakam vardır: 0 ve 1. Bu, bitin iki değer alabilmesinden gelir. (Not: Bitler örneğin üç farklı değer alabilseler, bilgisayarlar üçlü sayı sistemini kullanırlardı.)

Günlük hayatta kullandığımız sayıların birler, onlar, yüzler, binler, vs. basamakları vardır. Örneğin 1023 gibi bir sayı şöyle ifade edilebilir:

1023 == 1 adet 1000, 0 adet 100, 2 adet 10, ve 3 adet 1

Dikkat ederseniz, sola doğru ilerlendiğinde her basamağın değeri 10 kat artmaktadır: 1, 10, 100, 1000, vs.

Aynı tanımı ikili sayı sistemine taşıyınca, ikili sistemde yazılmış olan sayıların basamaklarının da birler, ikiler, dörtler, sekizler, vs. şeklinde gitmesi gerektiğini görürüz. Yani sola doğru ilerlendiğinde her basamağın değeri 2 kat artmalıdır: 1, 2, 4, 8, vs. Örneğin 1011 gibi bir ikili sayı şöyle ifade edilebilir:

1011 == 1 adet 8, 0 adet 4, 1 adet 2, 1 adet 1

Basamaklar numaralanırken; en sağdaki, yani en düşük değerli olan basamağa 0 numaralı basamak denir. Buna göre, ikili sayı sisteminde yazılmış olan 32 bitlik işaretsiz bir değerin bütün basamaklarını ve basamak değerlerini şöyle gösterebiliriz:

Basamak Değeri
312,147,483,648
301,073,741,824
29536,870,912
28268,435,456
27134,217,728
2667,108,864
2533,554,432
2416,777,216
238,388,608
224,194,304
212,097,152
201,048,576
19524,288
18262,144
17131,072
1665,536
1532,768
1416,384
138,192
124,096
112,048
101,024
9512
8256
7128
664
532
416
38
24
12
01

Yüksek değerli bitlere üst bit, düşük değerli bitlere alt bit denir.

İkili sistemde yazılan değerlerin 0b ile başladıklarını Hazır Değerler dersinde görmüştük. İkili sistemde değerler yazarak bu tabloya nasıl uyduklarına bakabiliriz. Okumayı kolaylaştırmak için alt çizgi karakterlerinden de yararlanarak:

import std.stdio;

void main()
{
    //             1073741824                     4 1
    //             ↓                              ↓ ↓
    int sayı = 0b_01000000_00000000_00000000_00000101;
    writeln(sayı);
}

Çıktısı:

1073741829

Dikkat ederseniz, o hazır değerin içinde rakamı 1 olan yalnızca 3 adet basamak vardır. Bu basamakların yukarıdaki tablodaki basamak değerlerini toplayınca bu değere eşit olduğunu görürüz: 1073741824 + 4 + 1 == 1073741829.

İşaretli türlerin işaret biti

En üst bit, işaretli türlerde sayının artı veya eksi olduğunu bildirmek için kullanılır:

    int sayı = 0b_10000000_00000000_00000000_00000000;
    writeln(sayı);
-2147483648

En üst bitin diğerlerinden bağımsız olduğunu düşünmeyin. Örneğin yukarıdaki sayı, diğer bitlerinin 0 olmalarına bakarak -0 değeri olarak düşünülmemelidir (zaten tamsayılarda -0 diye bir değer yoktur). Bunun ayrıntısına burada girmeyeceğim ve bunun D'nin de kullandığı ikiye tümleyen sayı gösterimi ile ilgili olduğunu söylemekle yetineceğim.

Burada önemli olan, yukarıdaki tabloda gösterilen en yüksek 2,147,483,648 değerinin yalnızca işaretsiz türlerde geçerli olduğunu bilmenizdir. Aynı deneyi uint ile yaptığımızda tablodaki değeri görürüz:

    uint sayı = 0b_10000000_00000000_00000000_00000000;
    writeln(sayı);
2147483648

Bu yüzden, aksine bir neden olmadığı sürece aşağıda gösterilenler gibi bit işlemlerinde her zaman için işaretsiz türler kullanılır: ubyte, uint, ve ulong.

On altılı sayı sistemi

Yukarıdaki hazır değerlerden de görülebileceği gibi, ikili sayı sistemi görüntülenmeye uygun değildir. Hem çok yer kaplar, hem de yalnızca 0 ve 1'lerden oluştuğu için okunması ve anlaşılması zordur.

Daha kullanışlı olduğu için on altılı sayı sistemi yaygınlaşmıştır.

On altılı sayı sisteminde toplam 16 rakam vardır. Alfabelerde 10'dan fazla rakam bulunmadığı için, Latin alfabesinden 6 harf ödünç alınarak bu sistemin rakamları olarak 0123456789abcdef kabul edilmiştir. O sıralamadan bekleneceği gibi; a 10, b 11, c 12, d 13, e 14, ve f 15 değerindedir. abcdef harfleri yerine isteğe bağlı olarak ABCDEF harfleri de kullanılabilir.

Yukarıdaki sayı sistemlerine benzer şekilde, bu sistemde sola doğru ilerlendiğinde her basamağın değeri 16 kat artar: 1, 16, 256, 4096, vs. Örneğin, on altılı sistemdeki 8 basamaklı bir sayının basamak değerleri şöyledir:

Basamak Değeri
7268,435,456
616,777,216
51,048,576
465,536
34,096
2256
116
01

On altılı hazır değerlerin 0x ile yazıldıklarını hatırlayarak bir deneme:

    //         1048576 4096 1
    //               ↓  ↓  ↓
    uint sayı = 0x_0030_a00f;
    writeln(sayı);
3186703

Bunun nedenini sayı içindeki sıfır olmayan basamakların katkılarına bakarak anlayabiliriz: 3 adet 1048576, a adet 4096, ve f adet 1. a'nın 10 ve f'nin 15 olduklarını hatırlayarak hesaplarsak: 3145728 + 40960 + 15 == 3186703.

On altılı ve ikili sistemde yazılan sayılar kolayca birbirlerine dönüştürülebilirler. On altılı sistemdeki bir sayıyı ikili sisteme dönüştürmek için, sayının her basamağı ikili sistemde dört basamak olarak yazılır. Birbirlerine karşılık gelen değerler şöyledir:

On altılı İkili Onlu
000000
100011
200102
300113
401004
501015
601106
701117
810008
910019
a101010
b101111
c110012
d110113
e111014
f111115

Örneğin yukarıda kullandığımız 0x0030a00f on altılı değerini ikili olarak şöyle yazabiliriz:

    // on altılı:    0    0    3    0    a    0    0    f
    uint ikili = 0b_0000_0000_0011_0000_1010_0000_0000_1111;

İkili sistemden on altılı sisteme dönüştürmek için de ikili sayının her dört basamağı on altılı sistemde tek basamak olarak yazılır. Yukarıda ilk kullandığımız ikili değer için:

    // ikili:           0100 0000 0000 0000 0000 0000 0000 0101
    uint onAltılı = 0x___4____0____0____0____0____0____0____5;
Bit işlemleri

Değerlerin bitlerle nasıl ifade edildiklerini ve ikili veya onaltılı olarak nasıl yazıldıklarını gördük. Şimdi, değerleri bit düzeyinde değiştiren işlemlere geçebiliriz.

Her ne kadar bit düzeyindeki işlemlerden bahsediyor olsak da, bitlere doğrudan erişilemediği için, bu işlemler en az 8 biti birden etkilemek zorundadır. Örneğin ubyte türündeki bir ifadenin 8 bitinin hepsi de, ama ayrı ayrı olarak bit işlemine dahil edilir.

Ben üst bitin özel anlamı nedeniyle işaretli türleri gözardı edeceğim ve bu örneklerde uint türünü kullanacağım. Siz buradaki işlemleri ubyte ve ulong türleri ile, ve işaret bitinin önemini hatırlamak şartıyla byte, int, ve long türleri ile de deneyebilirsiniz.

Önce, aşağıdaki işlemleri açıklamada yardımcı olacak bir işlev yazalım. Kendisine verilen sayıyı ikili, on altılı, ve onlu sistemde göstersin:

import std.stdio;

void göster(uint sayı)
{
    writefln("%032b %08x %10s", sayı, sayı, sayı);
}

void main()
{
    göster(123456789);
}

Sırasıyla; ikili, on altılı, ve onlu:

00000111010110111100110100010101 075bcd15  123456789
Tersini alma işleci ~

Bu işleç, önüne yazıldığı ifadenin bitleri ters olanını üretir. 1 olan bitler 0, 0 olanlar 1 olur:

    uint değer = 123456789;
    write("  "); göster(değer);
    write("~ "); göster(~değer);

Bu işlecin etkisi ikili gösteriminde çok kolay anlaşılıyor. Her bit tersine dönmüştür:

  00000111010110111100110100010101 075bcd15  123456789
~ 11111000101001000011001011101010 f8a432ea 4171510506

Bu işlecin bit düzeyindeki etkisini şöyle özetleyebiliriz:

~0 → 1
~1 → 0
Ve işleci &

İki ifadenin arasına yazılır. İki ifadenin aynı numaralı bitlerine sırayla bakılır. Sonuç olarak, her iki ifadede de 1 olan bitler için 1 değeri, diğerleri için 0 değeri üretilir.

    uint soldaki = 123456789;
    uint sağdaki = 987654321;

    write("  "); göster(soldaki);
    write("  "); göster(sağdaki);
    writeln("& --------------------------------");
    write("  "); göster(soldaki & sağdaki);

Mikro işlemci bu işlemde her iki ifadenin 31, 30, 29, vs. numaralı bitlerini ayrı ayrı kullanır.

Çıktıda önce soldaki ifadeyi, sonra da sağdaki ifadeyi görüyoruz. Kesikli çizginin altında da bit işleminin sonucu yazdırılıyor:

  00000111010110111100110100010101 075bcd15  123456789
  00111010110111100110100010110001 3ade68b1  987654321
& --------------------------------
  00000010010110100100100000010001 025a4811   39471121

Dikkat ederseniz, kesikli çizginin altına yazdırdığım sonuç değerde 1 olan bitler, çizginin üstündeki her iki ifadede de 1 değerine sahip olan bitlerdir.

Bu işleç bu yüzden ve işleci olarak isimlendirilmiştir: soldaki ve sağdaki bit 1 olduğunda 1 değerini üretir. Bunu bir tablo ile gösterebiliriz. İki bitin 0 ve 1 oldukları dört farklı durumda ancak iki bitin de 1 oldukları durum 1 sonucunu verir:

0 & 0 → 0
0 & 1 → 0
1 & 0 → 0
1 & 1 → 1

Gözlemler:

Veya işleci |

İki ifadenin arasına yazılır. İki ifadenin aynı numaralı bitlerine sırayla bakılır. Her iki ifadede de 0 olan bitlere karşılık 0 değeri üretilir; diğerlerinin sonucu 1 olur:

    uint soldaki = 123456789;
    uint sağdaki = 987654321;

    write("  "); göster(soldaki);
    write("  "); göster(sağdaki);
    writeln("| --------------------------------");
    write("  "); göster(soldaki | sağdaki);
  00000111010110111100110100010101 075bcd15  123456789
  00111010110111100110100010110001 3ade68b1  987654321
| --------------------------------
  00111111110111111110110110110101 3fdfedb5 1071639989

Dikkat ederseniz; sonuçta 0 olan bitler, her iki ifadede de 0 olan bitlerdir. Bitin soldaki veya sağdaki ifadede 1 olması, sonucun da 1 olması için yeterlidir:

0 | 0 → 0
0 | 1 → 1
1 | 0 → 1
1 | 1 → 1

Gözlemler:

Ya da işleci ^

İki ifadenin arasına yazılır. İki ifadenin aynı numaralı bitlerine sırayla bakılır. İki ifadede farklı olan bitlere karşılık 1 değeri üretilir; diğerlerinin sonucu 0 olur:

    uint soldaki = 123456789;
    uint sağdaki = 987654321;

    write("  "); göster(soldaki);
    write("  "); göster(sağdaki);
    writeln("^ --------------------------------");
    write("  "); göster(soldaki ^ sağdaki);
  00000111010110111100110100010101 075bcd15  123456789
  00111010110111100110100010110001 3ade68b1  987654321
^ --------------------------------
  00111101100001011010010110100100 3d85a5a4 1032168868

Dikkat ederseniz; sonuçta 1 olan bitler, soldaki ve sağdaki ifadelerde farklı olan bitlerdir. İkisinde de 0 veya ikisinde de 1 olan bitlere karşılık 0 üretilir.

0 ^ 0 → 0
0 ^ 1 → 1
1 ^ 0 → 1
1 ^ 1 → 0

Gözlem:

Değeri ne olursa olsun, aynı değişkenin kendisiyle "ya da"lanması 0 sonucunu üretir:

    uint değer = 123456789;

    göster(değer ^ değer);
00000000000000000000000000000000 00000000          0
Sağa kaydırma işleci >>

İfadenin değerini oluşturan bitleri belirtilen sayıda basamak kadar sağa kaydırır. Kaydırılacak yerleri olmayan en sağdaki bitler düşerler ve değerleri kaybedilir. Sol taraftan yeni gelen bitler işaretsiz türlerde 0 olur.

Bu örnek, bitleri 2 basamak kaydırıyor:

    uint değer = 123456789;
    göster(değer);
    göster(değer >> 2);

Sağdan kaybedilecek olan bitleri griyle, soldan yeni gelen bitleri de maviyle işaretliyorum:

00000111010110111100110100010101 075bcd15  123456789
00000001110101101111001101000101 01d6f345   30864197

Dikkat ederseniz, alt satırdaki bitler üst satırdaki bitlerin iki bit sağa kaydırılması ile elde edilmiştir.

Bitler sağa kaydırılırken sol tarafa yeni gelenlerin 0 olduklarını gördünüz. Bu, işaretsiz türlerde böyledir. İşaretli türlerde ise işaret genişletilmesi (sign extension) denen bir yöntem uygulanır ve sayının en soldaki biti ne ise, soldan hep o bitin değerinde bitler gelir.

Bu etkiyi göstermek için int türünde ve özellikle üst biti 1 olan bir değer seçelim:

    int değer = 0x80010300;
    göster(değer);
    göster(değer >> 3);

Asıl sayıda üst bit 1 olduğu için yeni gelen bitler de 1 olur:

10000000000000010000001100000000 80010300 2147549952
11110000000000000010000001100000 f0002060 4026540128

Üst bitin 0 olduğu bir değerde yeni gelen bitler de 0 olur:

    int değer = 0x40010300;
    göster(değer);
    göster(değer >> 3);
01000000000000010000001100000000 40010300 1073808128
00001000000000000010000001100000 08002060  134226016
İşaretsiz sağa kaydırma işleci >>>

Bu işleç sağa kaydırma işlecine benzer şekilde çalışır. Tek farkı, işaret genişletilmesinin kullanılmamasıdır. Türden ve en soldaki bitten bağımsız olarak soldan her zaman için 0 gelir:

    int değer = 0x80010300;
    göster(değer);
    göster(değer >>> 3);
10000000000000010000001100000000 80010300 2147549952
00010000000000000010000001100000 10002060  268443744
Sola kaydırma işleci <<

Sağa kaydırma işlecinin tersi olarak, bitleri belirtilen basamak kadar sola kaydırır:

    uint değer = 123456789 ;
    göster(değer);
    göster(değer << 4);

En soldaki bit değerleri kaybedilir ve sağ taraftan 0 değerli bitler gelir:

00000111010110111100110100010101 075bcd15  123456789
01110101101111001101000101010000 75bcd150 1975308624
Atamalı bit işleçleri

Bütün bu işleçlerin atamalı olanları da vardır: ~=, &=, |=, ^=, >>=, >>>=, ve <<=.

Tamsayılar ve Aritmetik İşlemler dersinde gördüğümüz atamalı aritmetik işleçlerine benzer şekilde, bunlar işlemi gerçekleştirdikten sonra sonucu soldaki ifadeye atarlar.

Örnek olarak &= işlecini kullanırsak:

    değer = değer & 123;
    değer &= 123;         // üsttekiyle aynı şey
Anlamları

Bu işleçlerin bit düzeyinde nasıl işledikleri, işlemlerin hangi anlamlarda görülmeleri gerektiği konusunda yeterli olmayabilir.

Burada bu anlamlara dikkat çekmek istiyorum.

| işleci, birleşim kümesidir

İki ifadenin 1 olan bitlerinin birleşimini verir. Uç bir örnek olarak, bitleri birer basamak atlayarak 1 olan ve birbirlerini tutmayan iki ifadenin birleşimi, sonucun bütün bitlerinin 1 olmasını sağlar:

    uint soldaki = 0xaaaaaaaa;
    uint sağdaki = 0x55555555;

    write("  "); göster(soldaki);
    write("  "); göster(sağdaki);
    writeln("| --------------------------------");
    write("  "); göster(soldaki | sağdaki);
  10101010101010101010101010101010 aaaaaaaa 2863311530
  01010101010101010101010101010101 55555555 1431655765
| --------------------------------
  11111111111111111111111111111111 ffffffff 4294967295
& işleci, kesişim kümesidir

Her iki ifadede de 1 olan bitlerin kesişimini verir. Uç bir örnek olarak, yukarıdaki iki ifadenin 1 olan hiçbir biti diğerini tutmadığı için, kesişimlerinin bütün bitleri 0'dır:

    uint soldaki = 0xaaaaaaaa;
    uint sağdaki = 0x55555555;

    write("  "); göster(soldaki);
    write("  "); göster(sağdaki);
    writeln("& --------------------------------");
    write("  "); göster(soldaki & sağdaki);
  10101010101010101010101010101010 aaaaaaaa 2863311530
  01010101010101010101010101010101 55555555 1431655765
& --------------------------------
  00000000000000000000000000000000 00000000          0
|= işleci, belirli bitleri 1 yapar

İfadelerden bir taraftakini asıl değişken olarak düşünürsek, diğer ifadeyi de 1 yapılacak olan bitleri seçen ifade olarak görebiliriz:

    uint ifade = 0x00ff00ff;
    uint birYapılacakBitler = 0x10001000;

    write("önce       :  "); göster(ifade);
    write("1 olacaklar:  "); göster(birYapılacakBitler);

    ifade |= birYapılacakBitler;
    write("sonra      :  "); göster(ifade);

Etkilenen bitlerin önceki ve sonraki durumlarını maviyle işaretledim:

önce       :  00000000111111110000000011111111 00ff00ff   16711935
1 olacaklar:  00010000000000000001000000000000 10001000  268439552
sonra      :  00010000111111110001000011111111 10ff10ff  285151487

birYapılacakBitler değeri, bir anlamda hangi bitlerin 1 yapılacakları bilgisini taşımış ve asıl ifadenin o bitlerini 1 yapmış ve diğerlerine dokunmamıştır.

&= işleci, belirli bitleri siler

İfadelerden bir taraftakini asıl değişken olarak düşünürsek, diğer ifadeyi de silinecek olan bitleri seçen ifade olarak görebiliriz:

    uint ifade = 0x00ff00ff;
    uint sıfırYapılacakBitler = 0xffefffef;

    write("önce        :  "); göster(ifade);
    write("silinecekler:  "); göster(sıfırYapılacakBitler);

    ifade &= sıfırYapılacakBitler;
    write("sonra       :  "); göster(ifade);

Etkilenen bitlerin önceki ve sonraki durumlarını yine maviyle işaretleyerek:

önce        :  00000000111111110000000011111111 00ff00ff   16711935
silinecekler:  11111111111011111111111111101111 ffefffef 4293918703
sonra       :  00000000111011110000000011101111 00ef00ef   15663343

sıfırYapılacakBitler değeri, hangi bitlerin sıfırlanacakları bilgisini taşımış ve asıl ifadenin o bitlerini sıfırlamıştır.

& işleci, belirli bir bitin 1 olup olmadığını sorgular

Eğer ifadelerden birisinin tek bir biti 1 ise, diğer ifadede o bitin 1 olup olmadığı sorgulanabilir:

    uint ifade = 123456789;
    uint sorgulananBit = 0x00010000;

    göster(ifade);
    göster(sorgulananBit);
    writeln(ifade & sorgulananBit ? "evet, 1" : "1 değil");

Asıl ifadenin hangi bitinin sorgulandığını mavi ile işaretliyorum:

00000111010110111100110100010101 075bcd15  123456789
00000000000000010000000000000000 00010000      65536
evet, 1

Başka bir bitini sorgulayalım:

    uint sorgulananBit = 0x00001000;
00000111010110111100110100010101 075bcd15  123456789
00000000000000000001000000000000 00001000       4096
1 değil

Sorgulama ifadesinde birden fazla 1 kullanarak o bitlerin hepsinin birden asıl ifadede 1 olup olmadıkları da sorgulanabilir.

Sağa kaydırmak ikiye bölmektir

Sağa bir bit kaydırmak, değerin yarıya inmesine neden olur. Bunu yukarıdaki basamak değerleri tablosunda görebilirsiniz: bir sağdaki bit her zaman için soldakinin yarısı değerdedir.

Sağa birden fazla sayıda kaydırmak, o kadar sayıda yarıya bölmek anlamına gelir. Örneğin 3 bit kaydırmak, 3 kere 2'ye bölmek, yani sonuçta 8'e bölmek anlamına gelir:

    uint değer = 8000;

    writeln(değer >> 3);
1000

Ayrıntısına girmediğim ikiye tümleyen sistemi nedeniyle, sağa kaydırmak işaretli türlerde de ikiye bölmektir:

    int değer = -8000;

    writeln(değer >> 3);
-1000
Sola kaydırmak iki katını almaktır

Basamaklar tablosundaki her bitin, bir sağındakinin iki katı olması nedeniyle, bir bit sola kaydırmak 2 ile çarpmak anlamına gelir:

    uint değer = 10;

    writeln(değer << 5);

Beş kere 2 ile çarpmak, 32 ile çarpmanın eşdeğeridir:

320
Bazı kullanımları
Bayraklar

Bayraklar, birbirlerinden bağımsız olarak bir arada tutulan tek bitlik verilerdir. Tek bitlik oldukları için var/yok, olsun/olmasın, geçerli/geçersiz gibi iki değerli kavramları ifade ederler.

Her ne kadar böyle tek bitlik bilgilerin yaygın olmadıklarını söylemiş olsam da, bazen bir arada kullanılırlar. Bayraklar özellikle C kütüphanelerinde yaygındır. C'den uyarlanan D kütüphanelerinde bulunmaları da beklenebilir.

Bayraklar bir enum türünün birbirleriyle örtüşmeyen tek bitlik değerleri olarak tanımlanırlar.

Bir örnek olarak, araba yarışıyla ilgili bir oyun programı düşünelim. Bu programın gerçekçiliği kullanıcı seçimlerine göre belirlensin:

Oyun sırasında bunlardan hangilerinin etkin olacakları bayrak değerleriyle belirtilebilir:

enum Gerçekçilik
{
    benzinBiter        = 1 << 0,
    hasarOluşur        = 1 << 1,
    lastiklerEskir     = 1 << 2,
    lastikİzleriOluşur = 1 << 3
}

Dikkat ederseniz, o enum değerlerinin hepsi de tek bir bitten oluşmakta ve birbirleriyle çakışmamaktadır. Her değer, 1'in farklı sayıda sola ötelenmesi ile elde edilmiştir. Bit değerlerinin şöyle olduklarını görebiliriz:

benzinBiter        : ...0001
hasarOluşur        : ...0010
lastiklerEskir     : ...0100
lastikİzleriOluşur : ...1000

Hiçbir bit diğerlerininkiyle çakışmadığı için, bu değerler | ile birleştirilebilir ve hep birden tek bir değişkende bulundurulabilir. Örneğin yalnızca lastiklerle ilgili ayarların etkin olmaları istendiğinde, değer şöyle kurulabilir:

    Gerçekçilik ayarlar = Gerçekçilik.lastiklerEskir
                          |
                          Gerçekçilik.lastikİzleriOluşur;
    writefln("%b", ayarlar);

Bu iki bayrağın bitleri, aynı değer içinde yan yana bulunurlar:

1100

Daha sonradan, programın asıl işleyişi sırasında, bu bayrakların etkin olup olmadıkları & işleci ile denetlenir:

    if (ayarlar & Gerçekçilik.benzinBiter) {
        // ... benzinin azalmasıyla ilgili kodlar ...
    }

    if (ayarlar & Gerçekçilik.lastiklerEskir) {
        // ... lastiklerin eskimesiyle ilgili kodlar ...
    }

& işlecinin sonucu, ancak belirtilen bayrak ayarlar içinde de 1 ise 1 sonucunu verir.

if koşuluyla kullanılabilmesinin bir nedeninin de Tür Dönüşümleri dersinden hatırlayacağınız gibi, 1 değerinin otomatik olarak true'ya dönüşmesidir. & işlecinin sonucu 0 olduğunda false, farklı bir değer olduğunda da true değerine dönüşür; ve bayrağın etkin olup olmadığı böylece anlaşılmış olur.

Bayrak örneği

Örnek olarak, Phobos'un std.cpuid modülünün kaynak kodlarına bakalım. Bu modül, mikro işlemci ile ilgili çok sayıda bilgiyi öyledir/değildir, destekler/desteklemez, vs. gibi tek bitlik olarak içerir:

import std.cpuid;
import std.stdio;

void main()
{
    writeln(std.cpuid.toString());
}

Bu dersi yazdığım ortamda aldığım çıktı şöyle:

Vendor string:    GenuineIntel
Processor string: Intel(R) Core(TM)2 CPU         T7400  @ 2.16GHz
Signature:        Family=6 Model=15 Stepping=6
Features:         MMX FXSR SSE SSE2 SSE3 SSSE3 AMD64 HTT
Multithreading:   2 threads / 2 cores

O çıktıdaki Features satırı, bu işlemcinin yetenekleri ile ilgili bilgi verir. O satırdaki her sözcük, o yeteneğin bu işlemcide var olduğunu belirtir. Listelenmediği için bilemediğimiz yetenekler bu işlemcide bulunmaz. Örneğin, dmd/src/phobos/std/cpuid.d dosyasının içine bakarak haberdar olduğum IA64 yeteneği, bu ortamda listelenmemektedir.

Mikro işlemcinin yeteneklerini belirleyen bayraklardan bazıları, cpuid.d dosyasında şöyle tanımlanmıştır:

        MMX_BIT = 1<<23,
        FXSR_BIT = 1<<24,
        SSE_BIT = 1<<25,
        SSE2_BIT = 1<<26,
        HTT_BIT = 1<<28,
        IA64_BIT = 1<<30

Bu modüldeki bazı işlevler, belirli bayrakların etkin olup olmadıklarını & işlecinin sonucuna bakarak bildirir:

    bool mmx()                  {return (flags&MMX_BIT)!=0;}

İlginç bir gözlem olarak, & işlecinin sonucu bool'a otomatik olarak değil, açıkça !=0 karşılaştırmasıyla dönüştürülüyor. İki yöntem de doğrudur ve aynı etkiye sahiptir.

Maskeleme

Bazı kütüphanelerde ve sistemlerde, belirli bir tamsayı değer içine birden fazla bilgi yerleştirilmiş olabilir. Örneğin 32 bitlik bir değerin üst 3 bitinin belirli bir anlamı ve alt 29 bitinin başka bir anlamı bulunabilir. Bu veriler, maskeleme yöntemiyle birbirlerinden ayrılabilirler.

Bunun bir örneğini IP adreslerinde görebiliriz. IP adresleri ağ paketleri içinde 32 bitlik tek bir değer olarak bulunurlar. Bu 32 bitin 8'er bitlik 4 parçası, günlük kullanımdan alışık olduğumuz noktalı adres gösterimi değerleridir. Örneğin 192.168.1.2 gibi bir adres, 32 bit olarak 0xc0a80102 değeridir:

c0 == 12 * 16 + 0 = 192
a8 == 10 * 16 + 8 = 168
01 ==  0 * 16 + 1 =   1
02 ==  0 * 16 + 2 =   2

Maske, ilgilenilen veri ile örtüşen sayıda 1'lerden oluşur. Asıl değişken bu maske ile "ve"lendiğinde, yani & işleci ile kullanıldığında verinin değerleri elde edilir. Örneğin 0x000000ff gibi bir maske değeri; diğer ifadenin alt 8 bitini olduğu gibi korur, diğer bitlerini sıfırlar:

    uint değer = 123456789;
    uint maske = 0x000000ff;

    write("değer: "); göster(değer);
    write("maske: "); göster(maske);
    write("sonuç: "); göster(değer & maske);

Maskenin seçerek koruduğu bitleri mavi ile işaretliyorum. Diğer bütün bitler sıfırlanmıştır:

değer: 00000111010110111100110100010101 075bcd15  123456789
maske: 00000000000000000000000011111111 000000ff        255
sonuç: 00000000000000000000000000010101 00000015         21

Bu yöntemi 0xc0a80102 IP adresine ve en üst 8 biti seçecek bir maskeyle uyguladığımızda noktalı gösterimin ilk adres değerini elde ederiz:

    uint değer = 0xc0a80102;
    uint maske = 0xff000000;

    write("değer: "); göster(değer);
    write("maske: "); göster(maske);
    write("sonuç: "); göster(değer & maske);

Maskenin üst bitleri 1 olduğundan, değerin de üst bitleri seçilmiş olur:

değer: 11000000101010000000000100000010 c0a80102 3232235778
maske: 11111111000000000000000000000000 ff000000 4278190080
sonuç: 11000000000000000000000000000000 c0000000 3221225472

Ancak, sonucun onlu gösterimi beklediğimiz gibi 192 değil, 3221225472 olmuştur. Bunun nedeni, maskelenen 8 bitin değerin en sağ tarafına kaydırılmalarının da gerekmesidir. O 8 biti 24 bit sağa kaydırırsak değerlerini doğru olarak elde ederiz:

    uint değer = 0xc0a80102;
    uint maske = 0xff000000;

    write("değer: "); göster(değer);
    write("maske: "); göster(maske);
    write("sonuç: "); göster((değer & maske) >> 24);
değer: 11000000101010000000000100000010 c0a80102 3232235778
maske: 11111111000000000000000000000000 ff000000 4278190080
sonuç: 00000000000000000000000011000000 000000c0        192
Problemler
  1. Verilen IP adresini noktalı gösteriminde yazdıran bir işlev yazın:
  2. string noktalıOlarak(uint ipAdresi)
    {
        // ...
    }
    
    unittest
    {
        assert(noktalıOlarak(0xc0a80102) == "192.168.1.2");
    }
    
  3. Verilen 4 değeri 32 bitlik IP adresine dönüştüren bir işlev yazın:
  4. uint ipAdresi(ubyte bayt3,  // en yüksek değerli bayt
                  ubyte bayt2,
                  ubyte bayt1,
                  ubyte bayt0)  // en düşük değerli bayt
    {
        // ...
    }
    
    unittest
    {
        assert(ipAdresi(192, 168, 1, 2) == 0xc0a80102);
    }
    
  5. Maske oluşturan bir işlev yazın. Belirtilen bit ile başlayan ve belirtilen uzunlukta olan maske oluştursun:
  6. uint maskeYap(int düşükBit, int uzunluk)
    {
        // ...
    }
    
    unittest
    {
        assert(maskeYap(1, 5) ==
               0b_0000_0000_0000_0000_0000_0000_0011_1110);
        //                                             ↑
        //                              başlangıç biti 1,
        //                              ve 5 bitten oluşuyor
    }
    

... çözümler