Makrolar Neler Yapamazlar

Bu yazı, Herb Sutter'ın comp.lang.c++.moderated haber grubuna düzensiz aralıklarla gönderdiği Guru Of The Week sorularından 77 numaralı olanından çevrilmiştir. Bu sorular oldukça ilginç tartışmalar başlatır; bir kaç hafta sonra da Herb Sutter sorunun oldukça kapsamlı bir yanıtını verir.

Herb Sutter Guru Of The Week yazılarını daha sonra geliştirerek kitaplar halinde de sunmuştur: http://www.gotw.ca/publications/index.htm (Bu kitaplardan en az birisinin Türkçeye çevrildiğini de öğrendim.)

Her hakkı saklıdır: Copyright © 2004 Herb Sutter

Yazar: Herb Sutter
Çeviren: Ali Çehreli
Yazının İngilizcesi: http://www.gotw.ca/gotw/077.htm


Haftanın Ustası #77

Tanım:

Güçlük derecesi: 4/10

Makrolar neler yapabilirler, neler yapamazlar? Bu konuda bütün derleyiciler aynı fikirde değil...

Soru

Küçük Usta Sorusu:

1. İki parametre alan, onları bildiğimiz < işleci ile karşılaştırarak büyük olanını döndüren basit bir max() önişlemci makrosu yazınız. Böyle makrolar yazmadaki güçlükler nelerdir?

Usta Sorusu:

2. Bir önişlemci makrosu ne oluşturamaz? Neden?

Çözüm:

Bilinen Makro Tuzakları:

Makroların çok sayıdaki zararları dışında dört ana tuzak vardır. Önce tuzaklara odaklanırsak, makro yazarken yapılabilecek yanlışlıklar şunlardır.

1. Parametrelerin etrafına parantez koymayı unutmayın.
  // Örnek 1(a): Parantez hatası #1: parametreler
  // 
  #define max(a,b) a < b ? b : a

Buradaki sorun, a ve b parametrelerinin parantez içine alınmamış olmalarıdır. Makrolar metni olduğu gibi dönüştürdükleri için, beklenmedik sonuçlara neden olabilirler. Örneğin:

  max( i += 3, j )

kullanımı şuna dönüştürülür:

  i += 3 < j ? j : i += 3

Bu da, işleç öncelikleri ve dil kurallarına göre aslında şu anlama gelir:

  i += ((3 < j) ? j : i += 3)

Bu da giderilmesi çok uzun zaman alan hatalara neden olabilir.

2. Bütün ifadenin etrafına parantez koymayı unutmayın.

İlk sorunu gidersek bile, başka bir ince ayrıntının kurbanı olabiliriz:

  // Örnek 1(b): Parantez hatası #2: açılım
  //
  #define max(a,b) (a) < (b) ? (b) : (a)

Bu seferki sorun, bütün ifadenin parantez içine alınmamış olmasıdır. Örneğin:

  k = max( i, j ) + 42;

kullanımı şuna dönüştürülür:

  k = (i) < (j) ? (j) : (i) + 42;

Bu da işleç öncelikleri nedeniyle aslında şu anlama gelir:

  k = (((i) < (j)) ? (j) : ((i) + 42));

Eğer i >= j, k'ye istendiği gibi i+42 değeri atanır. Ama eğer i < j, k'ye j'nin değeri atanır.

3. İşlemlerin birden fazla işletilebileceklerini bilin.

İkinci sorunu da bütün ifadenin etrafına parantezler koyarak giderebiliriz. Bu da bizi bir başka sorunla karşı karşıya bırakır:

  // Örnek 1(c): Parametrelerin birden fazla işletilmeleri
  //
  #define max(a,b) ((a) < (b) ? (b) : (a))

Şimdi, parametrelerden en az birisinin yan etkileri olduğunda neler olabileceğine bakın:

  max( ++i, j )

Eğer ++i >= j karşılaştırması doğruysa, i'nin değeri iki kere arttırılır. Bu da herhalde programcının istediği bir şey değildir:

  ((++i) < (j) ? (j) : (++i))

Aynı şekilde, şu koda bakın:

  max( f(), pi )

Bu da şuna dönüştürülür:

  ((f()) < (pi) ? (pi) : (f()))

Eğer f() >= pi doğruysa, f() iki kere işletilir. Bu da büyük olasılıkla hız kaybına yol açar, ve çoğu durumda da hatalıdır.

Ne yazık ki, ilk iki sorunu çözebiliyor olsak da, bunun önüne geçemeyiz. max'ın makro olarak gerçeklenmesi durumunda bu konuda bir çözüm yoktur.

4. Kapsam konusuna dikkat.

Son olarak, makrolar kapsamlara dikkat etmezler. (Aslında hiçbir şeye dikkat etmezler; bkz. 'Haftanın Ustası 63' [Çevirenin notu: 63 numaralı yazı henüz Türkçeye çevrilmedi. İngilizcesi: http://www.gotw.ca/gotw/063.htm]) Metinleri nerede olduklarına bakmaksızın dönüştürürler. Bu da, makro kullanacaksak onları çok dikkatli olarak adlandırmamız gerektiği anlamına gelir. Özellikle max makrosunun en büyük sorunlarından birisi, standart max() işlev şablonuyla çakışma olasılığının yüksek olmasıdır:

  // Örnek 1(d): İsim çakışması
  //
  #define max(a,b) ((a) < (b) ? (b) : (a))

  #include <algorithm> // eyvah!

Sorun, <algorithm> başlığı içinde şuna benzer bir şey bulunmasıdır:

  template<class T> const T& 
  max(const T& a, const T& b);

Bu da makro tarafından "yardımsever bir şekilde" derlenemez bir hale dönüştürülür:

  template<class T> const T& 
  ((const T& a) < (const T& b) ? (const T& b) : (const T& a));

Eğer makro tanımlarını #include edilen başlıklardan sonraya koyarak (bu zaten iyi bir fikirdir) kolayca bu sorunun önüne geçebileceğinizi düşünüyorsanız; böyle bir makronun, içinde max adlı değişken veya başka şeyler bulunan kendi kodunuza neler yapabileceğini bir düşünün.

Eğer makro yazmak zorunda kalırsanız, başka isimlerle çakışmamaları için onları garip ve yazması zor şekilde adlandırın.

Diğer Makro Sorunları

Makroların yapamadıkları bazı şeyler de vardır:

5. Makrolar özyinelemeli (recursively) olarak kullanılamazlar

Özyinelemeli işlevler yazabiliriz, ama özyinelemeli makrolar yazamayız. C++ standardı 16.3.4/2 paragrafında şöyle der:

[Çevirenin notu: Çevirmeye cesaret edemedim. :o)

If the name of the macro being replaced is found during this scan of the replacement list (not including the rest of the source file's pre- processing tokens), it is not replaced. Further, if any nested replacements encounter the name of the macro being replaced, it is not replaced. These nonreplaced macro name preprocessing tokens are no longer available for further replacement even if they are later (re)examined in contexts in which that macro name preprocessing token would otherwise have been replaced.]

6. Makroların adresleri yoktur.

Serbest ve öge işlevleri gösteren göstergeler oluşturulabilir (örneğin karar verme işlerinde kullanılmaları için), ama makroların adresleri olmadığı için, onları gösteren göstergeler oluşturulamaz. Neden oluşturulamadıkları açık olmalı: makrolar kod değildirler. Makroların kendi varlıkları yoktur, çünkü onlar yüceltilmiş (ama çok da görkemli olmayan) metin dönüştürme kurallarıdır.

7. Makrolar hata ayıklayıcılara uygun değildir

Makroların daha derleyicilere görme fırsatı vermeden kodu değiştirmeleri, ve bu nedenle değişken ve başka adları mahvetme olasılıkları bir yana; hata ayıklama uğraşları sırasında makrolar adım adım da işletilemezler.

Bilimcilerin neden laboratuar makroları yerine avukatlar üzerinde deney yapmaya başladıkları fıkrasını duydunuz mu? Çünkü...

Bazı şeyleri makrolar bile yapmazlar.

[Çevirenin notu: Avukatlara sataşan sayısız şakadan birisi gibi duran bu fıkranın asıl halinde herhalde makro yerine fare kullanılıyor olmalı. İngilizcesi de şöyle:

Have you heard the one about the scientists who started experimenting on lawyers instead of on laboratory macros? It was because...

There Are Some Things Even a Macro Just Won't Do.]

Makroları kullanmak için geçerli olan nedenler vardır (bkz. 'Haftanın Ustası 32' [Çevirenin notu: 32 numaralı yazı henüz Türkçeye çevrilmedi. İngilizcesi: http://www.gotw.ca/gotw/032.htm]), ama bunların sınırı vardır. Bu da bizi son soruya getiriyor:

2. Bir önişlemci makrosu ne oluşturamaz? Neden?

Standardın 2.1 bölümü kod dönüştürme (derleme) aşamalarını tanımlar. Önişlemci bildirimleri ve makro açılımları dördüncü aşamada gerçekleşir. Bu da, standarda uyumlu derleyicilerde makroların şunları oluşturamayacaklarını gösterir:

CUJ dergisinde çıkan bir makale[1], makro kullanarak açıklamalar oluşturulabileceğini öne sürmüştü:

  #define ACIKLAMA BOLU(/)
  #define BOLU(s) /##s

Bu ne standarttır, ne de taşınabilir; ama bazı derleyicilerde çalışır. Neden? Çünkü o derleyiciler kod dönüştürme aşamalarını doğru olarak gerçeklemezler. Denediğim dört derleyiciden aldığım sonuçlar şöyle:

Derleyici

Açıklama Makrosunu Kabul Eder mi?

Microsoft Visual Studio.NET 
(Visual C++ version 7), beta 1

Evet (yanlış)

Borland BCC 5.5.1

Evet (yanlış)

GCC 2.95.2

Hayır (doğru)

Comeau 4.2.44

Hayır (doğru)

Notlar:

1. M. Timperley. "A C/C++ Comment Macro" (C/C++ Users Journal, 19(1), January 2001).
Copyright © 2004 Herb Sutter