Utolsó módosítás: 2009. március 5.
A C nyelv egyik származtatott típusa a mutató (pointer). Egy mutató érték megadja egy másik változónak a helyét, így a mutatón keresztül ennek a változónak a tartalmát ki tudjuk olvasni, vagy módosítani tudjuk.
A mutatók szintaxisának alapja a következő.
*
p;
.
Egy ilyen változó értéke tehát kijelöli egy T
típusú
változó helyét.
&
x kifejezés egy mutató, ami x-re mutat,
ennek a típusa T *
.
Az egyoperandusú &
operátor tehát egy balértékből
annak a címét állítja elő.
*
p adja meg azt a változót, ahova ez mutat.
Ezt a *
p kifejezést ugyanúgy használhatjuk,
mint egy változót:
módosítjuk a mutatott változó tartalmát,
ha egy értékadás bal oldalán használjuk;
vagy kiolvashatjuk a tartalmát,
ha egy kifejezésben máshol használjuk.
A *
operátor tehát egy mutatóból
az általa mutatott balértéket állítja elő.
Egy egyszerű példa a következő.
#include <stdio.h> int main(void) { int a; int *p; p = &a; *p = 42; printf("%d\n", a); a = 137; printf("%d\n", *p); return 0; }
A kimenet a következő.
42 137
A p
egy int-re mutató típusú változó.
Miután ebbe beleraktuk az a
változó címét,
ezen keresztül írjuk és olvassuk is a változót.
A mutató értékeket lehet másolni is, például értékadással, függvénynek argumentumként átadással, vagy függvényből visszatérési értéknek adással.
Nézzük a következő példát.
#include <stdio.h> void nothing(int x) { x = 10; } void modify(int *p) { *p = 10; } int main(void) { int a = 5; nothing(a); printf("%d\n", a); modify(&a); printf("%d\n", a); return 0; }
A kimenet:
5 10
Itt a modify függvény paramétere int-re mutató típusú.
Ezen keresztül a függvény a main függvényben lévő lokális változót
közvetetten tudta elérni.
Vegyük észre,
hogy az a
változó nevét nem használhattuk volna közvetlenül
a modify függvényben, mivel ez lokális a main függvényre, ezért csak
ott van hatókörben.
Ennek ellenére a változó maga végig létezik a main függvény elindulásától
a kilépéséig, ezért egy mutatón keresztül el szabad érni.
A mutatóknak ez a használata hasznos, ha egy függvény több eredményt szeretne átadni a hívónak, mint amennyit egyszerűen a visszatérési értékbe belerakhatna. Így működik például a scanf függvény is: ez egyszerre több adatot is be tud olvasni a bemenetről, ezért argumentumnak azokat a címeket adjuk át, ahova eltárolja a beolvasott adatokat, a visszatérési értéke pedig csak a sikerességet jelzi.
Amikor a nothing függvényt meghívjuk,
akkor a függvény x
paramétere létrejön,
mint egy lokális változó a nothing-ban, és ennek az értéke
a híváskor átadott argumentumra állítódik be.
Ez a változó tehát máshol van, mint az a
változó
a main függvényben, a kettő értékét egymástól függetlenül lehet módosítani,
amíg a nothing függvény fut.
A nothing függvény tehát csak egy másolat értékét módosítja
(ezt a másolatot a függvénytörzsben később fölhasználhatnánk valahogyan),
az a
változót nem.
Azt is vegyük észre, hogy a nothing és a modify függvényt különböző módon hívjuk meg
nothing(a); modify(&a);
Az elsőnek egy egészt adunk argumentumként, mivel a paraméter típusa egész, a másodiknak egy int-re mutatót adunk át, mivel a paraméter int-re mutató. Fordítva nem hívhatnánk meg a függvényeket, mivel az argumentumukkal nem tudnának mit kezdeni: a fordító figyelmeztetést vagy hibát adna az eltérő típusok miatt.
Vegyük azt is észre, hogy a nothing függvény argumentuma lehetne egy konstans vagy ideiglenesen számított kifejezés is, mint például
nothing(a + a); nothing(19);
de ezeknek a kifejezéseknek nem vehetjük a címét, így ez hibás lenne:
modify(&(a + a)); /* HIBÁS */ modify(&19); /* HIBÁS */
Az a + a
vagy 19
kifejezések nem balértékek,
nincsen címük, éppen ezért nem lehetne őket értékadás bal oldalán sem használni,
mint például:
a + a = 10; 19 = 10;
Nézzünk még két dolgot, amit a mutatókkal nem szabad csinálni.
#include <stdio.h> int global = 4; int * good(void) { return &global; } int * bad(void) { int local = 8; return &local; } int main(void) { printf("%d\n", *good()); printf("%d\n", *bad()); /* HIBÁS */ return 0; }
A bad függvényben deklarált lokális változó csak akkor jön létre, amikor a függvényt meghívjuk, és megsemmisül, amikor a függvény visszatér. Ezért aztán indirekt módon sem szabad elérni ezt a változót a függvény visszatérése után, így a main függvényben a kapott mutató rossz helyre mutat. Valójában a függvény minden hívásánál új lokális változó jön létre ugyanazon a néven, ami az előző hívásokban használt változókkal nem áll kapcsolatban.
Az indirekció *
operátort tehát csak akkor szabad használni
(akár olvasásra, akár írásra),
ha biztosak vagyunk benne, hogy létezik az a dolog, amire az argumentuma
mutat.
A másik hiba talán még nyilvánvalóbb.
#include <stdio.h> int main(void) { int a; int *p; int *q; p = &a; scanf("%d", p); printf("%d", *p); scanf("%d", q); /* HIBÁS */ printf("%d", *q); /* HIBÁS */ return 0; }
A q változónak nem adtunk értéket, így nem tudhatjuk, hova mutat.
A scanf függvény második hívása az ez által mutatott helyre akarna írni,
ami szabálytalan, hasonlóan a *q
olvasás is szabálytalan.
Ha a programot lefordítjuk, a fordító figyelmeztet,
hogy a q értékét inicializásás nélkül használjuk,
de a fordító bonyolultabb esetben nem mindig tudja megtalálni az ilyen hibákat.
Ha lefuttatjuk a programot, jó esetben a q változó olyan címre fog
mutatni, ahol nincs a processznek memória lefoglalva, ezért egy
Segmentation fault
üzenettel leáll a program;
rossz esetben nem kapunk rögtön hibát,
hanem valami más adatot átír a program a memóriában,
ami a futás során később okozhat gondot.
Megjegyzem, hogy a függvény elején a deklarációkat és az értékadást így is lehetne írni:
int a, *p = &a, *q;