İngilizce Kaynaklar


Diğer




D Olanaklarının C Önişlemcisiyle [preprocessor] Karşılaştırılması

Not: Bu bir D1 karşılaştırmasıdır. Modern D'nin başka farklılıkları da var.

Bu sayfadaki bilgiler Digital Mars'ın sitesindeki aslından alınmıştır.

C'nin yaratıldığı günlerde derleyici teknolojisi henüz çok geriydi. Derleme işleminin öncesine metin makroları işleyen bir adım eklemek, çok kolay bir ek olması yanında programcıya büyük kolaylıklar da sunmuştu. Fakat programların boyutları ve karmaşıklıkları arttıkça önişlemci olanaklarının üstesinden gelinemeyecek sorunları da ortaya çıkmaya başlamıştır. D'de önişlemci bulunmaz, ama başka olanakları sayesinde aynı sorunları daha etkin olarak çözer.



Başlık dosyaları
C

Başlık dosyalarını oldukları gibi metin halinde #include etmek, C ve C++'da çok gündelik işlemlerdir. Bu yüzden derleyici başlık dosyalarında bulunan on binlerce satırı her seferinde baştan derlemek zorunda kalır. Başlık dosyalarının asıl yapmaya çalıştıkları; metin olarak eklenmek yerine, derleyiciye bazı isimleri bildirmektir. Bu D'de import deyimiyle yapılır. Bu durumda daha önceden zaten derlenmiş olan isimler öğrenilmiş olurlar. Böylelikle başlıkların birden fazla sayıda eklenmelerini önlemek için #ifdef'ler, #pragma once'lar, veya kullanımları çok kırılgan olan precompiled headers gibi çözümler gerekmemiş olur.

#include <stdio.h>
D
import std.c.stdio;


#pragma once
C

Başlık dosyalarının birden fazla #include edilmelerini önlemek gerekir. Bunun için başlık dosyalarına ya şu satır eklenir:

#pragma once

ya da daha taşınabilir olarak:

#ifndef __STDIO_INCLUDE
#define __STDIO_INCLUDE
... dosya içeriği
#endif
D

Tamamen gereksizdir; çünkü D, isimleri sembol olarak ekler. import bildirimi ne kadar çok yapılmış olursa olsun, isimler yalnızca bir kere eklenirler.



#pragma pack
C

Yapı üyelerinin bellekte nasıl yerleştirileceklerini belirler.

D

D sınıflarında üyelerin bellek yerleşimlerine karışmak gerekmez; derleyici üyelerin ve hatta yerel değişkenlerin sıralarını istediği gibi değiştirme hakkına sahiptir; ama dış veri yapılarına denk olmaları gerekebileceği için, D'de yapı üyelerinin yerleşimleri de ayarlanabilir:

struct Foo
{
        align (4):        // 4 baytlık yerleşim kullanılır
        ...
}


Makrolar

C'de önişlemci makroları esneklik de sağlayan çok güçlü olanaklardır; ama zayıflıkları da vardır:

Makroların temel kullanımları ve bunların D'deki karşılıkları şöyledir:

  1. Sabit değerler
    C
    #define DEGER        5
    
    D
    const int DEĞER = 5;
    
  2. Bir dizi sabit değer veya bayrak değerleri tanımlamak
    C
    int bayraklar:
    #define BAYRAK_X        0x1
    #define BAYRAK_Y        0x2
    #define BAYRAK_Z        0x4
    ...
    bayraklar |= BAYRAK_X;
    
    D
    enum BAYRAK { X = 0x1, Y = 0x2, Z = 0x4 };
    BAYRAK bayraklar;
    ...
    bayraklar |= BAYRAK.X;
    
  3. ASCII (char) veya evrensel (wchar) karakterlere göre derlemek
    C
    #if UNICODE
        #define dchar        wchar_t
        #define TEXT(s)      L##s
    #else
        #define dchar        char
        #define TEXT(s)      s
    #endif
    
    ...
    dchar m[] = TEXT("merhaba");
    
    D
    dchar[] m = "merhaba";
    
  4. Eski derleyicilere uygun olarak derleme
    C
    #if PROTOTYPES
    #define P(p)        p
    #else
    #define P(p)        ()
    #endif
    int func P((int x, int y));
    
    D

    D derleyicisinin açık kodlu olması farklı derleyici söz dizimleri sorunlarını da önler.

  5. Türlere yeni isimler
    C
    #define INT         int
    
    D
    alias int INT;
    
  6. Bildirimler ve tanımlar için tek başlık dosyası kullanmak
    C
    #define EXTERN extern
    #include "bildirimler.h"
    #undef EXTERN
    #define EXTERN
    #include "bildirimler.h"
    

    bildirimler.h dosyasında:

    EXTERN int foo;
    
    D

    D'de bildirim ve tanım aynıdır; aynı kaynak koddan hem bildirim hem de tanım elde etmek için extern anahtar sözcüğünü kullanmak gerekmez.

  7. Hızlı iç [inline] fonksiyonlar
    C
    #define X(i)        ((i) = (i) / 3)
    
    D
    int X(ref int i) { return i = i / 3; }
    

    Derleyici eniyileştiricisi fonksiyonu zaten iç fonksiyon olarak derler.

  8. assert fonksiyonu için dosya ve satır bilgisi
    C
    #define assert(e)  ((e) || _assert(__LINE__, __FILE__))
    
    D

    assert dilin bir iç olanağıdır. Bu, derleyici eniyileştiricisinin _assert()'in dönüş türünün olmadığı bilgisinden yararlanmasını da sağlar.

  9. Fonksiyon çağırma çeşitleri [calling conventions]
    C
    #ifndef _CRTAPI1
    #define _CRTAPI1 __cdecl
    #endif
    #ifndef _CRTAPI2
    #define _CRTAPI2 __cdecl
    #endif
    
    int _CRTAPI2 fonksiyon();
    
    D

    Fonksiyonların nasıl çağrıldıkları gruplar halinde belirlenebilir:

    extern (Windows)
    {
        int bir_fonksiyon();
        int baska_bir_fonksiyon();
    }
    
  10. __near ve __far işaretçi yazımını gizlemek
    C
    #define LPSTR        char FAR *
    
    D

    D'de 16 bitlik kod, işaretçi boyutlarının karışık kullanımı, değişik işaretçi türleri desteklenmez.

  11. Türden bağımsız temel kodlama
    C

    Hangi fonksiyonun kullanılacağını metni dönüştürerek belirlemek:

    #ifdef UNICODE
    int getValueW(wchar_t *p);
    #define getValue getValueW
    #else
    int getValueA(char *p);
    #define getValue getValueA
    #endif
    
    D

    D isimlere yeni takma isimler vermeyi destekler:

    version (UNICODE)
    {
        int getValueW(wchar[] p);
        alias getValueW getValue;
    }
    else
    {
        int getValueA(char[] p);
        alias getValueA getValue;
    }
    



Derlemeyi duruma göre yönlendirmek [conditional compilation]
C

Derlemeyi yönlendirebilmek önişlemcinin güçlü yönlerindendir ama zayıflıkları da vardır:

D

Derlemeyi yönlendirmek D'de dil tarafından sağlanır:

  1. Farklı sürüm kodlarını farklı modüllere yerleştirmek
  2. Hata ayıklamaya yardımcı olan debug deyimi
  3. Tek kaynak koddan programın farklı sürümlerini üretmeye yarayan version deyimi
  4. if(0) deyimi
  5. Bir grup kod satırını bir grup açıklama satırına dönüştüren /+ +/ açıklamaları


Kod tekrarını azaltmak
C

Bazen fonksiyonlar içinde tekrarlanan kodlarla karşılaşılabilir. Tekrarlanan kodların ayrı bir fonksiyona taşınması daha doğal olsa da, fonksiyon çağrısı bedelini önlemek için bazen makrolar kullanılır. Örneğin şu bayt kod yorumlayıcısı koduna bakalım:

unsigned char *ip;      // byte code instruction pointer
int *stack;
int spi;                // stack pointer
...
#define pop()     (stack[--spi])
#define push(i)   (stack[spi++] = (i))
while (1)
{
    switch (*ip++)
    {
        case ADD:
            op1 = pop();
            op2 = pop();
            result = op1 + op2;
            push(result);
            break;

        case SUB:
        ...
    }
}

Bu kodda çeşitli sorunlar vardır:

  1. Makrolar ifade olarak açıldıkları için değişken tanımlayamazlar. Örneğin program yığıtından taşmaya karşı denetim eklemek gibi bir iş oldukça güçtür.
  2. Sembol tablosunda bulunmadıklarından, etkileri tanımlandıkları fonksiyonun dışına da taşar
  3. Makro parametreleri metin olarak geçirildiklerinden makronun parametreyi gereğinden fazla işletmemeye dikkat etmesi ve kodun anlamını korumak için parametreyi parantezler içine alması gerekir
  4. Hata ayıklayıcı makrolardan habersizdir
D

D'de kapsam fonksiyonları kullanılır:

ubyte* ip;           // byte code instruction pointer
int[] stack;         // operand stack
int spi;             // stack pointer
...

int pop()        { return stack[--spi]; }
void push(int i) { stack[spi++] = i; }

while (1)
{
    switch (*ip++)
    {
        case ADD:
            op1 = pop();
            op2 = pop();
            push(op1 + op2);
            break;

        case SUB:
        ...
    }
}

Bunun çözdüğü sorunlar şunlardır:

  1. Kapsam fonksiyonları D fonksiyonları kadar güçlü olanaklara sahiptirler
  2. Kapsam fonksiyonunun ismi, tanımlandığı kapsamla sınırlıdır
  3. Parametreler değer olarak geçirildikleri için birden fazla işletilmeleri gibi sorunlar yoktur
  4. Kapsam fonksiyonları hata ayıklayıcı tarafından görülebilirler

Ek olarak, kapsam fonksiyonu çağrıları eniyileştirici tarafından yok edilince çağrı bedeli de önlenmiş olur.



#error ve derleme zamanı denetimleri

Derleme zamanında yapılan denetimler sırasında derlemenin bir hata mesajıyla sonlandırılması sağlanabilir.

C

Bir yöntem, #error önişlemci komutunu kullanmaktır:

#if FOO || BAR
    ... derlenecek kod ...
#else
#error "FOO veya BAR'dan en az birisi gerekmektedir"
#endif

Önişlemci yetersizlikleri burada da geçerlidir: yalnızca tamsayı ifadeler geçerlidir, tür değişimlerine izin verilmez, sizeof kullanılamaz, sabitler kullanılamaz, vs.

Bunların bazılarını gidermek için bir static_assert (derleme zamanı denetimi) makrosu kullanılabilir (M.Wilson tarafından):

#define static_assert(_x)            \
do {                                 \
  typedef int ai[(_x) ? 1 : 0];      \
} while(0)

Kullanımı şöyledir:

void foo(T t)
{
    static_assert(sizeof(T) < 4);
    ...
}

Bu, koşul yanlış (false) olduğunda bir yazım hatasına dönüşür ve derleme bir hata mesajıyla sonlanır. [Çevirenin notu: false olduğunda ai dizisinin boyu 0 olur; bu da C kurallarına göre yasal değildir.]

D

static assert D'de bir dil olanağıdır ve bildirimlerin ve deyimlerin kullanılabildikleri her yerde kullanılabilir. Örnek:

version (FOO)
{
    class Bar
    {
        const int x = 5;
        static assert(Bar.x == 5 || Bar.x == 6);

        void foo(T t)
        {
            static assert(T.sizeof < 4);
            ...
        }
    }
}
else version (BAR)
{
    ...
}
else
{
    static assert(0);        // yasal olmayan bir sürüm
}


Şablon katmaları [mixin]

D'nin şablon katma olanağı, dışarıdan bakıldığında tıpkı C'nin önişlemcisinde olduğu gibi bir metin yerleştirme olanağına benzer. Yerleştirilen metin yine açıldığı yerde ayrıştırılır [parse], ama katmaların makrolardan üstün tarafları vardır:

  1. Katmalar söz dizimine uyarlar ve ayrıştırma ağaçlarında [parse tree] yer alırlar. Makrolar ise hiçbir düzene bağlı olmadan metin yerleştirirler.
  2. Katmalar aynı dilin parçalarıdırlar. Makrolar ise C++'nın üstünde ayrı bir dildir; kendine ait ifade kuralları, türleri, sembol tabloları, ve kapsam kuralları vardır.
  3. Katmalar kısmi özelleme [partial specialization] kurallarına göre seçilirler; makrolarda aşırı yükleme yoktur.
  4. Katmalar kapsam oluştururlar; makrolar oluşturmazlar.
  5. Katmalar söz diziminden anlayan araç programlarla uyumludurlar; makrolar değildirler.
  6. Katmalarla ilgili bilgiler ve sembol tabloları hata ayıklayıcılara geçirilirler; makrolar metin yerleştirildikten sonra yok olurlar.
  7. Katmalarda birden fazla tanımın uygun olduğu durumlarda hangisinin kullanılacağının kuralları bellidir; makro tanımları çakışmak zorundadırlar.
  8. Katma isimlerinin başka katma isimleriyle çakışmaları standart bir algoritma yoluyla önlenmiştir; makrolarda kelime birleştirme yoluyla elle yapılır.
  9. Katma argümanları tek bir kere işletilirler; makro açılımlarında ise argümanlar kullanıldıkları her sefer işletilirler ve bu yüzden bulunması güç hatalara yol açabilirler.
  10. Katma argümanlarının etrafına koruyucu parantezler koymak gerekmez.
  11. Katmalar istendiği kadar uzunlukta normal D kodu olarak yazılırlar; makrolarda olduğu gibi satırları \ karakteriyle sonlandırmak gerekmez, // açıklamaları koyamamak gibi sorunlar yoktur, vs.
  12. Katmalar başka katmalar tanımlayabilirler; makrolar makro tanımlayamazlar.