Koşullu Derleme
Programın bazı bölümlerinin belirli koşullara bağlı olarak farklı derlenmesi veya hiç derlenmemesi istenebilir. D'nin koşullu derleme olanakları bu konuda kullanılır.
Bu koşullar yalnızca derleme zamanında değerlendirilirler; programın çalışması sırasında etkileri yoktur. Çalışma zamanında etkili olan if
, for
, while
gibi D olanakları koşullu derleme olanakları değildir.
Aslında önceki bölümlerde koşullu derleme olarak kabul edilebilecek olanaklarla karşılaşmıştık:
- Birim testi bloklarındaki kodlar yalnızca
‑unittest
derleyici seçeneği kullanıldığında derlenir ve çalıştırılır. - Sözleşmeli programlama olanakları olan
in
,out
, veinvariant
blokları-release
seçeneği kullanılmadığı zaman etkindir.
Yukarıdakiler programın doğruluğunu arttırma amacına yönelik yardımcı olanaklar olarak görülebilir. Derleyici seçeneklerine bağlı olarak kullanılıp kullanılmamaları, programın asıl davranışını zaten değiştirmemelidir.
D'nin koşullu derlemeyi destekleyen ve bütünüyle bu amaç için tasarlanmış olan başka olanakları da vardır:
debug
version
static if
is
ifadesi__traits
is
ifadesini bir sonraki bölümde göreceğiz.
debug
Program geliştirme aşamasında yararlı olan bir olanak debug
belirtecidir. Bu belirteçle işaretlenmiş olan ifadeler ve deyimler yalnızca derleyiciye -debug
seçeneği verildiğinde etkilidir:
debug koşullu_derlenen_bir_ifade; debug { // ... koşullu derlenen ifadeler ve deyimler ... } else { // ... diğer durumda derlenen ifadeler ve deyimler ... }
else
bloğu isteğe bağlıdır.
Yukarıdaki tek ifade de blok içindeki ifadeler de ancak -debug
derleyici seçeneği etkin olduğunda derlenir.
Şimdiye kadarki programların hemen hemen hepsinde programın nasıl işlediğini gösteren ve çıkışa "ekliyorum", "çıkartıyorum" gibi mesajlar yazdıran satırlar kullandık. Algoritmaların işleyişlerini böylece görsel hale getiriyor ve olası hatalarını bulabiliyorduk. "debug", hata gidermek anlamına gelir ve bu konuda yararlıdır.
Bunun bir örneği olarak Şablonlar bölümünde gördüğümüz ikiliAra
işlevine bakalım. O algoritmanın açıklama satırlarını çıkartıyorum ve bilerek hatalı olarak yazıyorum:
import std.stdio; // DİKKAT! Bu algoritma hatalıdır size_t ikiliAra(const int[] değerler, int değer) { if (değerler.length == 0) { return size_t.max; } immutable ortaNokta = değerler.length / 2; if (değer == değerler[ortaNokta]) { return ortaNokta; } else if (değer < değerler[ortaNokta]) { return ikiliAra(değerler[0 .. ortaNokta], değer); } else { return ikiliAra(değerler[ortaNokta + 1 .. $], değer); } } void main() { auto sayılar = [ -100, 0, 1, 2, 7, 10, 42, 365, 1000 ]; auto indeks = ikiliAra(sayılar, 42); writeln("Konum: ", indeks); }
Yukarıdaki program 42'nin aslında 6 olan konumunu yanlış bildirir:
Konum: 1 ← yanlış sonuç
Bu hatayı bulmanın bir yolu, işlevin önemli noktalarına işlemler hakkında bilgiler veren satırlar eklemektir:
size_t ikiliAra(const int[] değerler, int değer) { writeln(değerler, " içinde ", değer, " arıyorum"); if (değerler.length == 0) { writeln(değer, " bulunamadı"); return size_t.max; } immutable ortaNokta = değerler.length / 2; writeln("bakılan konum: ", ortaNokta); if (değer == değerler[ortaNokta]) { writeln(değer, ", ", ortaNokta, " konumunda bulundu"); return ortaNokta; } else if (değer < değerler[ortaNokta]) { writeln("ilk yarıda olması gerek"); return ikiliAra(değerler[0 .. ortaNokta], değer); } else { writeln("son yarıda olması gerek"); return ikiliAra(değerler[ortaNokta + 1 .. $], değer); } }
Programın şimdiki çıktısı algoritmanın işleyiş adımlarını da gösterir:
[-100,0,1,2,7,10,42,365,1000] içinde 42 arıyorum bakılan konum: 4 son yarıda olması gerek [10,42,365,1000] içinde 42 arıyorum bakılan konum: 2 ilk yarıda olması gerek [10,42] içinde 42 arıyorum bakılan konum: 1 42, 1 konumunda bulundu Konum: 1
Hatanın bu çıktıdan yararlanılarak bulunduğunu ve giderildiğini varsayalım. Hata giderildikten sonra artık writefln
satırlarına gerek kalmaz, üstelik silinmeleri gerekir. Buna rağmen, o satırları silmek de bir israf olarak görülebilir çünkü belki de ileride tekrar gerekebilirler.
Onun yerine, bu satırların başına debug
anahtar sözcüğü yazılabilir:
debug writeln(değer, " bulunamadı");
O satırlar artık yalnızca -debug
derleyici seçeneği kullanıldığında etkin olacaktır:
dmd deneme.d -ofdeneme -w -debug
Böylece programın normal işleyişi sırasında çıktıya hiçbir bilgi yazdırılmayacak, bir hata görüldüğünde ise -debug
kullanılarak algoritmanın işleyişi hakkında bilgi alınabilecektir.
debug(isim)
debug
belirtecinin çok yerde kullanılması durumunda programın çıktısı çok kalabalıklaşabilir. Böyle durumlarda debug
belirteçlerine isimler verebilir ve onların yalnızca komut satırında belirtilenlerinin etkinleşmelerini sağlayabiliriz:
debug(ikili_arama) writeln(değer, " bulunamadı");
İsimli debug
belirteçlerini etkinleştirmek için komut satırında -debug=isim
yazılır:
dmd deneme.d -ofdeneme -w -debug=ikili_arama
İsimli debug
belirteçleri de birden fazla ifade için kullanılabilir:
debug(ikili_arama) { // ... koşullu derlenen ifadeler ve deyimler ... }
Aynı anda birden çok isimli debug
belirteci de belirtilebilir:
$ dmd deneme.d -ofdeneme -w -debug=ikili_arama -debug=yigin_yapisi
O durumda hem ikili_arama
, hem de yigin_yapisi
isimli debug
blokları etkin olur.
debug(düzey)
Bazen debug
belirteçlerine isimler vermek yerine, hata ayıklama düzeylerini belirleyen sayısal değerler verilebilir. Örneğin, artan her düzey daha derinlemesine bilgi elde etmek için yararlı olabilir:
debug import std.stdio; void birİşlev(string dosyaİsmi, int[] sayılar) { debug(1) writeln("birİşlev işlevine girildi"); debug(2) { writeln("işlev parametreleri: "); writeln(" isim: ", dosyaİsmi); foreach (i, sayı; sayılar) { writefln(" %4s: %s", i, sayı); } } // ... asıl işlemler ... }
Derleyiciye bildirilen debug
düzeyi, o düzey ve daha düşük olanlarının etkinleşmesini sağlar:
$ dmd deneme.d -ofdeneme -w -debug=1 $ ./deneme birİşlev işlevine girildi
Daha derinlemesine bilgi almak istendiğinde:
$ dmd deneme.d -ofdeneme -w -debug=2 $ ./deneme birİşlev işlevine girildi işlev parametreleri: isim: deneme.txt 0: 10 1: 4 2: 100
version(isim)
, ve version(düzey)
version
, debug
olanağına çok benzer ve kod içinde aynı biçimde kullanılır:
version(denemeSürümü) /* ... bir ifade ... */; version(okulSürümü) { // ... okullara satılan sürümle ilgili ifadeler ... } else { // ... başka sürümlerle ilgili ifadeler ... } version(1) birDeğişken = 5; version(2) { // ... sürüm 2 ile ilgili bir olanak ... }
Bütünüyle aynı biçimde çalışıyor olsa da, debug
'dan farkı, programın farklı sürümlerini oluşturma amacıyla kullanılmasıdır.
Yine debug
'da olduğu gibi, aynı anda birden fazla version
bloğu etkinleştirilebilir:
$ dmd deneme.d -ofdeneme -w -version=kayit -version=hassas_hesap
Bazı version
isimleri hazır olarak tanımlıdır. Tam listesini Conditional Compilation sayfasında bulacağınız bu isimleri aşağıdaki tabloda özetliyorum:
Derleyici | DigitalMars GNU LDC SDC |
İşletim sistemi | Windows Win32 Win64 linux OSX Posix FreeBSD OpenBSD NetBSD DragonFlyBSD BSD Solaris AIX Haiku SkyOS SysV3 SysV4 Hurd |
Mikro işlemci sonculluğu | LittleEndian BigEndian |
Derleyici seçenekleri | D_Coverage D_Ddoc D_InlineAsm_X86 D_InlineAsm_X86_64 D_LP64 D_PIC D_X32 D_HardFloat D_SoftFloat D_SIMD D_Version2 D_NoBoundsChecks unittest assert |
Mikro işlemci mimarisi | X86 X86_64 |
Platform | Android Cygwin MinGW ARM ARM_Thumb ARM_Soft ARM_SoftFP ARM_HardFP ARM64 PPC PPC_SoftFP PPC_HardFP PPC64 IA64 MIPS MIPS32 MIPS64 MIPS_O32 MIPS_N32 MIPS_O64 MIPS_N64 MIPS_EABI MIPS_NoFloat MIPS_SoftFloat MIPS_HardFloat SPARC SPARC_V8Plus SPARC_SoftFP SPARC_HardFP SPARC64 S390 S390X HPPA HPPA64 SH SH64 Alpha Alpha_SoftFP Alpha_HardFP |
... | ... |
İki tane de özel version
ismi vardır:
-
none
: Bu isim hiçbir zaman tanımlı değildir; kod bloklarını etkisizleştirmek için kullanılabilir. -
all
: Bu isim her zaman tanımlıdır;none
'ın tersi olarak kullanılır.
O tanımlardan yararlanarak programınızın farklı olanaklarla derlenmesini sağlayabilirsiniz. Kullanım örneği olarak std.ascii
modülünde tanımlı olan newline
'a bakalım. Satır sonu anlamına gelen kodları belirleyen newline
dizisi, üzerinde derlenmekte olduğu işletim sistemine göre farklı kodlardan oluşmaktadır:
version(Windows) { immutable newline = "\r\n"; } else version(Posix) { immutable newline = "\n"; } else { static assert(0, "Unsupported OS"); }
debug
'a ve version
'a isim atamak
debug
ve version
'a sanki değişkenmişler gibi isim atanabilir. Değişkenlerden farklı olarak, atama işlemi değer değiştirmez, değer olarak belirtilen debug
veya version
ismini de etkinleştirir.
import std.stdio; debug(hepsi) { debug = ikili_arama; debug = yigin_yapisi; version = denemeSürümü; version = okulSürümü; } void main() { debug(ikili_arama) writeln("ikili_arama etkin"); debug(yigin_yapisi) writeln("yigin_yapisi etkin"); version(denemeSürümü) writeln("deneme sürümü"); version(okulSürümü) writeln("okul sürümü"); }
Yukarıdaki koddaki debug(hepsi)
bloğu içindeki atamalar o isimlerin de etkinleşmelerini sağlar. Böylece bu program için derleme satırında dört debug
ve version
seçeneği ayrı ayrı seçilebileceği gibi, -debug=hepsi
kullanıldığında; ikili_arama
, yigin_yapisi
, denemeSürümü
, ve okulSürümü
sanki komut satırında bildirilmişler gibi etkinleşirler:
$ dmd deneme.d -ofdeneme -w -debug=hepsi
$ ./deneme
ikili_arama etkin
yigin_yapisi etkin
deneme sürümü
okul sürümü
static if
Programın çalışması sırasındaki kararlarda çok kullandığımız if
koşulunun derleme zamanındaki eşdeğeri static if
'tir.
if
koşulunda olduğu gibi, static if
koşulu da bir mantıksal ifade ile kullanılır. static if
bloğundaki kodlar bu mantıksal ifade true
olduğunda derlenir ve programa dahil edilir, false
olduğunda ise o kodlar sanki hiç yazılmamışlar gibi etkisizleşirler. Yine if
'e benzer şekilde, else static if
ve else
blokları da bulunabilir.
Derleme zamanında işletildiğinden, mantıksal ifadenin sonucunun derleme zamanında biliniyor olması şarttır.
static if
her kapsamda kullanılabilir: Modül dosyasında en üst düzeyde veya yapı, sınıf, şablon, işlev, vs. kapsamlarında. Koşul sağlandığında blok içindeki kodlar yazıldıkları satırlarda programa dahil edilirler.
static if
şablon tanımlarında, is
ifadesi ile birlikte, ve __traits
olanağı ile çok kullanılır.
static if
'in is
ifadesi ile birlikte kullanım örneklerini bir sonraki bölümde göreceğiz. Burada çok basit bir şablon tanımında kullanalım:
import std.stdio; struct VeriYapısı(T) { static if (is (T == float)) { alias SonuçTürü = double; } else static if (is (T == double)) { alias SonuçTürü = real; } else { static assert(false, T.stringof ~ " desteklenmiyor"); } SonuçTürü işlem() { writefln("%s için sonuç türü olarak %s kullanıyorum.", T.stringof, SonuçTürü.stringof); SonuçTürü sonuç; // ... return sonuç; } } void main() { auto f = VeriYapısı!float(); f.işlem(); auto d = VeriYapısı!double(); d.işlem(); }
VeriYapısı
yalnızca float
ve double
türleriyle kullanılabilen bir tür. İşlem sonucunu hep bir adım daha hassas olan türde gerçekleştirmek için float
ile kullanıldığında double
, double
ile kullanıldığında ise real
seçiyor:
float için sonuç türü olarak double kullanıyorum. double için sonuç türü olarak real kullanıyorum.
static if
zincirleri oluştururken else static if
yazmak gerektiğine dikkat edin. Yanlışlıkla else if
yazıldığında, static if
'in else
bloğu olarak if
kullanılacak demektir ve if
de doğal olarak çalışma zamanında işletilecektir.
static assert
Aslında bir koşullu derleme olanağı olarak kabul edilmese de bu olanağı static if
'e benzerliği nedeniyle burada tanıtmaya karar verdim.
Çalışma zamanında kullanmaya alıştığımız assert
'le aynı biçimde ama derleme zamanında işletilir. Mantıksal ifadesi false
olduğunda derlemenin bir hata ile sonlandırılmasını sağlar.
static if
gibi static assert
de programda herhangi bir kapsamda bulunabilir.
static assert
kullanımının bir örneğini yukarıdaki programda gördük: float
veya double
türlerinden başka bir tür belirtildiğinde derleme static assert(false)
nedeniyle sonlanır:
auto i = VeriYapısı!int();
Derleme hatası:
Error: static assert "int desteklenmiyor"
Başka bir örnek olarak belirli bir algoritmanın yalnızca belirli büyüklükteki türlerle doğru olarak çalışabildiğini varsayalım. Bu koşulu bir static assert
ile denetleyebiliriz:
T birAlgoritma(T)(T değer) { // Bu algoritma ancak büyüklüğü dördün katı olan türlerle // çalışabilir static assert((T.sizeof % 4) == 0); // ... }
O işlev şablonu örneğin char
ile çağrıldığında programın derlenmesi bir hata ile sonlanır:
Error: static assert (1LU == 0LU) is false
Böylece algoritmanın uygunsuz bir türle kullanılmasının ve olasılıkla hatalı çalışmasının önüne geçilmektedir.
static assert
de is
ifadesi dahil olmak üzere derleme zamanında oluşturulabilen her mantıksal ifade ile kullanılabilir.
Tür nitelikleri
__traits
anahtar sözcüğü ve std.traits
modülü türlerin nitelikleriyle ilgili bilgileri derleme zamanında edinmeye yarar.
__traits
, derleyicinin koddan edinmiş olduğu bilgileri sorgulamaya yarar. Söz dizimi aşağıdaki gibidir:
__traits(sözcük, parametreler)
sözcük, __traits
'in hangi amaçla kullanıldığını belirtir. parametreler ise bir veya daha fazla sayıda olmak üzere tür ismi veya ifadedir. Parametrelerin anlamları kullanılan sözcüğe bağlıdır.
__traits
'in sunduğu bilgiler dilin başka olanakları tarafından edinilemeyen ve çoğunlukla derleyicinin toplamış olduğu bilgilerdir. Bu bilgiler özellikle şablon kodlarında ve koşullu derleme sırasında yararlıdır.
Örneğin, "aritmetik mi" anlamına gelen isArithmetic
, T
gibi bir şablon parametresinin aritmetik bir tür olup olmamasına göre farklı kod üretmek için kullanılabilir:
static if (__traits(isArithmetic, T)) { // ... aritmetik bir türmüş ... } else { // ... değilmiş ... }
std.traits
modülü de tür nitelikleriyle ilgili bilgileri şablon olanakları olarak yine derleme zamanında sunar. Örneğin, std.traits.isSomeChar
, kendisine verilen şablon parametresi bir karakter türü olduğunda true
üretir:
import std.traits; // ... static if (isSomeChar!T) { // ... herhangi bir karakter türüymüş ... } else { // ... bir karakter türü değilmiş ... }
Daha fazla bilgi için __traits
belgesine ve std.traits
belgesine başvurabilirsiniz.
Özet
debug
olarak tanımlanmış olan kodlar yalnızca-debug
derleyici seçeneği etkin olduğunda programa dahil edilirler.version
ile tanımlanmış olan kodlar programın-version
derleme seçeneği ile belirlenen sürümüne dahil olurlar.static if
derleme zamanında işleyenif
deyimi gibidir; kodların derleme zamanındaki koşullara göre programa dahil edilmesini sağlar.static assert
programla ilgili varsayımları derleme zamanında denetler.__traits
vestd.traits
türler hakkında derleme sırasında bilgi edinmeye yarar.