C Değişken Sayıda Argüman Alabilen Fonksiyonlar (Variadic Functions, ...)

Variadic Fonksiyon Nedir?

Variadic fonksiyonlar değişken sayıda argüman alabilir. Sıradan C fonksiyonlarının alacağı argüman sayısı ve türü önceden belirtilir. Her fonksiyon çağrısında, fonksiyon önceden türü(type) belirtilmiş argümanları bekler.

Ancak bazı fonksiyonlar önceden belirlenmemiş sayıda argüman alabilirler. Bunlar variadic fonksiyonlardır.

Variadic fonksiyonlara en güzel örneklerden birisi printf fonksiyonudur. Printf fonksiyonunun kaç tane argüman alacağı belirtilmemiştir. İstediğimiz kadar argüman gönderebiliriz.

Printf fonksiyonunun protatipi aşağıdaki gibidir.

int printf(char *fmt, ...)

Printf fonksiyonundaki kırmızı renkle gösterilmiş olan üç nokta(ellipsis), bu alana istenilen sayıda ve herhangi bir türde argümanı alabileceğimizi söylüyor.


NOT: Bir fonksiyonda ...(ellipsis) kullanırken dikkat edilmesi gerekenler şunlardır;

  1. ...(ellipsis) tek başına bir fonksiyonun argümanı olamaz.
  2. int printf(...)  /* YANLIŞ */
    int printf(char *fmt, ...) /* DOĞRU */
  3. ...(ellipsis) her zaman bir fonksiyondaki en son argüman olmalıdır.
  4. int printf(char *fmt, ..., int count) /* YANLIŞ */
    int printf(char *fmt, int count, ...) /* DOĞRU */

Variadic Fonksiyonlara Gönderdiğimiz Değişken Sayıdaki Argümana Nasıl Ulaşırız?

Variadic bir fonksiyona gönderdiğimiz değişken sayıdaki argümanı yani ...(ellipsis) ile belirtilen alana gelen argümanların listesini almak için sayfamıza <stdarg.h> standart header dosyasını dahil etmemiz gerekir.

stdarg.h dosyası argüman listesini işlememiz için gerekli macroları barındırır.

Örnek olarak şöyle bir fonksiyonumuz olsun;

int sum(int count, ...)

stdarg.h içerisindeki macrolar şunlardır;

  1. va_list - fonksiyona gönderdiğimiz argüman listesini işaret eden typedef ile tanımlandmış bir pointer veri türüdür.
  2. va_list ap;  /* ap'yi va_list türünde argüman pointerı olarak tanımladık. */
  3. va_start - oluşturduğumuz ap pointerını argüman listesindeki ilk argümana point ettirir.
  4. void va_start (va_list ap, sonargumaninadi)  /* Macro Prototipi */
    
    /* Burda sonargumaninadi olarak belirtilen fonksiyonumuzda ...(ellipsis) ifadesinden
    önceki argümanı belirtiliyor. Yukarıda oluşturduğumuz fonksiyona uygularsak şöyle olacak */
    
    va_start(ap, count);
  5. va_arg - her çağrıldığında argüman listesini point eden pointerı bir sonrakine geçirir.
  6. type va_arg (va_list ap, type)  /* Macro Prototipi */
    
    /* Burda ap'miz yani argüman listesini işaret eden pointerımız, argüman listesindeki
    bir sonraki argümana işaret ettirilir. Ve bu argumanın türü(type) belirtilir.
    Çünkü programımız gönderilen argümanın hangi türde olduğunu bilemeyecektir.
    Bu sebeple fonksiyona en az 1 tane önceden tanımlanmış bir argüman belirtilir.
    Bu önceden tanımlanmış argümanla, ...(ellipsis) alanına gelen argümanların türü
    belirlenebilir. Printf örneğinde ilk argüman alanına sonraki argümanların hangi
    türde olacağı bilgisini giriyoruz. ÖR: printf("%s", "hello world!"); */
    
    int sum;
    sum = va_arg(ap, int);	

    va_arg argüman pointerının(ap) bir sonraki argümana işaret ettirmesini sağlıyor. va_start ile argüman pointerımızı ...(ellipsis) alanından önceki argümanı işaret etmesi gerektiğini söylemiştik. va_arg ise ap'yi bir sonrakine işaret edecek şekilde kaydırdığı için ... alanındaki ilk argümana va_arg ile ulaşabiliriz.


Programın çalışır hali şu şekilde olacaktır:

#include <stdio.h>
#include <stdarg.h>

int sum(int count, ...); /* Prototip */

int main(void){

	printf("%s%d\n","Total : ",sum(9,1,2,3,4,5,6,7,8,9));
	return 0;
}

int sum(int count, ...){

	va_list ap;
	va_start(ap,count);
	int sum = 0, i = 1;
	while(i != count + 1){
		sum += va_arg(ap, int);
		i++;
	}
	va_end(ap); /* Temizleme - AP'nin işinin bittiğini belirtmek için */
	return sum;

}

Yukarıdaki programda fonksiyonumuzda kırmızı ile belirtilen argüman(9) kaç tane argümanın fonksiyona iletileceği bilgisini içeriyor. Program buna göre işlem yapıyor.

Argüman Listesindeki(va_list) Argüman Sayısı

Aslında bunun için doğrudan bir yol yok. Genellikle önceden tanımlanmış argümana kendisinden sonra gelecek argümanlar hakkında bilgi verilir. Argüman sayısı ya da her argümanda yapılacak işlem gibi, böylece bu tanımlanmış argümandaki işlemler bittiğinde başka argüman olmadığı anlaşılır.

Belirttiğimiz işlemi yapan fonksiyonu yazalım:

#include <stdio.h>
#include <stdarg.h>

void calculate(char *islem, int count, ...);

int main(void){
	calculate("*",3,4,5,4);
	calculate("+",4,10,20,30,40);
	calculate("-",5,50,20,4,10,2);
	calculate("/",6,1000,2,5,5,2,5);
	/* calculate fonksiyonuna istediğimiz sayıda değer girip
	istediğimiz işlemi yaptırabiliriz. Kırmızı ile belirtilen alanlar
	calculate fonksiyonuna işlem yapılmak üzere gönderilen değerlerin sayısı */

	return 0;
}

void calculate(char *islem, int count, ...){

	va_list ap;
	va_start(ap, count);  // count'u pointerın ilk argümanı olarak başlatıyoruz
	int a,b,i, result = 0;
	a = b = i = 1;

	switch(*islem){

		case '*':
			result = 1;
			while(i <= count){
				result *= va_arg(ap, int);
				i++;
			}
		break;
		case '+':
			while(i <= count){
				result += va_arg(ap, int);
				i++;
			}
		break;
		case '-':
			while(i <= count){
				if(a == 1){
					result = va_arg(ap, int);
					a = 0;
				}
				else{
					result -= va_arg(ap, int);
				}
				i++;
			}
		break;
		case '/':
			while(i <= count){
				if(b == 1){
					result = va_arg(ap, int);
					b = 0;
				}
				else{
					result /= va_arg(ap, int);
				}
				i++;
			}
		break;
	}
	va_end(ap);
	printf("%s%d\n","Calculate: ", result);
}

Çıktı:

user@pc:~/c/variadic$ gcc variadic.c -Wall
user@pc:~/c/variadic$ ./a.out
Calculate: 80
Calculate: 100
Calculate: 14
Calculate: 2

Programmımız içerisindeki calculate fonksiyonuna hangi işlemi yapacağımızı ve kaç sayı ile işlem yapması gerektiğini bildirdik. Sonuçlara bakarsak hiçbir hata olmadan doğru hesapladığı görebiliriz.