D.ershane D Programlama Dili Dersleri

alt düzey: [low level], donanıma yakın olanak
bayt sırası: [endianness], veriyi oluşturan baytların bellekte sıralanma düzeni
birlik: [union], birden fazla değişkeni aynı bellek bölgesinde depolayan veri yapısı
büyük soncul: [big endian], değerin üst bitlerini oluşturan baytın bellekte önceki adreste bulunduğu işlemci mimarisi
küçük soncul: [little endian], değerin alt bitlerini oluşturan baytın bellekte önceki adreste bulunduğu işlemci mimarisi
yapı: [struct], başka verileri bir araya getiren veri yapısı
... bütün sözlük

Bölümler
İngilizce Kaynaklar
Diğer



Birlikler

Birlikler, birden fazla üyenin aynı bellek alanını paylaşmalarını sağlarlar. D'ye C dilinden geçmiş olan alt düzey bir olanaktır.

İki fark dışında yapılarla aynı şekilde kullanılır:

Şimdiye kadar çok karşılaştığımız yapı türlerinin kullandıkları bellek alanı, bütün üyelerini barındıracak kadar büyüktü:

struct Yapı
{
    int i;
    double d;
}

// ...

    writeln(Yapı.sizeof);

Dört baytlık int'ten ve sekiz baytlık double'dan oluşan o yapının büyüklüğü 12'dir:

12

Aynı şekilde tanımlanan bir birliğin büyüklüğü ise, üyeleri aynı bellek bölgesini paylaştıkları için, üyelerden en büyüğü için gereken yer kadardır:

union Birlik
{
    int i;
    double d;
}

// ...

    writeln(Birlik.sizeof);

Dört baytlık int ve sekiz baytlık double aynı alanı paylaştıkları için bu birliğin büyüklüğü en büyük üye için gereken yer kadardır:

8

Bunun bellek kazancı sağlayan bir olanak olduğunu düşünmeyin. Aynı bellek alanına birden fazla bilgi sığdırmak olanaksızdır. Birliklerin yararı, aynı bölgenin farklı zamanlarda farklı türden bilgiler için kullanılabilmesidir. Belirli bir anda tek bir üye saklanabilir. Birliklerin yararlarından birisi, her ortamda aynı şekilde çalışmasa da, bilginin parçalarına diğer üyeler yoluyla erişilebilmesidir.

Yukarıdaki birliği oluşturan sekiz baytın bellekte nasıl durduklarını, ve üyeler için nasıl kullanıldıklarını şöyle gösterebiliriz:

       0      1      2      3      4      5      6      7
---+------+------+------+------+------+------+------+------+---
   | <--  int için 4 bayt  --> |                           |
   | <--------------  double için 8 bayt  ---------------> |
---+------+------+------+------+------+------+------+------+---

Ya sekiz baytın hepsi birden double üye için kullanılır, ya da ilk dört bayt int üye için kullanılır ve gerisine dokunulmaz.

Ben örnek olarak iki üye kullandım; birlikleri istediğiniz kadar üye ile tanımlayabilirsiniz. Üyelerin hepsi aynı alanı paylaşırlar.

Aynı bellek bölgesinin kullanılıyor olması ilginç sonuçlar doğurabilir. Örneğin, birliğin bir int ile ilklenmesi ama bir double olarak kullanılması, baştan kestirilemeyecek double değerleri verebilir:

    auto birlik = Birlik(42);    // int üyenin ilklenmesi
    writeln(birlik.d);           // double üyenin kullanılması

int üyeyi oluşturan dört baytın 42 değerini taşıyacak şekilde kurulmaları, double üyenin değerini de etkiler:

4.9547e-270

Mikro işlemcinin bayt sıralarına bağlı olarak int üyeyi oluşturan dört bayt bellekte 0|0|0|42, 42|0|0|0, veya daha başka bir düzende bulunabilir. Bu yüzden yukarıdaki double üyenin değeri başka ortamlarda daha farklı da olabilir.

İsimsiz birlikler

İsimsiz birlikler, içinde bulundukları bir yapının hangi üyelerinin paylaşımlı olarak kullanıldıklarını belirlerler:

struct BirYapı
{
    int birinci;

    union
    {
        int ikinci;
        int üçüncü;
    }
}

// ...

    writeln(BirYapı.sizeof);

Yukarıdaki yapının son iki üyesi aynı alanı paylaşırlar ve bu yüzden yapı, toplam iki int'in büyüklüğü kadar yer tutar. Birlik üyesi olmayan birinci için gereken 4 bayt, ve ikinci ile üçüncü'nün paylaştıkları 4 bayt:

8
Başka bir türün baytlarını ayrıştırmak

Birlikler, türleri oluşturan baytlara teker teker erişmek için kullanılabilirler. Örneğin aslında 32 bitten oluşan IP adreslerinin 4 bölümünü elde etmek için bu 32 biti paylaşan 4 baytlık bir dizi kullanılabilir. Adres değerini oluşturan üye ve dört bayt bir birlik olarak şöyle bir araya getirilebilir:

union IpAdresi
{
    uint değer;
    ubyte[4] baytlar;
}

O birliği oluşturan iki üye, aynı belleği şu şekilde paylaşırlar:

          0            1            2            3
---+------------+------------+------------+------------+---
   | <------  IP adresini oluşturan 32 bit  ---------> |
   | baytlar[0] | baytlar[1] | baytlar[2] | baytlar[3] |
---+------------+------------+------------+------------+---

Bu birlik, daha önceki derslerde 192.168.1.2 adresinin değeri olarak karşılaştığımız 0xc0a80102 ile ilklendiğinde, baytlar dizisinin elemanları teker teker adresin dört bölümüne karşılık gelirler:

void main()
{
    auto adres = IpAdresi(0xc0a80102);

    foreach (i; 0 .. 4) {
        write(adres.baytlar[i], ' ');
    }

    writeln();
}

Adresin bölümleri, bu programı denediğim ortamda alışık olunduğundan ters sırada çıkmaktadır:

2 1 168 192 

Bu, programı çalıştıran mikro işlemcinin küçük soncul olduğunu gösterir. Başka ortamlarda başka sırada da çıkabilir.

Bu örnekte asıl göstermek istediğim, birlik üyelerinin değerlerinin belirsiz olabilecekleridir. Birlikler, ancak ve ancak tek bir üyeleri ile kullanıldıklarında beklendiği gibi çalışırlar. Hangi üyesi ile kurulmuşsa, birlik nesnesinin yaşamı boyunca o üyesi ile kullanılması gerekir. O üye dışındaki üyelere erişildiğinde ne tür değerlerle karşılaşılacağı ortamdan ortama farklılık gösterebilir.

Bu dersle ilgisi olmasa da, std.intrinsic modülünün bswap işlevinin bu konuda yararlı olabileceğini belirtmek istiyorum. bswap, kendisine verilen uint'in baytları ters sırada olanını döndürür. std.system modülündeki endian değerinden de yararlanırsak, küçük soncul bir ortamda olduğumuzu şöyle belirleyebilir ve yukarıdaki IP adresini oluşturan baytları tersine çevirebiliriz:

import std.system;
import std.intrinsic;

// ...

    if (endian == Endian.LittleEndian) {
        adres.değer = bswap(adres.değer);
    }

Endian.LittleEndian değeri sistemin küçük soncul olduğunu, Endian.BigEndian değeri de büyük soncul olduğunu belirtir. Yukarıdaki dönüşüm sonucunda IP adresinin bölümleri alışık olunan sırada çıkacaktır:

192 168 1 2 

Bunu yalnızca birliklerle ilgili bir kullanım örneği olarak gösterdim. Normalde IP adresleriyle böyle doğrudan ilgilenmek yerine, o iş için kullanılan bir kütüphanenin olanaklarından yararlanmak daha doğru olur.

Protokol örneği

Bazı protokollerde, örneğin ağ protokollerinde, bazı baytların anlamı başka bir üye tarafından belirleniyor olabilir. Ağ pakedinin daha sonraki bir bölümü, o üyenin değerine göre farklı bir şekilde kullanılıyor olabilir:

struct Adres
{
    // ...
}

struct BirProtokol
{
    // ...
}

struct BaşkaProtokol
{
    // ...
}

enum ProtokolTürü { birTür, başkaTür }

struct AğPakedi
{
    Adres hedef;
    Adres kaynak;
    ProtokolTürü tür;

    union
    {
        BirProtokol birProtokol;
        BaşkaProtokol başkaProtokol;
    }

    ubyte[] geriKalanı;
}

Yukarıdaki AğPakedi yapısında hangi protokol üyesinin geçerli olduğu tür'ün değerinden anlaşılabilir, programın geri kalanı da yapıyı o değere göre kullanır.

Ne zaman kullanmalı

Bayt paylaşmak gibi kavramlar oldukça alt düzey kabul edilecekleri için, birlikler günlük tasarımlarımızda hemen hemen hiç kullanılmazlar.

Birliklerle C kütüphanelerinde de karşılaşılabilir.