Jazyk C umožňuje vytvářet funkce s proměnným počtem
parametrů, což je dost neobvyklá vlastnost. V tomto článku si ukážeme na
nebezpečí, která na nás číhají při programování a používání funkí s
proměnným počtem parametrů.
Nejprve si položme otázku, k čemu vlastně vytvářet funkce s
proměnným počtem parametrů. V některých chvílích je totiž výhodné vytvořit
funkci, jež nemá dán ani počet ani typy předávaných parametrů a toto odložit až
na okažik volání funkce. Typickým příkladem může být funkce printf a
její varianty. Tato funkce očekává jako první parametr formátovací řetězec a
další parametry jsou volitelné, což nám umožňuje používat funkci velmi volným
způsobem. To se může zdát jako výhodné a šikovné (no uvidíme později).
Před tím, než se podíváme na zmiňovaná nebezpečí, zopakujme si
jak vytvářet a používat funkce s proměnným počtem parametrů.
Pokud chceme mít funkci s proměnným počtem parametrů, uvedeme
v seznamu parametrů místo parametru tři tečky, které označují, že další
parametry jsou libovolného typu a je jich libovolný počet. Proměnný počet parametrů
musí být uveden vždy jako poslední položka v seznamu formálních parametrů a funkce
nesmí obsahovat pouze položku proměnný počet parametrů. Pro přístup k parametrům
se používají makra, která jsou definována v hlavičkovém souboru STDARG.H a jedná
se o následující:
va_list |
Definice proměnné představující seznam parametrů. |
va_start |
Nastaví adresu, kde začíná seznam parametrů. |
va_end |
Ukončí práci se seznamem parametrů. |
va_arg |
Získá další parametr ze seznamu parametrů. |
Proměnná představující seznam parametrů je ve skutečnosti
obyčejný ukazatel do paměti zásobníku, který se nastaví pomocí makra va_start.
Makro va_end naopak nastaví ukazatel na NULL. Makro va_arg posune ukazatel o počet
bajtů podle předaného typu. Tedy nic složitého ani světoborného.
Jako jednoduchý příklad si ukažme funkci Kalkul, která
bude pracovat jako sčítačka celočíslených parametrů. Tedy třeba takto:
int Kalkul(int nCount, ...)
{
// Definice ukazatele na parametry
va_list pParams;
// Inicializace ukazatele
va_start(pParams, nCount);
int nSum = 0;
// Pro všechny parametry...
while (nCount)
{
// Získání parametru a přesun na další parametr v seznamu
nSum += va_arg(pParams, int);
//
nCount--;
}
va_end(pParams);
return nSum;
}
void main()
{
// Volání funkce se čtyřmi volitelnými parametry
int nSum = Kalkul(4, 1, 2, 3, 4);
} |
Samozřejmě, že bychom mohli vymyslet inteligentnější příklad,
ale dejme přednost jednoduchosti. Nyní se podívejme, jaká nebezpečí na nás
číhají.
Problémem je, že se nikde nekontroluje počet a typy skutečných
parametrů, můžeme tedy volat funkci Kalkul následovně (a chybně):
// Příliš mnoho parametrů
nSum = Kalkul(2, 1, 2, 3, 4);
// Málo parametrů
nSum = Kalkul(3, 1, 2);
// Parametry jsou jiného typu
nSum = Kalkul(2, "nic", "moc"); |
Ve funkci Kalkul se prostě očekávají hodnoty typu int a je
jedno jakého typu jsou předané parametry - funkce Kalkul je chápe jako by to byla
celá čísla. Což samozřejmě vede k nesprávným výsledkům a v některých
případech to může vést až k pádu aplikace.
Je to způsobeno tím, že proměnný počet parametrů je realizován
pomocí ukazatele do zásobníku, který se posouvá na další hodnotu pomocí makra va_arg
o daný počet bajtů. Podíváme-li se na třetí případ, tak zjistíme, že na
zásobník jsou kromě hodnoty 2 předány ukazatele na řetězce. To ale ve funkci kalkul
nemáme šanci zjistit, a protože očekáváme typ int, pracujeme ve
skutečnosti s adresami řetězců. Výsledkem součtu tedy bude součet adres obou dvou
řetězců.
Další problém nastává, pokud chceme předat hodnoty různých
typů. Pak musíme jako první parametr předávat další informace o parametrech (nejen
počet jako v Kalkul). Dobrým příkladem pak může být funkce printf a její
formátovací řetězec.
Jak je vidět, tak nevýhody výrazně převyšují výhody, a proto si
myslím, že proměnný počet parametrů je sice zajímavá vlastnost jazyka C, ale
spíše teoreticky a v praxi bychom ji měli používat pouze v nejnutnějších
případech, kdy si nelze pomoc jinak.
A na samotný závěr malá otázečka (řešení je v komentářích
ke článku):
// Proč to padá?
int x = 0;
scanf("%d", x); |
|