C dilinde istenilen sayıda argümanla çağrılabilen işlevler (variadic functions)

Bir işlevin istenilen sayıda değerle çağrılması programlamada genel bir ihtiyaç ve programlama dillerinin çoğunda bu ihtiyacı karşılamaya yönelik araç ya da araçlar var. Özellikle giriş çıkış işlemlerine (input-output operations) yönelik hizmet veren kütüphanelerde böyle işlevler tercih ediliyor. Böyle işlevlere ingilizcede popüler olarak “variadic functions” deniyor.  Ben bu terim yerine Türkçede “istenilen sayıda argüman ile çağrılabilen işlev” terimini kullanacağım. Çağrıyı yapan kod böyle işlevlere, ne yaptırmak istediğine bağlı olarak, farklı sayıda veri gönderebiliyor. Örnekler verelim:

print isimli bir işlev kendisine gönderilen her bir ifadenin değerini ekrana yazdırıyor olabilir.
get_ave isimli işlev kendisine gönderilen tam sayıların aritmetik ortalamasını hesaplıyor olabilir.
concat isimli işlev kendisine gönderilen yazıları birleştirip tek bir yazı oluşturuyor olabilir.
push_back_vals isimli işlev kendisine gönderilen değerleri bir dinamik diziye ekliyor olabilir.

Farklı programlama dillerinin böyle bir işlevin oluşturulmasını sağlayan farklı araçları var. Örneğin C++ dilinde bu yapı için ağırlıklı olarak türden bağımsız programlama (generic programming) paradigmasına destek veren araçlardan faydalanılıyor. Bu yazının amacı C dilinde değişken sayıda argümanla çağrılabilen işlevleri ayrıntılı olarak incelemek.

C dilinde bir işlevin “variadic” olduğunu gösteren … atomu. Yan yana yazılmış üç nokta karakterinin oluşturduğu atoma (token) ingilizcede ellipsis deniyor. Bir işlevin “variadic” olması için son parametre değişkeni olarak atomunun yazılması gerekiyor. üç nokta atomu atomu ile gösterilen parametreye bundan sonra “variadic parametre” diyeceğim.

Bu işlevlerin çağrılmasına ilişkin genel kural şu: Çağrıyı yapan taraf “variadic” parametreden önce yer alan türleri belirtilmiş tüm parametre değişkenlerine argüman göndermek zorunda. variadic parametre için kendi seçimine bağlı olarak (opsiyonel olarak) dilediği sayıda argüman olarak gönderilebilir. Aşağıdaki kodu inceleyelim:

“variadic parametre” son parametre olarak yazılmalı ve kendisinden önce türü belirtilmiş en az bir parametre değişkeni olmalı.

tür kontrolü

Seçime bağlı olarak gönderilecek argümanların türlerinin “variadic” işlev tarafından bilinmesi mümkün değil. Derleyici seçimlik (opsiyonel) argümanları işleve göndermeden “varsayılan argüman dönüşümü” (default argument conversion) denilen dönüşümü gerçekleştirir. Yani char ve short türlerden olan argümanlar işaretli ya da işaretsiz int türüne, float türden olan argümanlar ise double türüne dönüştürülürler. İstenilen sayıda argümanla çağrılan işlevlerin en zayıf tarafı da bu. Derleyicinin bir tür kontrolü yapma şansı yok. Bu yüzden variadic işlevler normal işlevlere göre daha yüksek kodlama hatası riski içeriyorlar.

stdarg.h başlık dosyası içinde tanımlanan makrolar

variadic bir işlev tanımlayabilmemiz için standart <stdarg.h>başlık dosyasında bildirilen va_list türünün ve yine aynı başlık dosyasında tanımlanan bazı standart makroların kullanılması gerekiyor:

va_list
va_list opsiyonel argümanları gösterecek bir pointer türüne verilen bir tür eş ismi (type alias). standartlar va_list‘in hangi türe bir typedef bildirimi ile eş isim olarak seçileceğini derleyicilere bırakmış. Seçimlik argümanların dolaşılabilmesi için va_list türünden bir değişkenin tanımlanması ve bu değişkene va_start makrosuyla değer verilmesi gerekiyor.

va_start
va_start aşağıdaki gibi bir makro:

Bu makro seçimlik argümanları dolaşma işleminde kullanılacak va_list türünden değişkene  değerini veriyor. Böylece va_list türünden değişkenin seçimlik ilk argümanı göstermesi sağlanıyor. Makronun ikinci parametresine işlevin türü belirtilerek isimlendirilmiş son parametresinin isminin geçilmesi gerekiyor.

va_arg

va_arg makrosu sıradaki seçimlik argümanın değerini döndürürken va_list türünden değişkenin de değerini değiştirerek onun bir sonraki seçimlik argümanı göstermesini sağlıyor. Makronun ikinci parametresine elde edilecek argümanın tür bilgisinin geçilmesi gerekiyor. Bu makroya döngüsel bir yapıda, seçimlik argümanların sayısı kadar çağrı yapılmasıyla işlevin variadic parametresine gönderilen tüm argümanlara erişilebiliyor.

va_end makrosu

Bu makro seçimlik argümanların dolaşılması sürecini sonlandırmak için çağrılıyor. Variadic işlevin çalışacak kodunun sonlanmasından önce bu makroyu çağırmak gerekiyor.

va_copy makrosu

Bu makro dile C99 standartları ile eklendi. va_start makrosu çağrılarak ilk değerini almış va_list türünden değişkenin bu makro ile kopyası çıkartabiliyor. Böylece argümanları birden fazla kez dolaşmak kolayca mümkün hale geliyor.

variadic parametreye gönderilen argümanların kullanılması

İşlevlerin normal parametre değişkenlerini isimleri yoluyla kullanıyoruz. Ancak variadic parametreye ilişkin argümanların gönderildiği parametrelerin isimleri yok. Bu durumda işlev tanımında onları nasıl kullanacağız? İşleve gönderilen argümanlara yalnızca, standart stdarg.h başlık dosyasında tanımlanan özel makroları kullanarak, fonksiyon çağrısı ile gönderildikleri sıra ile erişilebiliyor. Standart va_start, va_arg ve va_end makrolarını kullanarak 3 aşamalı bir sürecin oluşturulması gerekiyor:

  1. Önce va_start makrosunu kullanarak va_list türünden bir pointer değişkene değer verilmesi gerekiyor. Bu yapıldığıda va_list türünden değişken seçimlik ilk argümanı gösteriyor hale geliyor.
  2. Daha sonra va_arg makrosuna döngüsel bir yapıda çağrı yaparak tüm seçimlik argümanlara erişilebiliyor. Yani va_arg makrosuna yapılan ilk çağrı ilk argümanı, ikinci çağrı ikinci argümanı veriyor. Argümanların hepsinin dolaşılması zorunlu değil. İşleve gönderilen argümanların bir kısmına erişmemek herhangi bir şekilde tanımsız davranış oluşturmuyor. Ancak gönderilen argüman sayısından daha fazla sayıda argümana erişim girişimi tanımsız davranış.
  3. Argümanların elde edilmesi işleminin bitirildiğini ifade etmek için va_list türünden değişken ile va_end makrosuna çağrı yapılması gerekiyor. Aslında derleyicilerin çoğu va_end makrosu karşılığı hiçbir kod üretmiyor. Yine de hem standartlara tam olarak uygun bir kod oluşturmak için hem de kodun okunmasını kolaylaştırmak va_end makrosu mutlaka çağrılmalı.

işlev tanımında seçimlik argümanların sayısının elde edilmesi

variadic işlevlerde argüman sayısının elde edilmesi için birden fazla teknik kullanılabilir.

İşlevin tam sayı türünden bir parametresi (tipik olarak birinci parametre) çağıran koddan işleve gönderilen diğer argümanların sayısını alır. Bu uygulanması en kolay tekniktir. Şimdi bu tekniği kullanan bir işlevi kodlayalım:

Yukarıdaki kodda tanımlanan sum isimli variadic işlev kendisine gönderilen tam sayıların toplamını hesaplıyor. Aşağıdaki kodda yine aynı tekniği kullanan min ve max isimli işlevlerin tanımları yer alıyor. Bu işlevler kendilerine gönderilen tam sayılardan en küçük ve en büyük olanlarının değerlerini buluyorlar:

Bir başka teknik variadic işlevin kendisini çağıran koddan bir yazının adresini alarak kendisine gönderilen seçimlik argümanların sayısını bu yazıdan elde etmesi. stdio kütüphanesinde bildirilen standart scanf ve printf işlevleri de bu tekniği kullanıyor. Aşağıda printf işlevini sarmalayan basitleştirilmiş bir print işlevi tanımlıyoruz:

Çağrıyı yapan kod, işleve son argüman olarak başka bir argüman gönderilmeyeceğini belirten özel bir değer gönderebilir. Aşağıdaki kodu inceleyelim:

Yukarıdaki kodda ismi concat olan istenilen sayıda argümanla çağrılabilen bir işlev tanımlanıyor. concat işlevi kendisine adresleri gönderilen yazıları elde ettiği dinamik bir bellek bloğunda birleştiriyor. İşlevi çağıran kod, birleştirilecek son yazının adresinden sonra işleve başka bir yazı gönderilemediğini bildirmek amacıyla işleve NULL pointer geçiyor. Yukarıdaki kodda va_copy makrosunun kullanılması ile seçimlik argümanların iki kez dolaşıldığını görüyorsunuz.

Share

Necati Ergin

C ve Sistem Programcıları Derneğinde eğitmen olarak çalışıyor.

Bunlar da ilginizi çekebilir

Kod Eklemek İçin Okuyun