1.Amaç
2.Variadic Macrolar
3. Sonuç

1.Amaç

Bu belgenin amacı Variadic Macroları anlatmaktır. Örnek uygulamalar ile konu pekiştirilmiştir.

2.Variadic Macrolar

Variadic fonsiyonlar ile ilgili birçok matematiksel ve mantıksal işlemler vardır. Örneğin herhangi bir sayıda sayıların toplanması, stringlerin birleştirilmesi gibi işlemler.  C fonksiyonu printf formatı buna bir örnektir. Printf, formatını belirten bir argüman ve formatlanacak değerleri sağlayan herhangi bir sayıda argüman alır. Dolayısı ile gönderilecek argümanın sayısı değişkendir. Fakat şunu da söylemek gerekir işlevin yığından daha fazla argüman çıkarmaya çalışmasına, yığının bozulmasına ve beklenmeyen davranışlara yol açmasına izin verebilir. Bunun bir sonucu olarak, CERT Koordinasyon Merkezi, C’ deki değişken işlevlerin yüksek önemde bir güvenlik riski olduğunu düşünmektedir. 

Şimdi belgenin konusu olan variadic makrolara gelelim. Aslında bu konuyu tesadüfen yazmaya karar verdim. C++ ile birden fazla data tipi ve farklı kayıt alanlarını destekleyen Logger yazarken bir amaç için makroları kullandım. Oluşan sonuç hoşuma gitti ve paylaşmak istedim. Buradaki örnek uygulamayı daha kolay anlaşılması için C dili ile yazdım.

Burada Logger olarak değil de bir SEND makrosu olarak ele alacağım. SEND makrosunda amacımız string dataları konsola/seri porta/dosyaya gönderirken binary dataları(array, struct) ise formatlayarak -yada olduğu gibi- EEPROM/konsolo/harici bir cihaza göndermek olsun. Bu amaç için birden fazla çözüm olacağı kesindir. Fakat variadic makro konusunu anlatmak için çözümü makro ile yapmaya çalışalım.

SEND(“just string msg”);              //just send string message
SEND(buff, 5);                        //just send array
SEND(buff, 3, “binary data message”); //send binary and string data

Amacımızı ifade eden örnek kullanım yukarıdaki gibi olacaktır. C++ bilenlerin gözünün önünde işlev yüklemesi (function overloading) konusu canlanmış olabilir. Aslında yapacağımız bir nebze buna benzemektedir. SEND() makrosunun(function like macro) kullanım örneklerinde görüldüğü gibi makrosunun 1, 2 ve 3 adet argümanlı çağrımları mevcut. Temel amacımız bu değişken argüman sayısını gerçekleştirebilmek ve argüman türüne bağlı olarak ilgili fonksiyonu çağırmak. Fakat bunu yapmadan önce argümanların diziliş sırasına göre kullanım kendi kuralımızı oluşturmamız gerekmektedir.

Tek argüman ⇒ Eğer tek argüman var ise gelen argüman string olmalıdır ve o şekilde ele     alınacaktır.

Çift argüman ⇒ Çift argüman var ise bu sadece binary veri içerdiğini belirtir ve ilk değer dizi adresini, ikinci değer ise uzunluğu/adet belirtir.

Üçlü argüman ⇒ Üç adet argümanlı çağrımda ise ilk iki argüman binary veri için çift argümanlı kullanımdaki kurallara tabidir. Son argüman ise string olmalıdır ve tek argümanlı kullanım kuralına tabidir.

Bu kurallara göre SEND makrosu çağrıldığında arka tarafta binary ve string argümanları ele alacak fonksiyonlarımız aşağıdaki örnek fonksiyonlar gibi olabilir.

void sendBinaryData(const void *ptr, unsigned int leng)
{
    printf(“\n”);
    for (int i = 0; i < leng; i++) printf(“0x%02x-“, ((char*)ptr)[i]);
    printf(“-> binary data send \n”);
}

void sendStrData(const char *str)
{
    printf(“-> -%s- data send\n”, str);
}

SEND makronunun argüman sayısına göre kuralımı ve arka planda çağrılacak fonksiyonlarımız hazır. Şimdi argüman sayısına göre fonksiyonların çağrımını yapan kısma bakalım. Aslında yapılacak iş tamamen argüman sayısı saymak ve eğer şu kadar argüman var ise şunu yap demek olacak. SEND makrosundan önce ilk olarak bu işin yapıldığını anlatan bir örneğimize bakalım.

#include<stdio.h>

#define ARG0()                          (“Hello Zero”) //0 arg
#define ARG1(val)                     (“1 -> ” #val )
#define ARG2(arr, leng)          (“2 -> ” #arr ” and ” #leng)
#define ARG3(arr, leng, …)     (“3 -> ” #arr ” , ” #leng ” and ”
#__VA_ARGS__)

#define GET_MACRO(_0, _1, _2, _3,NAME,…) NAME
#define MY_MACRO(…) GET_MACRO(_0, ##__VA_ARGS__,  ARG3, ARG2, ARG1, ARG0)(__VA_ARGS__)

int main(void)
{
printf(“\nHello Variadic Macros \n\n”);

printf(“%s  \n”, MY_MACRO()    );
printf(“%s  \n”, MY_MACRO(z)     );
printf(“%s  \n”, MY_MACRO(z, p)     );
printf(“%s  \n”, MY_MACRO(z, p, s)    );

return 0;
}

Yukarıdaki kodu koştuğumuzda  aşağıdaki çıktıyı görürüz.

Hello Variadic Macros 

Hello Zero  
1 -> z  
2 -> z and p  
3 -> z , p and s 

MY_MACRO kullanımı sonrasında değişken argüman sayısının nasıl yakalandığını görmüş olduk. #define GET_MACRO(_0, _1, _2, _3,NAME,…) NAME satırında argüman sayımı yapılıyor.
#define MY_MACRO(…) GET_MACRO(_0, ##__VA_ARGS__,  ARG3, ARG2, ARG1, ARG0)(__VA_ARGS__) satırında ise GET_MACRO saydığı argümana göre hangi isimdeki makronun/fonk çağrılacağı belirtiliyor. Belirtilen makro/fonk herhangi biri çağrıldığında ise argüman olarak (__VA_ARGS__) içeriği gönderiliyor. Örneğin  MY_MACRO(z, p, s) kullanımında ilgili makro/fonk bulunduktan sonra bu makro/fonk argüman olarak (z, p, s) gönderilir. define ARG3(arr, leng, …) bulunacağı ve dolayısı ile z ⇒ arr, p ⇒ leng, s ⇒ …(variadic) eşleşmesi olacağını görüyoruz. ARG3 içinde de variadic argüman kullanılarak o kısma da değişken sayıda argüman gönderilebileceğini görüyoruz. Buna örnek çağrım aşağıdaki gibi olabilir.
printf(“%s  \n”, MY_MACRO(z ,p, (“bir”, “iki”, “üç”))  ); // kullanım
3 -> z , p and (“bir”, “iki”, “üç”)     // çıktısı

Yapının nasıl çalıştığını öğrendikten sonra bu yapı ile fonksiyon birleşimi yapalım. SEND makrosu çağrımında arka planda fonksiyonlar kullandığı için bu örneği inceleyelim.

#include<stdio.h>

void sendBinaryData(const void *ptr, unsigned int leng)
{    
printf(“\n”);    for (int i = 0;  i < leng; i++) printf(“0x%02x-“, ((char*)ptr)[i]);    
printf(“-> binary data was sent \n”);
}
void sendStrData(const char *str)
{    
printf(“-> -%s- was sent\n”, str);
}

#define SEND_STR(str)                      (sendStrData(str))#define SEND_BIN(buff, leng)                        (sendBinaryData(buff, leng))#define SEND_BIN_STR(buff, leng, str…)    (sendBinaryData(buff, leng), sendStrData(str))

#define FIND_MACRO(_1, _2, _3,NAME,…) NAME#define SEND(…) FIND_MACRO(__VA_ARGS__, SEND_BIN_STR, SEND_BIN, SEND_STR)(__VA_ARGS__)

int main(void)
{
    char buff[5] = {1,2,3,4,5};
    SEND(“just string msg”);        //just send string message
    SEND(buff, 5);                        //just send array
    SEND(buff, 4, “3-String”); //send binary and string data
    return 0;
}

Program çıktısı aşağıdadır.

-> -just string msg- was sent
0x01-0x02-0x03-0x04-0x05–> binary data was sent 
0x01-0x02-0x03-0x04–> binary data was sent -> -3-String- was sent

3. Sonuç

Variadic makro kullanımı örnekler üzerinden antlatmaya çalıştım. Tabiki birden fazla kullanım biçimi ve amacı vardır. Bu belgeyi yazmamdaki temel amaç C ve C++ dillerinde variadic maroların olduğunu okuyucuya göstermek ve kendi uygulamalarında bu araçtan faydalanmasına zemin hazırlamaktır. 

Makrolar hakkında daha geniş bilgiye bu linkten ulaşabilirsiniz. Bu arada benim sıklıkla kullandığım tüm C preprocessor komutlarını anlatan linke de buradan ulaşabilirsiniz.

Pdf olarak indir.

Zafer Satılmış
İyi Çalışmalar