A C függvények nagyban hasonlÃtanak a C# metódusaira, de van különbség. Ha definiálunk egy paraméter nélküli FunctionTest() függvényt, akkor azt meg tudjuk hÃvni paraméterekkel is, annak ellenére, hogy nem definiáltunk bemeneti paramétereket. Ezért ha tényleg azt szeretnénk, hogy egy függvény ne fogadjon paramétereket, akkor a void kulcsszót ki kell tennünk a függvény paraméterlistájába:
/*Ez a függvény már nem fogad argumentumot*/
void FunctionTest2(void)
{
printf("Ez egy fuggveny");
}
Az értékek átadása a függvények esetén alapértelmezetten érték szerint történik, vagyis a függvény nem a változót kapja meg, hanem annak az értékét. Ezért ha a függvény módosÃtja a kapott paramétert, akkor az nem lesz hatással az eredeti változóra. De történhet referencia szerinti átadás is. Ez pointerek segÃtségével van megoldva. A függvény a paramétert pointerként definiálja, a hÃvó pedig az & operátorral a változó memóriacÃmét adja át a függvénynek. Erre a viselkedésre láthattunk már példát a programjainkban a scanf különbözÅ‘ variánsainak használatakor.
A tömbök és a struktúrák átadhatóak referenciaként és értékként is. Viszont arra ügyelni kell, hogy a tömbök esetén csak az első elemre mutató pointer értéke fog lemásolódni, nem a teljes memóriaterület. A struktúráknál a teljes memóriatartalom másolódni fog, ha érték szerinti átadás történik. Az alábbi program az érték és referencia szerinti átadást mutatja be:
#include<stdio.h>
void increment(int x) {
x++;
}
void incrementByReference(int *x) {
(*x)++;
}
int main()
{
int num = 5;
printf("eredeti: %d\r\n", num);
increment(num);
printf("increment() hivas utan: %d\r\n", num);
incrementByReference(&num);
printf("incrementByReference() hivas utan: %d\r\n", num);
return 0;
}
A program kimenete:
eredeti: 5
increment() hivas utan: 5
incrementByReference() hivas utan: 6
A tömbök mutatóként kerülnek átadásra. A függvény a tömb első elemét kapja meg mutatóként, a méretéről azonban nem kap információt. Ennek az átadásáról a programozónak kell gondoskodnia:
#include <stdio.h>
/*A tömb pointer! Méretet is kell vele adni*/
void modifyArray(int arr[], size_t size) {
/*mivel pointer, módosÃtható is az érték*/
for (size_t i = 0; i < size; i++) {
arr[i] *= 2;
}
}
int main()
{
int numbers[] = {1, 2, 3, 4, 5};
size_t size = sizeof(numbers) / sizeof(int);
modifyArray(numbers, size);
for (size_t i=0; i<size; i++)
{
printf("%d ", numbers[i]);
}
return 0;
}
A program kimenete:
2 4 6 8 10
A referenciaátadás esetén problémás lehet, ha csak úgy passzolgatjuk a referenciáinkat, mert nem tudhatjuk, hogy mi módosÃtja azt és mi nem. Éppen ezért, ha referenciát vár a függvényünk és jelezni szeretnénk azt, hogy nem fogja módosÃtani a referenciát, akkor tegyük ki a const kulcsszót a paraméter elé. Ha az elÅ‘zÅ‘ programban módosÃtjuk a modifyArray függvényt a const kulcsszóval, akkor fordÃtási hibát fogunk kapni a *= 2 kifejezésnél.
void modifyArray(const int arr[], size_t size) {
for (size_t i = 0; i < size; i++) {
arr[i] *= 2;
}
}
A const kulcsszó konstansok definiálására szolgál, azonban nagyon nem mindegy, hogy hol helyezkedik el a kulcsszó mutatók esetén, ha paraméterekről van szó:
const int *x;
int const *x;
A fenti két deklaráció ugyanazt jelenti, mégpedig azt, hogy az x által mutatott memória konstans és nem módosÃtható. Azonban, ha a const a * után szerepel, akkor teljesen más jelentéssel bÃr:
int *const x;
Ez azt jelenti, hogy az x mutató a konstans és nem az általa mutatott memória. Ez gyakorlatban azt jelenti, hogy x értéke nem módosÃtható az értékadó (=) operátorral, de az x által mutatott memóriaterület igen.
A const kettőssége akár kombinálható is:
int const * const x;
A fenti esetben x által mutatott memória és a pointer is konstans lesz. Gyakorlati haszna ugyan nincs, de a const kulcsszó viselkedéséből adódik a lehetősége.
C függvények esetén még egy fontos téma a függvényhÃvás módszere. EbbÅ‘l három létezik és bináris kompatibilitás miatt fontos tudni, hogy ha egy DLL fájlba belehÃvunk, akkor a paramétereknek hogyan kell majd a memóriában elhelyezkedniük, illetve kinek a felelÅ‘ssége a stack eltakarÃtása a hÃvás után. Ha nem jelezzük egy metódus esetén a hÃvási módszert, akkor a __cdecl hÃvási módszer kerül alkalmazásra. Ez a C alapértelmezett viselkedése. DLL fájlokba hÃvás és Windows API esetén az __stdcall hÃvási módszer preferált.
Ezen felül létezik még a __fastcall hÃvási módszer, ami a 64 bites x64 processzorok esetén elveszÃtette a relevanciáját. Régen, x86-ra fordÃtott kódok esetén olyan függvényeknél alkalmazták Å‘ket, amik kevés paraméterrel dolgoztak és kritikus volt, hogy a hÃvás ideje ne tartson sokáig. Ennek ellenére X64-re fordÃtott kód esetén is működik, de leginkább csak kompatibilitási okokból tartották meg, tényleges elÅ‘nnyel modern hardveren nem jár a használata.
| HÃvási mód | Stack takarÃtás | Paraméterek |
|---|---|---|
__cdecl |
HÃvó | Paraméterek a stack-en, fordÃtott sorrendben (jobbról balra) |
__stdcall |
HÃvott | Paraméterek a stack-en, fordÃtott sorrendben (jobbról balra) |
__fastcall |
HÃvott | Paraméterek regiszterben, majd a stack-en |
Változó argumentumszámú függvények
Hasonlóan a C#-hoz a C is lehetőséget ad változó argumentumszámú függvények definiálására. Ehhez az stdarg.h headerre lesz szükségünk. Nézzünk egy példát:
#include <stdio.h>
#include <stdarg.h>
double average(int arg_count, ...)
{
va_list valist;
double sum = 0.0;
/* változó argumentumszám feldolgozásának kezdete */
va_start(valist, arg_count);
/* argumentumok kiolvasása */
for (int i = 0; i < arg_count; i++)
{
sum += va_arg(valist, int);
}
/* argumentum lista felszabadÃtása*/
va_end(valist);
return sum / arg_count;
}
int main()
{
printf("average(1, 2, 3, 4, 5) = %f\r\n", average(5, 1, 2, 3, 4, 5));
return 0;
}
A változó argumentumszámú függvények esetén egy argumentumban definiálnunk és átadnunk kell a paraméterek számát. Ezt követÅ‘en a ... karakterekkel jelezhetjük, hogy a függvény korlátlan számú paramétert fogad. A va_start függvényhÃvással olvashatjuk ki ezeket az argumentumokat. Ez egy va_list tÃpusban tárolja majd ezeket, aminek az elsÅ‘ paraméternek kell lennie. A második paraméter a definiált paraméterek száma.
Ezt követÅ‘en a va_arg n alkalommal való meghÃvásával megszerezhetjük az n darab argumentumot. A va_end hÃvással pedig felszabadÃthatjuk a függvényargumentumokat. A hÃvásnál látható, hogy 6 számot adunk át. EbbÅ‘l az elsÅ‘ a további paraméterek száma.
A program kimenete:
average(1, 2, 3, 4, 5) = 3.000000
Inline
Függvények esetén lehetÅ‘ségünk van az inline módosÃtó használatára. Ez a kulcsaszó egyfajta ajánlásként működik a fordÃtó számára, vagyis a fordÃtó dönthet úgy, hogy figyelmen kÃvül hagyja.
Az inline módosÃtóval azt ajánljuk a fordÃtónak, hogy ha lehetÅ‘sége van rá, akkor a függvényt ne függvényhÃvással hÃvja meg, hanem a generált kódot a hÃvás helyére másolja be.
Ez bizonyos platformok esetén hatalmas elÅ‘nnyel járhat, mert a függvényhÃvásnak van egy költsége és ha egy függvényt sokszor hÃvunk, akkor a hÃvás költsége jóval nagyobb lehet, mint a tényleges végrehajtási ideje.
Azonban a függvény kódjának beágyazása negatÃv hatású is lehet, mivel függvényhÃvás helyett minden hÃvási helyre bemásolódik a kód, ami végsÅ‘ soron a program méretét növeli. Ez beágyazott rendszerek esetén okozhat problémát, ahol a memóriát még mindig KiB vagy esetlegesen pár MiB méretben mérik.
Mivel a mai asztali számÃtógépek esetén a függvényhÃvás költsége elhanyagolható, az inline kulcsszónak nagyon sok hatása nincs, sÅ‘t a modern fordÃtók egy az egyben ignorálják és a választott optimalizációs szinttÅ‘l függÅ‘en dönthetnek úgy, hogy az inline kulcsszóval megjelölt függvényt nem ágyazzák be, de egy másik nem jelöltet pedig igen.
/*ajánlás a fordÃtónak*/
inline int squareGcc(int num) {
return num*num;
}
ElÅ‘fordulhat, hogy optimalizációs céllal mégis ki szeretnénk kényszerÃteni egy függvény beágyazását. Ha erre lenne szükségünk, akkor Visual Studio alatt a __forceinline használható, mÃg GCC esetén a __attribute__((always_inline)) módosÃtóval kell ellátnunk a függvényünket.
Megjegyzés: Ezen megoldások használata nem ajánlott, csak akkor, ha mindenképpen szükség van rá.
/*beágyazás kikényszerÃtése GCC és Clang esetén*/
inline __attribute__((always_inline)) int squareGcc(int num) {
return num*num;
}
/*beágyazás kikényszerÃtése Visual Studio esetén*/
inline __forceinline int square(int num) {
return num*num;
}