8. FEJEZET | Tartalom | B. FÜGGELÉK |
A. FÜGGELÉK:
Referencia-kézikönyv
A1. Bevezetés
Ez a kézikönyv a C nyelv ANSI felé 1988. október 31-én benyújtott és az „Amerikai Nemzeti Szabvány Információs Rendszerekre – A C Programozási Nyelv, X3.159-1989.” címmel elfogadott szabvány alapján készült. A kézikönyv a tervezett szabvány értelmezése és nem magának a szabványnak a leírása, bár gondosan ügyeltünk arra, hogy a nyelv megbízható leírását adjuk.Ez a leírás a legtöbb részletében követi a szabvány felépítését (ami a könyv első kiadása után jelent meg), de szerkezete a részletekben különbözik attól. Néhány fogalom átnevezésétől, a lexikális tokenek (szintaktikai egységek) nem formalizált leírásától vagy az előfeldolgozó rendszertől eltekintve az itt leírt szintaxis megfelel a szabványban foglaltaknak.
Bár ez a rész egy kézikönyv, az egyes pontokhoz magyarázatokat fűztünk, amit kisebb betűtípussal szedtünk. A magyarázatok többsége rávilágít arra, hogy az ANSI C miben különbözik a könyv első kiadásában definiált nyelvtől, vagy hogy a különböző fordítóprogramok esetén milyen finomítások érvényesek.
A2. Lexikális megállapodások
Egy program egy vagy több fordítási egységből áll, amelyek állományokban helyezkednek el. A program feldolgozása több fázisban történik, az egyes fázisokat az A12. pontban foglaltuk össze. A legelső feldolgozási fázisban a program elvégzi az alacsony szintű lexikális átalakítást, amelynek során először végrehajtódnak a # jellel kezdődő sorokban elhelyezett direktívák, majd megtörténik a makródefiníciók feldolgozása és végül létrejön a makrókifejtés. Az A12. pontban leírt előfeldolgozás befejeztével a program szintaktikai egységek (tokenek) sorozatára egyszerűsödik.
A2.1. Szintaktikai egységek
A szintaktikai egységek hat osztályba sorolhatók: azonosítók, kulcsszavak, állandók, karaktersorozatok, operátorok és egyéb szeparátorok. A szóközt, a vízszintes és függőleges tabulátort, az új sort, a lapemelést és a megjegyzéseket (közös néven üres helyeket) a fordítóprogram nem veszi figyelembe, kivéve ha szintaktikai egységeket választanak el egymástól. Valamennyi üres helyre szükség van a szomszédos azonosítók, kulcsszavak és állandók elválasztásához.Ha a beolvasott programszöveg adott karakterig szintaktikai egységekre lett bontva, akkor a fordítóprogram a következő szintaktikai egységnek azt a leghosszabb karaktersorozatot tekinti, amelyről feltételezhető, hogy egyetlen szintaktikai egységet alkot.
A2.2. Megjegyzések
A megjegyzés szövege a karakterekkel zárul. A megjegyzések nem ágyazhatók egymásba és nem fordulhatnak elő karaktersorozatokban vagy karakteres állandókban.
A2.3. Azonosítók
Egy azonosító betűkből és számjegyekből áll. Az első karakterének betűnek kell lenni és az _ aláhúzás-karakter betűnek számít. Azonosítókban a nagy- és kisbetűk különböznek. Az azonosítók hossza tetszőleges lehet és belső azonosítók esetén legalább 31 karakter szignifikáns, de néhány rendszerben a szignifikáns karakterek száma több is lehet. Belső azonosítók közé tartozik az előfeldolgozó rendszerrel értelmezett makrónév és minden más név, amelynek nincs külső csatolása (l. az A11.2. pontot). A külső csatolású azonosítókra ennél több megszorítás érvényes: a megvalósításokban csak az első hat karakter szignifikáns és nem tesznek különbséget kis-, ill. nagybetű között.
A2.4. Kulcsszavak
A következő azonosítók fenntartott kulcsszavak és más célra nem használhatók:
auto double int struct break else long switch case enum register typedef char extern return union const float short unsigned continue for signed void default goto sizeof volatile do if static while
Néhány megvalósításban fenntartott szó még az asm és a fortran.
A2.5. Állandók
A C nyelvben többféle állandó létezik, ezek mindegyikéhez egy adattípus tartozik. Az alapvető adattípusok leírása az A4.2. pontban található.
állandók egész_állandó karakteres_állandó lebegőpontos_állandó felsorolt_állandó
A2.5.1. Egész állandók
Egy egész állandó számjegyek sorozatából áll, amit oktális számként értelmezünk, ha a 0-val (nulla számjeggyel) kezdődik és decimális számként minden más esetben. Az oktális állandókban nem fordulhatnak elő a 8 és 9 számjegyek. A számjegyek 0x vagy 0X (nulla számjegy) kezdetű sorozatát hexadecimális egész számként értelmezzük. A hexadecimális számok számjegyei közé tartoznak a 10…15 értékű számjegyeket jelző a vagy A … f vagy F karakterek.Az egész állandók az u vagy U betűből álló utótaggal láthatók el, ami azt jelzi, hogy a szám előjel nélküli. Az l vagy L utótag szintén használható és long típust jelöl.
Az egész állandók típusa a leírási formától, az értéktől és az utótagtól függ. (Lásd még az A4. pontban az adattípusok tárgyalásánál!) Ha a leírt szám utótag nélküli, decimális szám, akkor a típusa az értéke által meghatározott int, long int vagy unsigned long int típusok közül az első megfelelő típus. Ha a leírt szám utótag nélküli, oktális vagy hexadecimális szám, akkor típusa az int, unsigned int, long int, unsigned long int típusok közül az első megfelelő típus. Ha az utótag u vagy U, akkor a típus unsigned int vagy unsigned long int. Ha az utótag l vagy L, akkor a típus long int vagy unsigned long int.
A2.5.2. Karakteres állandók
A karakteres állandó egy vagy több aposztrófok (‘) közé zárt karakterből áll. Az egyetlen karakterből álló karakteres állandó értéke a karakternek a végrehajtáskor érvényes gépi karakterkészletből vett számértéke. A több karakterből álló karakteres állandók értéke a megvalósítástól függ.A karakteres állandókban nem szerepelhet a ‘ vagy az újsor-karakter; azért hogy ezeket, valamint bizonyos más karaktereket ábrázolni tudjuk, a következő escape-sorozatok használhatók:
új sor | NL (LF) | \n |
vízszintes tabulátor | HT | \t |
függőleges tabulátor | VT | \v |
visszalépés (backspace) | BS | \b |
kocsivissza | CR | \r |
lapemelés (formfeed) | FF | \f |
hangjelzés (bell) | BEL | \a |
backslash | \ | \\ |
kérdőjel | ? | \? |
aposztróf | ‘ | \’ |
idézőjel | “ | \” |
oktális szám | ooo | \ooo |
hexadecimális szám | hh | \xhh |
A \ooo escape-sorozat egy backslash karakterből és az azt követő 1, 2 vagy 3 oktális számjegyből áll, amely a kívánt karakter értékét határozza meg. Erre a legjobb példa a \0 (amit nem követ további számjegy), ami a NULL karaktert jelenti. A \xhh escape-sorozat a backslash karakterből, az azt követő x betűből és az utána írt hexadecimális számjegyekből áll, amelyek a kívánt karakter értékét határozzák meg. A beírt számjegyek száma nincs korlátozva, de ha a kapott karakterérték nagyobb, mint a legnagyobb karakterérték, akkor a hatás definiálatlan. Ha a gépi megvalósítás a char típust előjelesen kezeli, akkor az oktális vagy hexadecimális escape-sorozatok értéke előjel-kiterjesztéssel keletkezik, csakúgy, mint a kényszerített típuskonverziójú char típus esetén. Ha a \ karaktert követő karakter nem a fentiek egyike, akkor az eredmény definiálatlan.Néhány gépi megvalósítás kiterjesztett karakterkészletet használ, amelyben a karakteres állandók nem ábrázolhatók char típussal. Az ilyen kiterjesztett karakterkészletű karakteres állandó az L előtaggal írható be, pl. az L’x’ formában, és ezt az állandót széles karakteres állandónak nevezzük. Az ilyen állandók wchar_t típusúak, ami egy egész adattípus és az <stddef.h> standard headerben van definiálva. Ezt a típust a közönséges karakteres állandókhoz, ill. oktális vagy hexadecimális escape-sorozatokhoz lehet használni, de a hatás definiálatlan, ha a megadott érték nagyobb a wchar_t típussal ábrázolható legnagyobb értéknél.
A2.5.3. Lebegőpontos állandók
A lebegőpontos állandó egy egészrészből, tizedespontból, egy törtrészből, egy e vagy E betűből, egy opcionálisan előjelezhető kitevőből, valamint az f, F, l vagy L egyikének megfelelő opcionális utótagból áll. Az egész- és törtrészt számjegyek sorozata alkotja. Az egészrész vagy a törtrész (de nem mind a kettő) hiányozhat, csakúgy, mint a tizedespont vagy az e és a kitevő (de az egyiknek léteznie kell). A lebegőpontos állandó típusát az utótag határozza meg: az f vagy F float típust, az l vagy L long double típust jelöl, minden más esetben a típus double.
A2.5.4. Felsorolt állandók
int típusú állandók felsorolásaként deklarált azonosítók. (Bővebben lásd az A8.4. pontban!)
A2.6. Karaktersorozat-állandók
Egy karaktersorozat-állandó (stringállandó) idézőjelekkel határolt karaktersorozatból áll, mint pl. a “…”. A karaktersorozat „karakteres tömb” típusú, static tárolási osztályú (l. az A4. pontot) és az adott karakterekkel inicializált adat. Az azonos karaktersorozatállandók a gépi megvalósítástól függően különbözhetnek, és ha a program megkísérli a karaktersorozat-állandó tartalmát megváltoztatni, akkor az eredmény definiálatlan.A szomszédos karaktersorozat-állandók egyetlen karaktersorozattá konkatenálódnak. Bármely konkatenáció után egy \0 végjel íródik a karaktersorozathoz, így a program a karaktersorozatot végignézve azonosíthatja annak végét. A karaktersorozat-állandók nem tartalmazhatnak újsor vagy idézőjel-karaktereket, ezek ábrázolására – csakúgy, mint a karakteres állandók esetén – a megfelelő escape-sorozatok használhatók.
Ahogy azt már a karakteres állandóknál elmondtuk, a karaktersorozat-állandóknál is az L előtagot kell használni a kiterjesztett karakterkészlet esetén, pl. L”…” formában. A széles karaktersorozat-állandók „wchar_ elemek tömbje” típusúak. A közönséges és széles karaktersorozat-állandók konkatenálásának eredménye definiálatlan.
A3. A szintaxis jelölése
A kézikönyvben a szintaktikai kategóriákat dőlt betűkkel, a literálisokat és karaktereket a programoknál használt betűtípussal jelöljük. Az alternatív kategóriák általában külön sorban, listaszerűen felsorolva szerepelnek, ill. néhány esetben a hosszú felsorolást egy sorba írtuk és előtte az „egyike a(z)” kifejezést használtuk. Az opcionális szimbólumokat az „opc” index jelzi, mint pl. az
{kifejezésopc}
esetén, ami egy kapcsos zárójelek között elhelyezett elhagyható kifejezést jelöl. A szintaxist az A13. pontban foglaltuk össze.
A4. Az azonosítók értelmezése
Az azonosítók vagy nevek többféle dologra vonatkozhatnak: kijelölhetnek függvényt, struktúra-címként, uniont, felsorolást, struktúra- vagy uniontagot, felsorolt állandót, typedef utasítással létrehozott típusnevet, ill. objektumot. Egy objektum, amit néha változónak nevezünk, a tárolóban helyezkedik el és az értelmezése két fő attribútumtól, a tárolási osztálytól és a típustól függ. A tárolási osztály az azonosítóhoz rendelt tárterület élettartamát, a típus pedig az azonosított objektumban tárolt érték jelentését határozza meg. Egy névhez egy érvényességi tartomány és egy csatolás is tartozik. Az érvényességi tartomány megadja, hogy a név a program melyik részében ismert, a csatolás pedig meghatározza, hogy ugyanaz a név egy másik érvényességi tartományban ugyanazt az objektumot vagy függvényt jelenti-e vagy sem. Az érvényességi tartomány és a csatolás leírása az A11. pontban található.
A4.1. A tárolási osztály
Két tárolási osztályt különböztetünk meg: automatikust és statikust. A tárolási osztályt több kulcsszó határozza meg az objektum deklarációjának szövegkörnyezetével együtt. Az automatikus tárolási osztályú objektumok egy blokkon belül helyiek, vagy más néven lokálisak (l. az A9.3. pontot), és a blokkból való kilépéskor elvesznek. Egy blokkon belül szereplő deklaráció, ha a tárolási osztályt külön nem specifikáltuk vagy az auto specifikációt használtuk, automatikus tárolási osztályú objektumot hoz létre. A register specifikációval deklarált objektum szintén automatikus, és (ha ez lehetséges) a számítógép gyors elérésű regisztereiben tárolódik.A statikus objektumok egy blokkra érvényes lokális vagy több blokkra érvényes külső (external) típusúak lehetnek, de mindkét esetben a függvényből vagy blokkból való kilépés és visszatérés közti időszakban is megőrzik az értéküket. Blokkon belül, beleértve a függvényen belüli blokkot is, a statikus objektum a static kulcsszóval deklarálható. Az összes blokkon kívül, a függvénydefiníciókkal azonos szinten deklarált objektumok mindig statikus tárolási osztályúak. A statikus objektumok egy adott fordítási egységre vonatkozóan lokálissá tehetők a static kulcsszó alkalmazásával. Az ilyen objektumokhoz belső csatolás tartozik. A statikus objektumok a tárolási osztály explicit megadása nélkül a teljes programra nézve globálisak, vagy az extern kulcsszó használatával tehetők globálissá. Az ilyen objektumokhoz külső csatolás tartozik.
A4.2. Alapvető adattípusok
A C nyelvben számos alapvető adattípus létezik. A B. Függelékben leírt <limits.h> standard header definiálja az egyes adattípusok helyi gépi megvalósításban érvényes legnagyobb és legkisebb értékét. A B. Függelékben megadott számok a szóba jöhető legkisebb nagyságrendet jelentik.A karakterként (char) deklarált objektumok elegendően nagyok ahhoz, hogy a végrehajtó rendszer karakterkészletének bármely tagját tárolni tudják. Ha egy, a karakterkészletből vett eredeti karaktert egy char típusú objektumban tárolunk, akkor annak értéke megegyezik a karakter egész értékű kódjával és garantáltan nem negatív. Egy char típusú változóban más objektumok is tárolhatók, de ilyenkor a rendelkezésre álló értékkészlet, valamint az érték előjeles vagy előjel nélküli ábrázolásmódja a gépi megvalósítástól függ.
Az unsigned char típusúnak deklarált karakterek ugyanakkora tárterületet igényelnek, mint a közönséges karakterek, de mindig nem negatív értékűek. Az explicit módon előjeles karaktereket signed char típusúnak kell deklarálni, és természetesen ezek is ugyanakkora helyet igényelnek, mint az egyszerű karakterek.
A char típus mellett még háromféle egész adattípus, a short int, int és long int alkalmazható. Az egyszerű int típusú objektum mérete megegyezik a befogadó számítógép társzervezéséből adódó természetes alapegységgel, és a speciális igények kielégítéséről más méretek gondoskodnak. A hosszabb egészek legalább akkora tárolóhelyet foglalnak el, mint a rövidebbek, de a gépi megvalósítás az egyszerű egészeket egyenlővé teheti a rövid vagy a hosszú egészekkel. Az int típus, ha csak másképpen nem specifikáltuk, mindig előjeles értéket jelent.
Az előjel nélküli egészek az unsigned kulcsszóval deklarálhatók és kielégítik a modulo 2n aritmetika szabályait (ahol n a gépi ábrázoláshoz használt bitek száma), így az előjel nélküli egészekkel végzett aritmetikai műveletek során túlcsordulás soha nem fordulhat elő. A nem negatív értékek halmaza egy előjeles objektumban is tárolható, mint az értékek részhalmaza és ezek az értékek előjel nélküli objektumban is tárolhatók. Ilyenkor az átfedő értékek ábrázolása azonos.Az egyszeres pontosságú lebegőpontos (float), a kétszeres pontosságú lebegőpontos (double) és az extra pontosságú lebegőpontos (long double) adattípusok egymás szinonimái lehetnek, de egy, a listában hátrébb álló típus legalább olyan pontosságú, mint az előrébb álló.
A felsorolások speciális, egyedi, egész értékű adattípusok, amelyek a névvel ellátott állandók felsorolásával kapcsolatosak (l. az A8.4. pontot). A felsorolások egész adatként viselkednek, de elég általános, hogy a fordítóprogram figyelmeztető jelzést ad, ha egy megadott felsorolás típusú objektumot nem annak egy állandójához vagy annak egy kifejezéséhez rendelünk.Mivel az eddig felsorolt objektumok mindegyike számként értelmezhető, ezért ezeket aritmetikai adattípusoknak nevezzük. A char, az előjeles vagy előjel nélküli összes int, valamint a felsorolt típusokat összefoglaló néven egész adattípusoknak nevezzük. A float, double és long double típusokat lebegőpontos adattípusoknak nevezzük.
A void típus egy üres értékkészletet specifikál. A void típust függvények visszatérési értékének típusjelzésére használjuk, és azt jelenti, hogy nem jön létre visszatérési érték.
A4.3. Származtatott adattípusok
Az alapvető adattípusokon kívül a származtatott adattípusoknak elvileg végtelen sok változata létezik, amelyek az alapvető adattípusokból az alábbi módon hozhatók létre:
- tömbök, amelyek adott típusú objektumok sorozatából állnak;
- függvények, amelyek adott típusú objektummal térnek vissza;
- mutatók, amelyek adott típusú objektumot címeznek;
- struktúrák, amelyek különböző típusú objektumok sorozatából állnak;
- unionok, amelyek a különböző típusú objektumok bármelyikét tartalmazhatják.
Általában az objektumok előállítására használt módszereket rekurzívan is alkamazhatjuk.
A4.4. Típusminősítők
Egy objektum típusa járulékos minősítőkkel rendelkezhet. Egy objektum const deklarálása azt jelzi, hogy az objektum értéke nem fog megváltozni. Az objektum volatile deklarációja az optimálásnál lényeges speciális tulajdonságokat jelzi. Egyetlen minősítő sem befolyásolja az objektum értékkészletét vagy aritmetikai tulajdonságait. A minősítőket részletesen az A8.2. pontban tárgyaljuk.
A5. Az objektumok és a balérték
Egy objektum a tároló névvel kijelölt része, a balérték pedig az objektumra hivatkozó kifejezés. A balérték (lvalue) kifejezésre jó példa a megfelelő típusú és tárolási osztályú azonosító. Az operátorok balértéket eredményeznek pl. ha E egy mutató típusú kifejezés, akkor a *E balérték kifejezés arra az objektumra hivatkozik, amire az E mutat. A „balérték” elnevezés az E1 = E2 értékadó kifejezésből származik, amelyben az E1 bal oldali operandusnak balértéknek kell lennie. Az egyes operátorok tulajdonságainak tárgyalásakor mindig megadjuk, hogy a balérték operandust igényel vagy a balérték operandust állít elő.
A6. Típuskonverziók
Néhány operátor az operandusaitól függően valamelyik operandusát az egyik adattípusról a másik adattípusra alakítja. Ebben a pontban ismertetjük az ilyen típuskonverziók várható eredményét. A közönséges operátorok típuskonverziós igényeit az A6.5. pontban összegezzük, és ezt az egyes operátorok tárgyalásánál további információkkal egészítjük ki.
A6.1. Az egész-előléptetés
Egy karakter, egy rövid egész számot, vagy egy egész értékű bitmezőt (függetlenül attól, hogy előjeles vagy előjel nélküli értékűek) vagy egy felsorolt típus objektumát minden olyan kifejezésben használhatjuk, amelyben egész mennyiséget használhatunk. Ha egy int típusú mennyiség az eredeti típus összes értékét (teljes értékkészletét) ábrázolja, akkor az értéke int típusúvá konvertálódik, máskülönben pedig unsigned int típusúvá. Ezt a típuskonverziós folyamatot egész-előléptetésnek (promóciónak) nevezzük.
A6.2. Egészek konverziója
Bármely egész úgy konvertálódik egy adott előjel nélküli típussá, hogy megkeressük azt a legkisebb nem negatív értéket, amely az előjel nélküli típussal ábrázolható legnagyobb értéknél eggyel nagyobb modulussal kongruens az egész számmal. Kettes komplemens kódú számábrázolás esetén ez megfelel a balról csonkításnak, ha az előjel nélküli típus a keskenyebb, és az előjel nélküli érték nullákkal való feltöltésének és előjel-kiterjesztéssel előjelezett értéknek, ha az előjel nélküli típus a szélesebb.Amikor bármely egész számot előjeles típusúvá alakítunk, akkor az értéke változatlan marad, ha az az új típusban ábrázolható. Minden más esetben az eredmény a gépi megvalósítástól függ.
A6.3. Egész és lebegőpontos mennyiségek
Egy lebegőpontos típusú érték egésszé alakításakor a törtrész mindenképpen elvész, és ha az eredmény nem ábrázolható az egész típussal, akkor a művelet viselkedése definiálatlan. Különösen fontos megjegyezni, hogy egy negatív lebegőpontos érték előjel nélküli egész típussá alakításának eredménye nincs specifikálva.Amikor egy egész értéket alakítunk lebegőpontossá és az érték az ábrázolható tartományban van, de egzaktul nem ábrázolható, akkor az eredmény a következő nagyobb vagy az előző kisebb ábrázolható érték lehet. Ha az átalakítás eredménye kívül esik az ábrázolható számok tartományán, akkor a művelet eredménye definiálatlan.
A6.4. Lebegőpontos típusok konverziója
Ha egy kisebb pontosságú lebegőpontos értéket egy vele egyező vagy nagyobb pontosságú lebegőpontos típussá alakítunk, akkor az érték változatlan marad. Ha egy nagyobb pontosságú lebegőpontos értéket alakítunk kisebb pontosságúvá, és az érték belül van az ábrázolható számtartományon, akkor az eredmény a következő nagyobb vagy előző kisebb ábrázolható érték lehet. Ha az eredmény az ábrázolható számtartományon kívülre esik, akkor a művelet eredménye definiálatlan.
A6.5. Aritmetikai típuskonverziók
Számos operátor eredményez típuskonverziót és az átalakítás után kapott típus előállításának módja hasonló. Az alapelv az, hogy az operandusokat olyan közös típusra hozzuk, ami megegyezik az eredmény típusával. Ezt a sémát a szokásos aritmetikai típuskonverziónak nevezzük.
Ha az egyik operandus long double típusú, akkor a, másik is long double típusúvá alakul.
Különben, ha az egyik operandus double típusú, akkor a másik is double típusúvá alakul.
Különben, ha az egyik operandus float típusú, akkor a másik is float típusúvá alakul.
Különben mindkét operandusra az egész-előléptetés fog végrehajtódni, és ha az egyik operandus unsigned long int típusú, akkor a másik is unsigned long int típusúvá alakul.
Különben, ha az egyik operandus long int, a másik unsigned int típusú, akkor a működés attól függ, hogy a long int típusú mennyiség ábrázolható-e az unsigned int összes értékével, teljes értékkészletével; ha igen, akkor az unsigned int típusú operandus long int típusúvá alakul; ha nem, akkor mindkét operandus unsigned long int típusúvá alakul.
Különben, ha az egyik operandus long int típusú, akkor a másik is long int típusúvá alakul.
Különben, ha az egyik operandus unsigned int típusú, akkor a másik is unsigned int típusúvá alakul.
Különben mindkét operandus int típusú.
A6.6. Mutatók és egész mennyiségek
Egy mutatóhoz hozzáadható vagy abból kivonható egy egész típusú kifejezés, és ilyen esetben az egész kifejezés úgy konvertálódik, ahogyan azt az összeadás operátorának leírásában (az A7.7. pontban) specifikáljuk.Két, azonos tömbben, azonos típusú objektumot címző mutató kivonható egymásból és az eredmény a kivonás operátorának leírásában (az A7.7. pontban) specifikált módon egész mennyiséggé konvertálódik.
Egy 0 értékű egész típusú állandó kifejezés, vagy kényszerített típuskonverzióval void * típusúvá alakított kifejezés kényszerített típuskonverzióval, értékadással vagy összehasonlítással bármilyen típusú objektum mutatójává konvertálható. Ez a művelet egy null-mutatót eredményez, ami egyenlő bármely, ugyanilyen típushoz tartozó null-mutatóval, de nem egyenlő bármely függvényt vagy objektumot címző mutatóval.
Más, mutatókra vonatkozó típuskonverziók is megengedettek, de ezek értelmezése függhet a gépi megvalósítástól. Az ilyen átalakításokat egy explicit típuskonverziós operátorral vagy kényszerített típuskonverzióval kell specifikálni.
Egy mutató átalakítható saját tárolásához elegendően nagy egész típussá, de a kívánt méret a gépi megvalósítástól függ. Az átalakítást végző leképező függvény szintén a gépi megvalósítástól függ.
Az egyik adattípus mutatója egy másik adattípus mutatójává alakítható, de az eredményül kapott mutató címzési hibát jelezhet, ha az átalakított mutató nem megfelelő tárillesztésű objektumra hivatkozik. Csak az garantálható, hogy egy adott objektumhoz tartozó mutató változtatás nélkül egy azonos vagy enyhébb tárillesztési feltételeket igénylő adattípus mutatójává konvertálható vagy abból visszakonvertálható. Megjegyezzük, hogy a „tárillesztés” a gépi megvalósítástól függ, és a legkevésbé szigorú tárillesztési feltétel a char típusú objektumokra vonatkozik. Mint az A6.8. pontban majd részletesen tárgyaljuk, egy mutató mindig változtatás nélkül átalakítható void * típusúvá, ill. abból visszaalakítható.
Minden mutató átalakítható egy másik, azonos típusú, legfeljebb a típusminősítő meglétében vagy hiányában különböző objektumra hivatkozó mutatóvá (l. az A4.4. és A8.2. pontot). Ha az objektum típusához minősítőt is rendelünk, akkor az új mutató egyenértékű lesz a régivel, kivéve, hogy az új minősítőtől eredő korlátozások vonatkoznak rá. Ha a minősítőt elhagyjuk, akkor az alapul szolgáló objektumra továbbra is az aktuális deklarációjában szereplő minősítő marad érvényben a műveletek során.
Végül, adott függvényhez tartozó mutató átalakítható egy másik függvénytípus mutatójává. A függvényt az átalakított mutatón keresztül híva a hatás a gépi megvalósítástól függ, viszont ha az átalakított mutatót visszaalakítjuk az eredeti típusára, akkor az eredmény azonos lesz az eredeti mutatóval kapott eredménnyel.
A6.7. A void típus
Egy void típusú objektum (nemlétező) értékét nem lehet semmiféle módon felhasználni, és sem explicit, sem implicit konverzióval nem alakítható semmiféle nem void típussá. Mivel egy void kifejezés egy nemlétező értéket jelöl, ezért egy ilyen kifejezést csak ott lehet használni, ahol nincs szükség értékre, pl. kifejezésutasításként vagy egy vessző operátor bal oldali operandusaként (l. az A9.2. és az A7.18. pontot).Egy kifejezés kényszerített típuskonverzióval alakítható void típusúvá. Például egy kényszerített void típuskonverzió törli egy kifejezésutasításban szereplő függvényhívás értékét.
A6.8. A void típushoz tartozó mutatók
Egy objektumhoz tartozó bármilyen mutató informácóveszteség nélkül átalakítható void * típusúvá. Ha az eredményt visszaalakítjuk az eredeti mutatótípusra, akkor az eredeti mutatót kapjuk vissza. Eltérően az A6.6. pontban a mutató-mutató konverziókról írtaktól, amely általában egy explicit kényszerített típuskonverziót igényel, bármely mutató értékül adható void * típusú mutatónak, ill. értéket kaphat void * típusú mutatótól, valamint az így kapott mutatók összehasonlításban is szerepelhetnek.
A7. Kifejezések
A kifejezésekben előforduló operátorok precedenciája megegyezik a következő tárgyalási sorrenddel, amelyben a legmagasabb precedenciájú operátorral kezdjük a tárgyalást. Így pl. azokat a kifejezéseket, amelyek a + operátor operandusai (A7.7. pont) lehetnek, az A7.1. … A7.6. pontokban definiáljuk. Az egyes pontokban leírt operátorok azonos precedenciájúak, és leírásuknál megadjuk a bal vagy jobb oldali asszociativitásukat is. A szintaktika A13. pontbeli leírásában összesítve is megadjuk az operátorok precedenciáját és asszociativitását.Az operátorok precedenciája és asszociativitása teljesen specifikált, de a kifejezések kiértékelési sorrendje, néhány kivételtől eltekintve definiálatlan, különösen, ha a részkifejezések mellékhatásokat eredményeznek. Ezért azt az esetet kivéve, amikor egy operátor garantálja, hogy operandusai az előírt sorrendben értékelődnek ki, a gépi megvalósítás szabadon dönthet az operandusok tetszőleges kiértékelési sorrendje mellett, vagy minden sorrend nélkül, a leghatékonyabban végezheti a kiértékelést. Természetesen az egyes operátorok az operandusaik által képviselt értékeket úgy kombinálják, hogy az kompatibilis legyen annak a kifejezésnek a szintaktikai elemzésével, amelyben előfordul.
A nyelvben nincs definiálva a túlcsordulás kezelése, az osztás ellenőrzése vagy a kifejezések kiértékelése során fellépő más kivételes esetek kezelése. A legtöbb létező C megvalósítás az előjeles egész kifejezések kiértékelésekor vagy értékadáskor fellépő túlcsordulást figyelmen kívül hagyja, de ez a működési mód nem garantálható. A nullával való osztás és az összes lebegőpontos extra eset kezelése gépi megvalósításonként változik, néha nem standard könyvtári függvényekkel oldható meg.
A7.1. Mutatógenerálás
Ha egy kifejezés vagy részkifejezés típusa valamilyen T típusra „T tömbje”, akkor a kifejezés értéke a tömb első objektumát címző mutatóra, és a kifejezés típusa „mutató T-re” típusra változik. Ez a konverzió nem történik meg, ha a kifejezés az unáris & operátor, vagy a ++, —, ill. sizeof operátor operandusa, vagy egy értékadó operátor bal oldali operandusa, vagy egy operátor operandusa. Hasonló módon egy kifejezés „T-vel visszatérő függvény” típusúról „T-vel visszatérő függvény mutatója” típusra konvertálódik, kivéve azt az esetet, amikor az & operátor operandusa.
A7.2. Elsődleges kifejezések
Elsődleges kifejezés az azonosító, az állandó, a karaktersorozat vagy a zárójelbe tett kifejezés. Az elsődleges kifejezés szintaktikai leírása:
elsődleges_kifejezés: azonosító állandó karaktersorozat (kifejezés)
Egy azonosító elsődleges kifejezés, ha a később ismertetett módon megfelelően deklarálták. Ilyenkor az azonosító típusát annak deklarációjában határozták meg. Egy azonosító balérték, ha egy objektumra hivatkozik (l. az A5. pontot) és ha típusa aritmetikai, struktúra, union vagy mutató típus.Az állandó elsődleges kifejezés, és típusa függ az alakjától, mint azt az A2.5. pontban már tárgyaltuk.
A karaktersorozat-állandó szinten elsődleges kifejezés. Típusa eredetileg „char elemek tömbje” (vagy széles karakterekből álló karaktersorozat esetén „wchar_t elemek tömbje”), de az A7.1. szabályt követve ez rendszerint „char-hoz tartozó mutatóra” vagy „wchar_t-hez tartozó mutatóra” módosul, és az eredmény a karaktersorozat első karakterére hivatkozó mutató lesz. A konverzió egyes inicializálásoknál nem jön létre, erre vonatkozóan l. az A8.7. pontot.
Egy zárójelbe tett kifejezés elsődleges kifejezés, amelynek típusa és értéke megegyezik annak egyszerű (zárójel nélküli) kifejezéséhez tartozó típussal és értékkel. A zárójel jelenléte nincs hatással a kifejezés esetleges balérték szerepére.
A7.3. Utótagos kifejezések
Az utótagos (postfix) kifejezésekben az operátorok csoportosítása balról jobbra történik. Az utótagos kifejezések szintaktikai leírása:
utótagos_kifejezés: elsődleges kifejezés utótagos_kifejezés[kifejezés] utótagos_kifejezés(argumentum_kifejezés_listaopc) utótagos_kifejezés.azonosító utótagos_kifejezés->azonosító utótagos_kifejezés++ utótagos_kifejezés-- argumentum-kifejezés_lista: értékadó_kifejezés argumentum-kifejezés_lista, értékadó_kifefezés
A7.3.1. Tömbhivatkozások
Egy szögletes zárójelbe tett kifejezéssel követett utótagos kifejezés egy utótagos kifejezést alkot, ami egy indexelt tömbre való hivatkozást jelöl. A két kifejezés egyikének „T-hez tartozó mutató” típusúnak kell lennie, ahol T bármilyen típus lehet, és a másiknak egész típusúnak kell lennie. Az indexkifejezés T típusú. Az El [E2] kifejezés definíció szerint azonos a *((E1) + (E2)) kifejezéssel. A kérdéssel bővebben az A8.6.2. pontban foglalkozunk.
A7.3.2. Függvényhívások
Egy függvényhívás szintén utótagos kifejezés, amelyet a hívott függvény nevét követő, zárójelben elhelyezett, vesszővel elválasztott elemekből álló (esetleg üres) értékadó kifejezés lista alkot. Az értékadó kifejezések listája képezi a függvény argumentumlistáját. Ha az utótagos kifejezés az aktuális érvényességi tartományban nem deklarált azonosítóból áll, akkor az azonosító implicit módon úgy deklarálódik, mint ha az
extern int azonosító( );
deklaráció a függvényhívást tartalmazó legbelső blokkban helyezkedne el. Az utótagos kifejezés típusának (az esetleges implicit deklaráció és az A7.1. pontban leírt mutatógenerálás után) „T értékkel visszatérő függvény mutatója” típusúnak kell lennie (ahol T bármilyen típus lehet), és a függvényhívás értéke T típusú.
Az argumentum megjelölést a függvényhívással átadott kifejezésekre alkalmazzuk, a paraméter megjelölést pedig a függvény definíciójában átvett, ill. a deklarációjában leírt bemeneti objektumokra (vagy azok azonosítójára) használjuk. Néha ugyanilyen értelemben használjuk az „aktuális argumentum (paraméter)” és a „formális argumentum (paraméter)” fogalmakat is.A függvényhívás előkészítése során az egyes argumentumokról másolat készül és minden argumentumátadás szigorúan érték szerint történik. A függvény megváltoztathatja a paraméterként átadott objektumok értékét, amik az argumentumkifejezés másolatai, de ez a változtatás semmiféle módon nem befolyásolhatja az argumentum értékét (vagyis a hívó oldalon szereplő értéket). Természetesen lehetőség van rá, hogy a függvénynek mutatót adjunk át és ekkor a függvény megváltoztathatja a mutatóval címzett eredeti (hívó oldali) objektum értékét is.
A függvényt kétféle stílusban deklarálhatjuk. Az új stílusú deklarációban a paraméterek típusát explicit módon adjuk meg és az a függvény típusmegadásának része. Az ilyen deklarációt függvényprototípusnak is nevezzük. A régi stílusú deklarációban a paraméterek típusát nem specifikáljuk. A függvénydeklaráció kérdéseit az A8.6.3. és A10.1. pontokban tárgyaljuk.
Ha a híváshoz tartozó függvénydeklaráció régi stílusú, akkor alapfeltételezés szerint az egyes argumentumokra bekövetkezik az ún. argumentum-előléptetés, vagyis az egész típusú argumentumok az A6.1. pontban leírt egész-előléptetéssel konvertálódnak, a float típusú argumentumok pedig double típusúvá alakulnak. A függvényhívás hatása definiálatlan, ha az argumentumok száma nem egyezik meg a definícióban szereplő paraméterek számával, vagy ha egy argumentum típusa az előléptetés után nem egyezik meg a megfelelő paraméter típusával. A típusegyeztetés attól függ, hogy a függvény definíciója régi vagy új stílusú. Ha a definíció régi stílusú, akkor a híváskor megadott argumentum előléptetett típusát hasonlítja össze a gép a paraméter előléptetett típusával. Ha a definíció új stílusú, akkor az argumentum előléptetett típusának meg kell egyezni a paraméter előléptetés nélküli típusával.
Ha a híváshoz tartozó függvénydeklaráció új stílusú, akkor az argumentumok típusa úgy konvertálódik, mint az értékadásnál, vagyis a függvényprototípusban szereplő megfelelő paraméterek típusára alakul át. Az argumentumok számának meg kell egyezni az explicit módon leírt paraméterek számával, kivéve azt az esetet, ha a deklaráció paraméterlistája a további ki nem írt paraméterekre utaló ,…) jelzéssel végződik. Ilyen esetben az argumentumok száma egyenlő vagy több kell legyen, mint a paraméterek száma és az explicit módon beírt paraméterekhez képest többlet argumentumok az előzőekben leírt argumentum-előléptetésnek lesznek kitéve. Ha a függvény definíciója régi stílusú, akkor a híváskor a prototípusban látható egyes paraméterek típusának meg kell egyezni a definícióban szereplő, a definíció paramétereire végrehajtott argumentum előléptetés utáni paramétertípusokkal.
Az argumentumok kiértékelési sorrendje nincs meghatározva, a különböző fordítóprogramok eltérően viselkednek. Mindezek ellenére az argumentumok és maga a függvénykijelölés a függvénybe való belépés előtt teljesen kiértékelődnek, beleértve a mellékhatásokat is.
A7.3.3. Struktúrahivatkozások
Utótagos kifejezést alkot egy utótagos kifejezést követő pontból és az azt követő azonosítóból álló szerkezet. Az első operandust alkotó kifejezésnek sturktúrának vagy unionnak, a pont után következő azonosítónak pedig egy struktúra- vagy uniontag nevének kell lennie. Az így kapott utótagos kifejezés értéke a struktúra vagy union megnevezett tagjának értéke és típusa a tag típusa. A kifejezés balérték, ha az első kifejezés balérték és ha a második kifejezés típusa nem egy tömb.Egy utótagos kifejezést követő nyílból (amit a – és > jelekből rakunk össze) és egy azt követő azonosítóból álló szerkezet szintén utótagos kifejezést alkot. Az első operandust alkotó kifejezésnek sruktúrát vagy uniont címző mutatónak, az azonosítónak pedig a struktúra- vagy uniontag nevének kell lennie. A művelet eredménye a mutatókifejezéssel címzett struktúra vagy union megnevezett tagjának értéke és típusa a tag típusának felel meg. Az eredmény balérték, ha a típus nem tömbtípus.
A fentiek alapján az E1->TAG kifejezés azonos a (*E1).TAG kifejezéssel. A sturktúrákat és az unionokat az A8.3. pontban ismertetjük.
A7.3.4. Utótagos inkrementálás
Egy utótagos kifejezést követő ++ vagy — operátorból álló szerkezet szintén utótagos kifejezés. A kifejezés értéke az operandus értéke. Az érték elővétele (és felhasználása) után az operandus értéke ++ esetén eggyel növekszik (inkrementálás), — esetén pedig eggyel csökken (dekrementálás). Az operandusnak balértéknek kell lenni. Az operandusra vonatkozó további megszorítások, ill. a működés részletei az additív operátoroknál (A7.7.) és az értékadásnál (A7.17.) találhatók.
A7.4. Egyoperandusú operátorok
Az egyoperandusú (unáris) operátorokkal létrehozott kifejezések csoportosítása jobbról balra történik. Az egyoperandusú kifejezések szintaktikai leírása:
egyoperandusú_kifejezés: utótagos_ kifejezés ++ egyoperandusú_kifejezés -- egyoperandusú_kifejezés egyoperandusú_operátor kényszerített_típuskonverziójú_kifejezés sizeof egyoperandusú_kifejezés sizeof (típusnév) egyoperandusú_operátorok: egyike a következőknek: & * + - ~ !
A7.4.1. Előtagos inkrementáló operátorok
Egy egyoperandusú kifejezést megelőző ++ vagy — operátorból álló szerkezet szintén egyoperandusú kifejezés. A végrehajtás során az operandus ++ esetén eggyel növekszik (inkrementálás), — esetén pedig eggyel csökken (dekrementálás). A kifejezés értéke az inkrementálás vagy dekrementálás után kapott érték. Az operandusnak balértéknek kell lennie. Az operandusra vonatkozó további megszorítások, ill. a működés részletei az additív operátoroknál (A7.7.) és az értékadásnál (A7.17.) találhatók.
A7.4.2. Címoperátor
Az egyoperandusú & operátor az operandusának a címét állítja elő. Az operandusnak nem bitmezőre vagy register típusúnak deklarált objektumra hivatkozó balértéknek, vagy függvény típusúnak kell lenni. Az eredmény egy mutató, amely a balértékkel egy objektumot vagy függvényt címez. Ha az operandus típusa T, akkor az eredmény típusa „T típust címző mutató”.
A7.4.3. Indirekciós operátor
Az egyoperandusú * indirekciós operátor eredményül azt az objektumot vagy függvényt adja, amelyre az opreandusa mutat. Az eredmény balérték, ha az operandus egy aritmetikai objektum, struktúra, union vagy mutató típus mutatója. Ha a kifejezés típusa „T típust címző mutató”, akkor az eredmény típusa T.
A7.4.4. Egyoperandusú plusz operátor
Az egyopreandusú + operátor operandusának aritmetikai típusúnak kell lenni, és az eredmény az operandus értéke. Egy egész típusú operandusra végrehajtódik az egész előléptetés. Az eredmény típusa megegyezik az előléptetett operandus típusával.
A7.4.5. Egyoperandusú mínusz operátor
Az egyoperandusú – operátor operandusának aritmetikai típusúnak kell lenni, és az eredmény az operandus negáltja. Egész típusú operandus esetén végrehajtódik az egészelőléptetés. Egy előjel nélküli mennyiség negáltját úgy számítjuk ki, hogy az előléptetett értékét kivonjuk az előléptetett típussal ábrázolható legnagyobb számból, és hozzáadunk egyet. A negatív nulla értéke nulla lesz. A művelet eredményének típusa megegyezik az előléptetett operandus típusával.
A7.4.6. Egyes komplemens operátor
A ~ operátor operandusának egész típusúnak kell lenni és az eredmény az operandus egyes komplemense. Az egész-előléptetés itt is végrehajtódik. Ha az operandus előjel nélküli, akkor az eredményt úgy számítjuk ki, hogy az operandus értékét kivonjuk az előléptetett típussal ábrázolható legnagyobb számból. Ha az operandus előjeles mennyiség, akkor az eredményt úgy számítjuk ki, hogy az előléptetett operandust a megfelelő előjel nélküli típusra konvertáljuk, végrehajtjuk a ~ műveletet, majd a kapott értéket visszakonvertáljuk előjeles típusra. Az eredmény típusa megegyezik az előléptetett operandus típusával.
A7.4.7. Logikai negálás operátor
A ! operátor operandusának aritmetikai vagy mutató típusúnak kell lenni, és az eredmény 1, ha az operandus értéke 0, ill. 0 minden más esetben. Az eredmény típusa int.
A7.4.8. Sizeof operátor
A sizeof operátor az operandusaként megadott típusú objektum tárolásához szükséges bájtok számát határozza meg. Az operandus egy kifejezés (amely nem értékelődik ki) vagy egy zárójelben elhelyezett típusnév lehet. A sizeof operátort char típusra alkalmazva, az eredmény 1 lesz, tömbre alkalmazva pedig a tömb által lefoglalt teljes terület bájtban mért hossza. Az operátort struktúrára vagy unionra alkalmazva az eredmény az objektumban lévő bájtok száma, beleértve a helykitöltő bájtokat is (amelyek az illesztési feltételek teljesítése miatt szükségesek). Tömb esetén a hosszt úgy kapjuk meg, hogy az elemek számát szorozzuk egy elem méretével. Az operátort nem alkalmazhatjuk függvény típusra, bitmezőre vagy nem teljes típusra. Az eredmény előjel nélküli egész típusú állandó, amelynek a típusa a gépi megvalósítástól függ. Ezt a típust az <stddef.h> standard header (l. a B. Függeléket) size_t típusként definiálja.
A7.5. Kényszerített típusmódosító
Egy egyoperandusú kifejezést megelőző zárójelbe tett típusnév a kifejezés értékének megadott típusra alakítását okozza. A kényszerített típusmódosítás szintaktikai leírása:
kényszerített_típusmódosítójú_kifejezés: egyoperandusú kifejezés (típusnév) kényszerített_típusmódosítójú_kifejezés
Ezt a konstrukciót kényszerített típusmódosításnak (a C nyelv terminológiájában cast szerkezetnek) nevezik. A típusnevek leírása az A8.8. pontban található, az átalakítás hatásait pedig az A6. pontban adtuk meg. A kényszerített típusmódosítójú kifejezés nem balérték.
A7.6. Multiplikatív operátorok
A *, / és % multiplikatív operátorok csoportosítása balról jobbra történik. A szintaktikai leírásuk:
multiplikatív_kifejezés: kényszerített_típusmódosítójú_kifejezés multiplikatív_kifejezés * kényszerített_típusmódosítójú_kifejezés multiplikatív_kifejezés / kényszerített_típusmódosítójú_kifejezés multiplikatív_kifejezés % kényszerített_típusmódosítójú_kifejezés
A * és / operátorok operandusainak aritmetikai típusúnak, a % operátor operandusainak egész típusúnak kell lenni. A művelet során az operandusok szokásos aritmetikai átalakításai végbemennek és ezekből meghatározható az eredmény várható típusa.A kétoperandusú * operátor a szorzás műveletét jelzi.
A kétoperandusú / operátor a hányadost, a kétoperandusú % operátor a maradékot állítja elő, ha az első operandust osztjuk a másodikkal. Ha a második operandus nulla, akkor az eredmény definiálatlan. Ha a második operandus nulla, akkor mindig igaz az
(a/b)*b + a%b = a
összefüggés. Ha mindkét operandus nem negatív, akkor a maradék nem negatív és mindig kisebb, mint az osztó. Ha a fenti feltétel nem teljesül, akkor csak az garantálható, hogy a maradék abszolút értéke kisebb az osztó abszolút értékénél.
A7.7. Additív operátorok
A + és – additív operátorok csoportosítása balról jobbra történik. Ha az operandusok aritmetikai típusúak, akkor végbemennek a szokásos aritmetikai típuskonverziók. Mindkét operátor esetén vannak további típuslehetőségek is. Az additív operátorok szintaktikai leírása:
additív_kifejezés: multiplikatív_kifejezés additív_kifejezés + multiplikatív_kifejezés additív_kifejezés - multiplikatív_kifejezés
A + operátor eredménye az operandusok összege. Egy tömb egy objektumát címző mutató bármilyen egész típusú értékkel összeadható, és ilyenkor a második operandus a mutatóval címzett objektum méretével való szorzással egy címeltolássá alakul. Az eredmény az eredeti mutatóval azonos típusú mutató, amely ugyanazon tömb másik, az eredeti objektumtól a címeltolással odébb lévő objektumát címzi. Így ha P egy tömb objektumának mutatója, akkor P + 1 kifejezés szintén egy mutató, amely a tömb következő objektumát címzi. Ha az összegként kapott mutató a tömb határain kívülre címez, kivéve a felső határ utáni első helyet, akkor az eredmény definiálatlan.
A – operátor eredménye az operandusok különbsége. Egy mutatóból bármilyen egész típusú érték kivonható, és a műveletre az összeadásnál elmondott típuskonverziós szabályok, ill. feltételek érvényesek.Ha két, azonos típusú objektumot címző mutatót kivonunk egymásból, akkor az eredmény előjeles egész érték, ami két megcímzett objektum közötti címkülönbséget jelenti. Ez az érték egymást követő objektumok esetén 1. Az eredmény típusa a gépi megvalósítástól függ, de az <stddef.h> standard headerben ez a típus ptrdiff_t típusként van definiálva. A kivonással kapott érték csak akkor definit, ha a mutatók azonos tömb elemeit címzik. Ha P egy tömb utolsó elemére mutat, akkor mindig igaz, hogy
(P + 1) - P = 1.
A7.8. Léptető operátorok
A << és >> léptető operátorok csoportosítása balról jobbra történik. Mindkét operátor operandusainak egész típusúaknak kell lenni és végbemegy az egész-előléptetés. Az eredmény típusa megegyezik a bal oldali, előléptetett operandus típusával. Az eredmény definiálatlan, ha a jobb oldali operandus negatív, vagy ha értéke a bal oldali kifejezés típusának megfelelő gépi ábrázolás bitszámával egyenlő, ill. annál nagyobb. A szintaktikai leírás:
léptető_kifejezés: additív_kifejezés léptető_kifejezés << additív_kifejezés léptető_kifejezés >> additív_kifejezés
Az E1<<E2 kifejezés értéke a bitmintaként értelmezett E1 E2 számú bittel balra léptetett értéke, ami, ha nem jött létre túlcsordulás, akkor a 2E2 értékkel való szorzásnak felel meg. Az E1>>E2 kifejezés értéke az E1 E2 számú bittel jobbra léptetett értéke. A jobbra léptetés 2E2 értékkel való osztásnak felel meg, ha E1 előjel nélküli vagy nem negatív mennyiség. Minden más esetben az eredmény a gépi megvalósítástól függ.
A7.9. Relációs operátorok
A relációs operátorok csoportosítása balról jobbra történik, de ennek nincs túl nagy jelentősége, mivel a kiértékelés során az a<b<c reláció mindig (a<b)<c alakra íródik át és először mindig az a<b értékelődik ki, aminek értéke 0 vagy 1 lesz. A szintaktikai leírás:
relációs_kifejezés: léptető_kifejezés relációs_kifejezés < léptető_kifejezés relációs_kifejezés > léptető_kifejezés relációs_kifejezés <= léptető_kifejezés relációs_kifejezés >= léptető_kifejezés
A < (kisebb), > (nagyobb), <= (kisebb vagy egyenlő) és >= (nagyobb vagy egyenlő) operátorok 0 eredményt adnak, ha a kijelölt reláció hamis és 1 eredményt, ha igaz. Az eredmény int típusú. Az aritmetikai típusú operandusokon végrehajtódnak a szokásos aritmetikai típuskonverziók. Csak (a minősítőket figyelmen kívül hagyva) azonos típusú objektumokhoz tartozó mutatók hasonlíthatók össze és az eredmény a címzett objektumok címtartományon belüli egymáshoz viszonyított (relatív) helyétől függ. A mutatók összehasonlítása csak azonos objektum részeire van értelmezve: ha két mutató ugyanazon egyszerű objektumot címzi, akkor összehasonlíthatók egyenlőségre; ha a mutatók ugyanazon struktúra tagjait címzik, akkor a struktúrában később deklarált objektumokhoz tartozó mutatók összehasonlíthatók a nagyobb feltétel szerint; ha a mutatók egy tömb elemeit címzik, akkor az összehasonlítás egyenértékű a megfelelő indexek összehasonlításával. Ha a P mutató egy tömb utolsó elemét címzi, akkor az összehasonlításban P+1 nagyobb mint P, függetlenül attól, hogy P+1 már a tömbön kívülre mutat. Minden, itt felsoroltaktól eltérő esetben a mutatók összehasonlítása nincs definiálva.
A7.10. Egyenlőségoperátorok
Az egyenlőségoperátorok szintaktikai leírása:
egyenlőség_kifejezés: relációs_kifejezés egyenlőség_kifejezés == relációs_kifejezés egyenlőség_kifejezés != relációs_kifejezés
A == (egyenlő valamivel) és a != (nem egyenlő valamivel) operátorok megegyeznek a megfelelő relációs operátorokkal, kivéve, hogy alacsonyabb a precedenciájuk. (így a<b == c<d akkor 1, ha a<b és c<d egyformán igaz vagy egyformán hamis.)Az egyenlőségoperátorok eleget tesznek mindazon szabályoknak, mint a relációs operátorok, de azokhoz képest további lehetőségeket is megengednek: mutatót összehasonlíthatunk egy állandó egész kifejezéssel vagy egy void típushoz tartozó mutatóval (l. az A6.6. pontot).
A7.11. Bitenkénti ÉS operátor
A szintaktikai leírás:
ÉS_kifejezés: egyenlőség_kifejezés ÉS_kifejezés & egyenlőség_kifejezés
A szokásos aritmetikai típuskonverziók végbemennek, az eredmény az operandusok bitenkénti ÉS (AND) kapcsolata. Az operátor csak egész típusú operandusokra alkalmazható.
A7.12. Bitenkénti kizáró VAGY operátor
Szintaktikai leírás:
kizáró_VAGY_kifejezés: ÉS_kifejezés kizáró_VAGY_kifejezés^ ÉS_kifejezés
A szokásos aritmetikai típuskonverziók végrehajtódnak, az eredmény az operandusok bitenkénti kizáró VAGY kapcsolata. Az operátor csak egész típusú operandusokra alkalmazható.
A7.13. Bitenkénti inkluzív VAGY operátor
Szintaktikai leírás:
inkluzív_VAGY_kifejezés: kizáró_VAGY_kifejezés inkluzív_VAGY_kifejezés | kizáró_VAGY_kifejezés
A szokásos aritmetikai típuskonverziók végbemennek, az eredmény az operandusok bitenkénti inkluzív VAGY kapcsolata. Az operátor csak egész típusú operandusokra alkalmazható.
A7.14. Logikai ÉS operátor
Szintaktikai leírás:
logikai_ÉS_kifejezés: inkluzív_ VAGY_kifejezés logikai_ÉS_kifejezés && inkluzív_VAGY_kifejezés
Az && operátor csoportosítása balról jobbra történik. A művelet eredménye 1, ha mindkét operandus nullától különböző, és 0 különben. Az & operátortól eltérően az && operátor esetén garantált a balról jobbra történő végrehajtás: először az első operandus értékelődik ki, beleértve az összes mellékhatást is, és ha ez 0 értékű, akkor a teljes kifejezés értéke nulla. Különben a jobb oldali operandus is kiértékelődik, és ha az értéke nulla, akkor a teljes kifejezés értéke nulla. Minden más esetben a teljes kifejezés értéke 1.Nem szükséges, hogy az operandusok azonos típusúak legyenek, de mindegyiknek aritmetikai vagy mutató típusúnak kell lennie. A művelet eredménye int típusú.
A7.15. Logikai VAGY operátor
A szintaktikai leírás:
logikai_VAGY_kifejezés logikai_ÉS_kifejezés logikai_VAGY_kifejezés || logikai_ÉS_kifejezés
A || logikai VAGY operátor csoportosítása balról jobbra történik. A művelet eredménye 1, ha az egyik operandus nullától különböző és minden más esetben 0. Az | operátortól eltérően a || operátor esetén garantált a balról jobbra való kiértékelés: először mindig az első operandus értékelődik ki (beleértve a mellékhatásokat is) és ha ez nem egyenlő nullával, akkor a kifejezés értéke 1. Máskülönben a jobb oldali operandus értékelődik ki, és ha ez nem egyenlő nullával, a kifejezés értéke 1, ha nulla, akkor pedig 0.Nem szükséges, hogy az operátor operandusai azonos típusúak legyenek, de mindegyiknek aritmetikai vagy mutató típusúnak kell lennie. A művelet eredménye int típusú.
A7.16. Feltételes operátor
A szintaktikai leírás:
feltételes_kifejezés: logikai_VAGY_kifejezés logikai_VAGY_kifejezés ? kifejezés : feltételes_kifejezés
A művelet végrehajtása során kiértékelődik az első kifejezés (beleértve a mellékhatásokat is) és ha ez nem nulla, akkor az eredmény a második kifejezés értéke, különben pedig a harmadik kifejezés értéke. A második és harmadik kifejezések közül mindig csak az egyik értékelődik ki. Ha a második és harmadik kifejezés (operandus) aritmetikai típusú, akkor a szokásos aritmetikai típuskonverziók mennek végbe a közös típus elérése érdekében, és ez a közös típus lesz az eredmény típusa is. Ha mindkét operandus void típusú, vagy azonos típusú struktúra, ill. union, vagy azonos típusú objektumok mutatója, akkor az eredmény típusa a közös típus. Ha az egyik operandus mutató, a másik pedig 0 értékű állandó, akkor a 0 mutató típusra konvertálódik és ez lesz az eredmény típusa is. Ha az egyik operandus void típushoz tartozó mutató, a másik pedig nem, akkor a másik operandus is void típushoz tartozó mutatóvá alakul és ez lesz az eredmény típusa is.A mutatók típusának összehasonlításakor a mutatókkal címzett objektumok típusát meghatározó típusminősítők (l. A8.2. pont) érdektelenek, de az eredmény típusa örökli a feltétel mindkét oldalának típusminősítőjét.
A7.17. Értékadó kifejezések
A C nyelvben számos értékadó operátor létezik, amelyek csoportosítása jobbról balra történik. Az értékadó kifejezés szintaktikai leírása:
értékadó_kifejezés: feltételes_kifejezés egyoperandusú_kifejezés értékadó_operátor értékadó_kifejezés értékadó_operátor: egyike a következőknek: = *= /= %= += -= <<= >>= &= ^= |=
Az összes operátor bal oldali operandusként balértéket igényel és ennek a balértéknek módosíthatónak kell lennie, azaz nem lehet tömb és nem lehet nemteljes vagy függvény típusú. Az szintén követelmény, hogy a típusa nem lehet const minősítésű, ill. ha struktúra vagy union, akkor nem lehet egyetlen tagja vagy rekurzívan beleágyazott altagja sem const minősítésű. Egy értékadó kifejezés típusa a bal oldali operandus típusával egyezik meg és értéke az értékadás után a bal oldali operandusban tárolt érték lesz.Az egyszerű, = jellel történő értékadás esetén a kifejezés értéke beíródik a balértékkel kijelölt objektumba. A következő állítások egyike igaz kell hogy legyen: mindkét operandus aritmetikai típusú, és ebben az esetben a jobb oldali operandus az értékadás során a bal oldali operandus típusára konvertálódik; mindkét operandus azonos típusú struktúra vagy union; az egyik operandus mutató, a másik void típushoz tartozó mutató; a bal oldali operandus egy mutató és a jobb oldali egy 0 értékű állandó kifejezés; mindkét operandus függvényhez vagy azonos típusú objektumokhoz (kivéve, hogy a jobb oldali operandus nem lehet const vagy volatile minősítésű) tartozó mutató.
Az E1 op= E2 alakú kifejezés egyenértékű az E1 = E1 op (E2) kifejezéssel, kivéve, hogy E1 csak egyszer értékelődik ki.
A7.18. Vesszőoperátor
A szintaktika leírása:
kifejezés: értékadó_kifejezés kifejezés, értékadó_kifejezés
Egy vesszővel elválasztott kifejezéspár balról jobbra értékelődik ki és a bal oldali kifejezés értéke elvész. Az eredmény típusa és értéke megegyezik a jobb oldali operandus típusával és értékével. A jobb oldali operandus kiértékelése során létrejövő mellékhatások teljesen lezajlanak. Olyan szövegkörnyezetben, ahol a vessző speciális jelentésű pl. függvények argumentumlistájában (A7.3.2.) vagy inicializáló kifejezések listájában (A8.7.), az igényelt szintaktikai egység egy értékadó kifejezés, így a vesszőoperátor csak zárójelbe tett csoporton belül jelenhet meg. Például az
f(a, (t=3, t + 2 ), c)
függvénynek három argumentuma van, és ezek közül a második (vesszőoperátorral előállított) értéke 5.
A7.19. Állandó kifejezések
Szintaktikailag egy állandó kifejezés az operátorok egy részhalmazára korlátozódó kifejezés. A szintaktikai leírása:
állandó_kifejezés: feltételes_kifejezés
A kifejezéseket kiértékelve állandó értéket kapunk, ami különböző értelemben használható (pl. a case utasítás után, tömbhatárként vagy bitmező hosszaként, felsorolt állandó értékeként, kezdeti értékként, az előfeldolgozó rendszer bizonyos kifejezéseiben).Az állandó kifejezések nem tartalmazhatnak értékadást, inkrementáló vagy dekrementáló operátorokat, függvényhívást vagy vesszőoperátort, kivéve a sizeof operátor operandusát. Ha az állandó kifejezésnek egész típusúnak kell lennie, akkor az operandusai csak egész, felsorolt, karakteres és lebegőpontos állandókat tartalmazhatnak, a kényszerített típuskonverziónak egész típust kell kijelölni és bármely lebegőpontos állandót kényszerített típuskonverzióval kell egész típusúvá alakítani. Ennek következtében a műveletben nem szerepelhetnek tömbökre vonatkozó műveletek, indirekció, címgenerálás és struktúratagra vonatkozó művelet. (Viszont bármely operandusra alkalmazható a sizeof operátor.)
A C nyelv a kezdeti értéket adó állandó kifejezések számára sokkal tágabb lehetőségeket enged meg. Az operandusok bármilyen típusú állandók lehetnek, az egyoperandusú & operátor alkalmazható a külső vagy statikus objektumokra, valamint állandó kifejezéssel indexelt statikus tömbökre. Az egyoperandusú & operátor ugyancsak alkalmazható implicit módon indexeletlen tömbökre és függvényekre. A kezdeti értéket adó kifejezés kiértékelésével állandót vagy egy korábban deklarált külső vagy statikus objektum állandóval növelt vagy csökkentett címét kell kapnunk.
Az #if utasítás utáni egész típusú állandó kifejezésekre kevesebb lehetőség van megengedve: ilyen helyen a sizeof kifejezés, felsorolt állandó és kényszerített típuskonverzió nem alkalmazható (l. az A12.5. pontot).
A8. Deklarációk
A deklarációk határozzák meg a fordítóprogram számára az egyes azonosítók értelmezését. A deklaráció nem szükségszerűen jelent tárbeli helyfoglalást az azonosító számára. A tárterületet lefoglaló deklarációkat definíciónak nevezzük. A deklaráció formája:
deklaráció: deklaráció_specifikátorok kezdeti_deklarátor_listaopc;
A kezdeti deklarátorlistában szereplő deklarátorok tartalmazzák a deklarálandó azonosítókat. A deklaráció specifikátorok típus és tárolási osztály specifikátorokból állnak.
deklaráció_specifikátorok: tárolási_osztály_specifikátor deklaráció_specifikátoropc típus_specifikátor deklaráció_specifikátoropc típus_minősítő deklaráció_specifikátoropc kezdeti_deklarátor_lista: kezdeti_deklarátor kezdeti_deklarátor_lista, kezdeti_deklarátor kezdeti_deklarátor: deklarátor deklarátor = kezdeti_érték
A deklarátorokat az A8.5. pontban fogjuk részletesen tárgyalni, itt csak annyit említünk meg, hogy a deklarátorok tartalmazzák a deklarálandó neveket. Egy deklarációnak legalább egy deklarátort vagy egy struktúracímkét, unioncímkét, ill. felsorolástagot deklaráló típusspecifikátort kell tartalmaznia. Az üres deklarációk nem használhatók.
A8.1. Tároláslosztály-specifikátorok
A tárolásiosztály-specifikátorok a következők:
tárolási_osztály_specifikátor: auto register static extern typedef
Az egyes tárolási osztályok jelentését az A4. pontban tárgyaltuk.Az auto és register specifikátorok azt mondják ki, hogy a deklarált objektumok automatikus tárolási osztályúak és csak függvényeken belül használhatók. Az ilyen deklarációk egyben definíciók is és lefoglalják az objektum számára a tárolóhelyet. Egy register deklaráció egyenértékű az auto deklarációval, de arra utal, hogy a deklarált objektumot gyakran kívánjuk használni. Ténylegesen csak kevés objektum helyezkedik el regiszterben és csak meghatározott típusú objektumok lehetnek regiszteres típusúak. A korlátozások a gépi megvalósítástól függenek. Ha az objektum register tárolási osztályúnak deklarált, akkor az egyoperandusú & címgeneráló operátor sem explicit, sem implicit módon nem alkalmazható rá.
A static specifikáció hatására a deklarált objektum statikus tárolási osztályú lesz, és függvények belsejében vagy azokon kívül egyaránt használható. Egy függvény belsejében a static specifikátor egyben tárterületet is rendel az objektumhoz (azaz definícióként szolgál), a függvényen kívüli alkalmazásra vonatkozóan l. az A11.2. pontot.Az extern deklaráció használható a függvények belsejében és azt jelzi, hogy a deklarált objektumhoz máshol rendeljük hozzá a tárterületet, a függvényen kívüli alkalmazásokra vonatkozóan l. az A11.2. pontot.
A typedef specifikátor nem foglal le tárterületet és csak a kényelmes szintaktikai leírás miatt nevezzük tárolásiosztály-specifikátornak. A részletes leírása az A8.9. pontban található.
Egy deklarációban legfeljebb csak egy tárolásiosztály-specifikátor adható meg. Ha egyet sem adunk meg, akkor a következő szabályok érvényesek: egy függvényen belül deklarált objektum mindig auto tárolási osztályú lesz; a függvényen belül deklarált függvények mindig extern tárolási osztályúak lesznek; a függvényen kívül deklarált objektumok és függvények mindig külső csatolással rendelkező statikus tárolási osztályúak lesznek. A részletesebb leírás az A10. és A11. pontokban található.
A8.2. Típusspecifikátorok
A típusspefcifikátorok a következők:
típus_specifikátor: void char short int long float double signed unsigned struktúra_vagy_union_specifikátor felsorolás_specifikátor typedef_név
A short vagy long specifikátorok közül legfeljebb az egyik alkalmazható az int specifikátorral együtt, és az ilyen deklarációk jelentése ugyanaz, mint ha az int nem is szerepelne benne. A long specifikátor alkalmazható a double specifikátorral együtt is. A signed vagy unsigned specifikátorok közül legfeljebb egy alkalmazható az int-tel vagy annak short, ill. long változatával, vagy char specifikátorral együtt. A signed és unsigned specifikátorok bármelyike önmagában is megjelenhet, az int megadása magától értetődő, így elhagyható. A signed specifikátor alkalmazásával a char típusú mennyiségekre rákényszeríthető az előjeles adatábrázolás, viszont az alkalmazásuk az egész típusokkal együtt megengedett, de redundáns.A fentieken kívül, minden más esetben egy deklarációban legfeljebb csak egy típusspecifikátor használható. Ha egy deklarációból hiányzik a típusspecifikátor, akkor a fordítóprogram a deklarált objektumot int típusúnak tekinti.
A típusok minősítettek is lehetnek, és a minősítés a deklarált objektum speciális tulajdonságait jelzi.
típusminősítő: const volatile
A típusminősítők bármelyik típusspecifikátorral együtt is megjelenhetnek. A const minősítésű objektumhoz kezdeti értéket értéket rendelhetünk, de azután az értéke már nem változtatható. A volatile minősítőjű objektumoknak nincs a gépi megvalósítástól független szemantikájuk.
A8.3. Struktúrák és unionok deklarációja
A struktúra különböző típusú, névvel azonosított tagok sorozatából álló objektum. Az union különböző időpontokban a különböző típusú tagok egyikét tartalmazza. A struktúra- és unionspecifikátorok azonos alakúak.
struktúra_vagy_union_specifikátor: struktúra_vagy_union azonosítóopc { struktúra_deklarációs_lista } struktúra_vagy_union azonosító struktúra_vagy_union: struct union
A struktúradeklarációs lista a struktúra vagy union tagjaihoz tartozó deklarációk sorozata.
struktúra_deklarációs_lista: struktúra_deklaráció struktúra_deklarációs_lista struktúra_deklaráció struktúra_deklaráció: specifikátor_minősítő_lista struktúra_deklarátor_lista specifikátor_minősítő_lista: típus_specifikátor specifikátor_minősítő_listaopc típusminősítő specifikátor_minősítő_listaopc struktúra_deklarátor_lista: struktúra_deklarátor struktúra_deklarátor_lista, struktúra_deklarátor
Általában egy struktúradeklarátor a struktúra vagy union egy tagjának deklarátora. Egy struktúratag meghatározott számú bitből is állhat és az ilyen tagokat bitmezőnek vagy röviden mezőnek nevezzük. A mező hossza a deklarátorból, a mező nevét követő kettőspont utáni részből vehető ki.
struktúra_deklarátor: deklarátor deklarátoropc : állandó kifejezés
A következő alakú típusspecifikátor
struktúra_vagy_union azonosító { struktúra_deklarációs_lista }
az azonosítót a listában megadott struktúra vagy union címkéjeként deklarálja. Egy soron következő, az előzővel azonos vagy azon belüli érvényességi tartományú deklaráció ugyanerre a típusra a specifikátorban használt címke alapján, a lista nélkül hivatkozhat. Ennek formája:
struktúra_vagy_union azonosító
Ha egy specifikátor a címkével, de lista nélkül jelenik meg, amikor a címke még nincs deklarálva, akkor egy nemteljes típus megadásáról beszélünk. Egy nemteljes struktúra vagy union típusú objektum minden olyan programkörnyezetben használható, ahol az objektum méretére nincs szükség, és csak ilyen esetekben használható. Megengedett pl. a használata deklarációkban (de nem definíciókban), mutató specifikálásakor vagy typedef létrehozásakor. Ügyeljünk arra, hogy a listával ellátott specifikátorban a struktúra vagy union típusa a listán belül nemteljes deklarációjú és csak akkor válik teljessé, ha a specifikátort a } kapcsos zárójellel lezárjuk!Egy struktúra nem tartalmazhat nemteljes típusú tagot, ezért lehetetlen olyan struktúrát vagy uniont deklarálni, amely saját magát tartalmazza. Ettől függetlenül a struktúra vagy union típushoz nevet rendelhetünk és az így kapott címke már lehetővé teszi önhivatkozó struktúrák definiálását. Ez azon alapszik, hogy a struktúra vagy union tartalmazhat egy önmagát címző mutatót, mivel a nemteljes típusokhoz is deklarálhatók mutatók.
Nagyon speciális szabály alkalmazható a
struktúra_vagy_union azonosító;
alakú deklarációkra, amelyek egy struktúrát vagy uniont deklarálnak, de nincs a deklarációban sem deklarációs lista, sem deklarátor. Ez a deklaráció az aktuális érvényességi tartományon belül még akkor is létrehoz egy új, nemteljes típusú struktúrát vagy uniont az azonosítónak megfelelő címkével, ha az azonosító egy külső érvényességi tartományban már deklarált struktúra vagy union címkéje.
Egy listát igen, de címkét nem tartalmazó struktúra- vagy unionspecifikátor egy egyedi típust hoz létre, amire közvetlenül csak abban a deklarációban hivatkozhatunk, amelynek része.
A tagok és címkék neve nem kerül konfliktusba egymással vagy a közönséges változók nevével. Egy tagnév nem jelenhet meg kétszer ugyanazon struktúrában vagy unionban, de ugyanaz a tagnév más struktúrában vagy unionban használható.
Egy struktúra vagy union nem mező típusú tagja bármilyen objektumnak megfelelő típussal rendelkezhet. Egy mező típusú tag (amelyhez nem szükséges, hogy deklarátor tartozzon, ezért név nélküli is lehet) int, unsigned int vagy signed int típusú, és úgy értelmezhető, mint az adott számú bitnek megfelelő hosszúságú egész típusú objektum. Az, hogy az int típusú mező, mint előjeles mennyiség, hogyan kezdődik, a gépi megvalósítástól függ. A struktúra szomszédos, mező típusú tagjai a gépi megvalósítástól függő méretű tárolóhelyekre, egymás mellé kerülnek, de az elhelyezési sorrendjük szintén a megvalósítástól függ. Amikor egy mezőt követő másik mező nem illeszthető egy részlegesen feltöltött tárolóhelybe, akkor a fordítóprogram megosztja azokat két tárolóhely között vagy a második mezőt teljes egészében egy új tárolóhelyre teszi és a részlegesen feltöltött tárolóhelyre helykitöltő egységet rak. Ezt a helykitöltést a 0 hosszúságú, név nélküli mező alkalmazásával lehet kikényszeríteni, így az utána következő mező már biztosan a következő tárolóhely kezdetére kerül.
Egy struktúra tagjai a deklaráció sorrendjében, folyamatosan növekvő címeken helyezkednek el. A nem mező típusú tagok az adott típustól függő címhatárhoz illeszkednek, ezért a struktúrában név nélküli lyukak (üres helyek) lehetnek. Ha egy struktúrát címző mutatót kényszerített típuskonverzióval a struktúra első tagját címző mutatóvá alakítunk, az eredő mutató ténylegesen a struktúra első tagjára fog mutatni.Az uniont olyan struktúraként értelmezhetjük, amelynek tagjai a 0 ofszetnél kezdődnek és méretük elegendő bármelyik tag befogadására. Egy union egy időben legfeljebb csak egyetlen tagját tartalmazhatja. Ha egy uniont címző mutatót kényszerített típuskonverzióval a tagját címző mutatóvá alakítunk, akkor az eredményül kapott mutató magára a tagra mutat.
A következőkben egyszerű példát mutatunk a struktúra deklarálására.
struct tcsomo { char tszo[20]; int szam; struct tcsomo *bal; struct tcsomo *jobb; };
Ez a deklaráció egy 20 elemű karakteres tömbből, egy egészből és két, hasonló struktúrát címző mutatóból áll. Ha egyszer ezt a deklarációt megadtuk, akkor a
struct tcsomo s, *sp;
deklaráció a megadott fajtájú s struktúrát, valamint a megadott fajtájú struktúrát címző sp mutatót deklarálja. Ezeket a deklarációkat felhasználva az
sp->szam
kifejezés az sp mutatóval címzett struktúra szam nevű tagjára hivatkozik. Hasonló módon az
s.bal
az s struktúra bal oldali részfájának mutatójára hivatkozik. Az
s.jobb->tszo[0]
az s struktúrában lévő jobb oldali részfa tszo karakteres tömbjének első karakterét címzi.Általában egy union tagját nem lehet ellenőrizni, kivéve, ha az unionhoz ugyanezen tagnak megfelelő értéket rendelünk. Ezért az unionok használatát egy speciális szabály egyszerűsíti: ha egy union többféle, de azonos kezdeti résszel (az elején azonosan deklarált tagokkal) rendelkező struktúrát tartalmaz és ha az union aktuális tartalma ezen struktúrák egyike, akkor megengedett, hogy bármely, az unionban lévő ilyen struktúra közös kezdeti részére hivatkozzunk. Például a következő egy szintaktikailag helyes programrészlet:
union { struct { int tipus; }n; struct { int tipus; int intcsomo; }ni; struct { int tipus; float floatcsomo; }nf; }u; ... u.nf.tipus = FLOAT; u.nf.floatcsomo = 3.14; ... if (u.n.tipus == FLOAT) ... sin(u.nf.floatcsomo) ...
A8.4. Felsorolások
A felsorolás olyan egyedi típus, amelynek értékei sorra felveszik a névvel megadott állandók (elemek) halmazából a megfelelő értéket. A felsorolásspecifikátor alakját a struktúrák és unionok specifikátorától kölcsönözték. A szintaktikai leírás:
felsorolás_specifikátor: enum azonosítóopc {felsorolás-lista} enum azonosító felsorolás-lista: felsorolt_érték felsorolás_lista, felsorolt_érték felsorolt_érték: azonosító azonosító = állandó_kifejezés
A felsoroláslistában lévő azonosítók int típusú állandóként vannak deklarálva, és a programban bárhol megjelenhetnek, ahol állandó kifejezésre van szükség. Ha nincs egyenlőségjel és az azt követő felsorolt érték, akkor a megfelelő állandók értéke nullával kezdődő, eggyel növekvő számsorozat lesz, amely hozzárendelődik a deklarációban szereplő azonosítókhoz. A hozzárendelés balról jobbra történik. Az = jelből és az utána következő felsorolt értékből álló lista esetén az azonosítóhoz a megadott érték rendelődik, és ha a listában soron következő azonosítóhoz nem tartozik = felsorolt érték rész, akkor az utolsó értékadástól folytatólagosan rendelődnek az értékek az azonosítókhoz.A felsorolásban deklarált neveknek az adott érvényességi tartományon belül különbözniük kell egymástól és a közönséges változók neveitől, de az értékek megegyezhetnek.
A felsorolásspecifikátorban az azonosítók feladata analóg a struktúraspecifikátorban szereplő struktúracímkével vagyis megnevezi az adott felsorolást. A címkével és listával ellátott vagy anélküli felsorolásspecifikátorra vonatkozó szabályok ugyanazok, mint a struktúra- vagy unionspecifikátorokra vonatkozóak, kivéve, hogy nemteljes felsorolás típus nem létezik. A felsoroláslista nélküli felsorolásspecifikátor címkéjének egy, az érvényességi tartományon belüli, listával ellátott specifikátorra kell hivatkozni.
A8.5. Deklarátorok
A deklarátorok szintaktikája:
deklarátor: mutatóopc direkt_deklarátor direkt_deklarátor azonosító (deklarátor) direkt_deklarátor [állandó_kifejezésopc] direkt_deklarátor (paraméter_típus_lista) direkt deklarátor (azonosító listaopc) mutató: típus_minősítő_listaopc típus_minősítő_listaopc mutató típus_minősítő_lista: *típus_minősítő *típus_minősítő_lista típus_minősítő
A deklarátorok szerkezete hasonlít az indirekció, függvény és tömbkifejezések szerkezetéhez; a csoportosítás ugyanaz.
A8.6. A deklarátorok jelentése
A deklarátorok listája a típus- és tárolásiosztály-specifikátorok sorozata után jelenik meg. Minden egyes deklarátor egy egyedi fő azonosítót deklarál, ez az első alternatíva a direkt deklarátor szintaktikai leírásában. Erre az azonosítóra kövzetlenül alkalmazzuk a tárolási-osztály-specifikátorokat, de a típus a deklarátor alakjától függ. Egy deklarátor azt jelenti, hogy amikor az azonosítója megjelenik a deklarátorral azonos alakú kifejezésben, akkor az a megadott típusú objektumot eredményezi.Egyelőre foglalkozzunk csak a deklarációspecifikátor (A8.2.) típusleíró részével: egy adott deklarátor esetén a deklaráció T D alakú, ahol T a típus és D a deklarátor. A típus a különböző alakú deklarátorokban hozzárendelődik az azonosítóhoz, így ezt a jelölésrendszert használjuk a deklarátorok leírására.
Egy T D alakú deklarációban, ahol D sima azonosító, az azonosító típusa T lesz.
Egy T D alakú deklarációban, ahol D
(D1)
alakú, a D1-ben szereplő azonosító típusa ugyanaz, mint D-ben. A zárójelzés nem változtatja meg a típust, csak az összetett deklarátorok kötésére lehet hatással.
A8.6.1. Mutatódeklarátorok
A T D deklarációban, ahol D
* típusminősítő_listaopc D1
alakú és az azonosító típusa a T D1 deklarációban „típusmódosított T”, a D azonosítójának típusa „típus_módosító típus_minősítő_lista mutató T típushoz” lesz. A minősítőt követő * operátor a mutatóra magára és nem a mutatóval címzett objektumra vonatkozik. Például nézzük a következő deklarációt:
int *ap[];
Itt ap[] játssza a D1 szerepét. Az int ap[] deklaráció az ap-hoz „egész elemek tömbje” típust rendel, a típusminősítő lista üres, és a típusmódosító „tömbje a …-nek“. Így az aktuális deklaráció ap-hez „int típusú adatokat címző mutatók tömbje” típust rendel. Egy másik példa a deklarációra:
int i, *pi, *const cpi = &i; const int ci = 3, *pci;
Ez az első részében egy i egészt és a pi egészhez tartozó mutatót deklarál. A cpi állandó mutató értéke nem változhat, mindig ugyanarra a helyre fog mutatni és értéke nem módosítható (bár kezdeti érték hozzárendelhető, csakúgy, mint itt). A pci típusa „const int-et címző mutató”, és a pci-t magát meg lehet változtatni, hogy más címre mutasson, de az értéket, amelyre mutat a pci mutatón keresztüli értékadással nem lehet módosítani.
A8.6.2. Tömbdeklarátorok
A T D deklarációban, ahol D
D1 [állandó_kifejezésopc]
alakú és az azonosító típusa a T D1 deklarációban „típusmódosított T”, a D azonosítójának típusa „típusmódosított tömbje a T-nek” lesz. Ha az állandó kifejezés jelen van, annak egész típusúnak és nullánál nagyobb értékűnek kell lennie. Ha az állandó kifejezéssel specifikált tömbhatár hiányzik, akkor a tömb nemteljes típusú.Egy tömb előállítható aritmetikai típusból, mutatóból, struktúrából vagy unionból, vagy más tömbből (ez többdimenziós tömböt eredményez). Bármilyen típusú elemekből is állítottuk elő a tömböt, annak teljes típusúnak kell lenni, nem szabad, hogy nemteljes típusú tömbből vagy struktúrából álljon. Ezért egy többdimenziós tömbnek csak az első dimenziója hiányozhat. A nemteljes típusú tömb objektumának típusa egy másik, az objektumra vonatkozó teljes deklarációval (A10.2.) vagy kezdetiérték-adással (A8.7.) tehető teljessé. Például a
float fa[17], *afp[17];
deklaráció float típusú számokból álló tömböt és float típusú számokat címző mutatókból álló tömböt deklarál. Hasonlóan a
static int x3d[3][5][7];
deklaráció egy statikus, háromdimenziós egészekből álló tömböt deklarál, amelynek mérete 3*5*7 elem. Részleteiben nézve x3d valójában háromelemű tömb, amelynek minden eleme öt tömb tömbje, és ez utóbbi tömbök hét egész elem tömbjét alkotják. Az x3d, x3d[i], x3d[i][j], x3d[i][j][k] kifejezések bármelyike megjelenhet más kifejezésben, és ezek közül az első három kifejezés tömb típusú, a negyedik pedig int típusú. Pontosabban nézve az x3d[i][j] hét egész számból álló tömb, az x3d[i] pedig öt, egyenként hét egész számból álló tömb tömbje.Az E1[E2] formában definiált tömbindexelési művelet azonos a *(E1+E2) művelettel, ezért az aszimmetrikus megjelenés ellenére az indexelés kommutatív művelet. Mivel a konverziós szabályokat alkalmazni kell a + műveletre és a tömbökre (A6.6., A7.1., A7.7.), ha E1 tömb és E2 egész típusú, akkor E1[E2] az E1 tömb E2-dik elemére hivatkozik.
A példánkban x3d[i][j][k] egyenértékű a *(x3d[i][j]+k)-vel. Az x3d[i][j] első részkifejezés az A7.1. pontban leírtak szerint „egészekből álló tömb mutatója” típusúvá alakítódik és az A7.7. szerint az összeadás egy egész méretének megfelelő többszörözést von maga után. Az eddigiek a tömb soronkénti tárolásának szabályából következnek. A deklarációban az első index segít a tömb által igényelt teljes tárolóterület meghatározásában, de nincs további szerepe az index kiszámításában.
A8.6.3. Függvénydeklarátorok
Az új stílusú függvénydeklaráció is felírható T D alakban, ahol D
D1 (paraméter_típus_lista)
alakú, és a T D1 deklarációban levő azonosító „típusmódosított T” típusú, a D-ben szereplő azonosító pedig „típusmódosított függvény paraméter_típus_lista argumentumokkal és T típusú visszatérési értékkel” típusú. A paraméterek szintaxisa:
paraméter_típus_lista: paraméterlista paraméterlista , ... paraméter_lista: paraméter_deklaráció paraméterlista, paraméter_deklaráció paraméter_deklaráció: deklaráció_specifikátorok deklarátorok deklaráció_specifikátorok absztrakt_deklarátoropc
Az új stílusú deklarációban a paraméterlista meghatározza a paraméterek típusát. Speciális esetben az új stílusú függvény deklarátora nem tartalmaz paramétereket és a paraméterlistában mindössze a void kulcsszó áll. Ha a paraméterlista a „ ,…” résszel végződik, akkor a függvény a paraméterlistában explicit módon megadottakon kívül további argumentumokhoz is hozzáférhet (l. az A7.3.2. pontot).A függvény tömb vagy függvény típusú paramétereinek típusa mutató típusra változik az A10.1. pontban leírásra kerülő paraméterkonverziós szabályoknak megfelelően. Egy paraméter deklarációjában csak a register tárolásiosztály-specifikátor használható, és ez a specifikátor is törlődik, kivéve ha a függvénydeklarátor megelőzi a függvénydefiníciót. Hasonló módon, ha a paraméterdeklarációban a deklarátorok azonosítót tartalmaznak, valamint a függvénydeklarátor nem előzi meg a függvénydefiníciót, akkor az azonosítók kilépnek a pillanatnyi érvényességi tartományból. Az azonosítót nem tartalmazó absztrakt deklarátorokat az A8.8. pontban tárgyaljuk.
A régi stílusú függvénydeklaráció T D alakú felírásában D
D1 (azonosító listaopc)
formájú, és a T D1 deklarációban szereplő azonosító típusa „típusmódosított T”, amíg a D-ben szereplő azonosító típusa „típusmódosított függvény nem specifikált argumentumokkal, T típusú visszatérési értékkel” típus lesz. A paraméterek (ha jelen vannak) alakja:
azonosító_lista: azonosító azonosító_lista, azonosító
A régi stílusú deklarátorokban az azonosítólistának hiányozni kell, kivéve, ha a deklarátort a függvénydefiníció előtt használjuk (l. A10.1.). Így a deklaráció alapján a paraméterek típusáról nincs információnk.
Példák függvénydeklarációra:
int f(), *fpi(), (*pfi)();
Ez egész értékkel visszatérő f függvényt, egészt címző mutatóval visszatérő fpi függvényt, valamint egész értékkel visszatérő függvényhez tartozó pfi mutatót deklarál. Egyik deklarációnál sem specifikáltuk a paraméter típusát, így ezek régi stílusú deklarációk.A következőkben új stílusú deklarációt mutatunk be:
int strcpy(char *cel, const char *forras), rand(void);
Itt az strcpy egész értékkel visszatérő függvény, amelynek két argumentuma van és az első karaktert címző mutató, a második pedig állandó karaktereket címző mutató. A paraméterek neve egyben utal a szerepükre is. A másodiknak deklarált rand függvénynek nincs argumentuma és int értékkel tér vissza.
A paraméterlista végén elhelyezhető „ ,…” kiegészítéssel jelzett változó hosszúságú paraméterlistájú függvények bevezetése szintén új. Ezt az <stdarg.h> standard headerben lévő makrókkal együtt megvalósított formális kezelési mechanizmust a könyv első kiadása hivatalosan tiltotta, de nem hivatalosan szemet hunyt felette.
Ezeket az új megoldásokat a szabvány a C++ nyelvből vette át.
A8.7. Kezdetiérték-adás
Egy objektum deklarálása során a kezdetiérték-deklarátorral adhatunk kezdeti értéket a deklarált azonosítónak. A kezdeti értéket = jelnek kell megelőzni, és az egy kifejezés vagy kapcsos zárójelek között elhelyezett kezdetiérték-lista lehet. A lista a megfelelő formátum kialakítása érdekében végződhet vesszővel. A szintaktikai leírás:
kezdeti_érték: értékadó_kifejezés { kezdeti_érték-lista } { kezdeti_érték-lista , } kezdetiérték-lista: kezdeti érték kezdetiérték-lista kezdeti érték
A statikus objektumokhoz vagy tömbökhöz tartozó kezdeti értékekben szereplő kifejezéseknek állandó kifejezéseknek kell lennie (ahogy ezt az A7.19. pontban leírtuk). Az auto vagy register tárolási osztályú objektumok vagy tömbök kezdetiérték-kifejezésének ugyancsak állandó kifejezésnek kell lennie, ha a kezdeti értékek kapcsos zárójelben lévő listában helyezkednek el. Viszont ha egy automatikus tárolási osztályú objektum kezdeti értéke egyetlen kifejezés, akkor nem szükséges, hogy az állandó kifejezés legyen (de az objektumhoz való hozzárendelés miatt a típusának megfelelőnek kell lennie).
Egy nem explicit módon inicializált statikus objektum úgy inicializálódik, mintha önmaga vagy tagja állandó nulla értéket kapott volna. A nem explicit módon inicializált automatikus tárolási osztályú objektumok kezdeti értéke definiálatlan.Egy mutató vagy egy aritmetikai típusú objektum kezdeti értéke egyetlen, esetleg kapcsos zárójelek között elhelyezett kifejezés. A kifejezés értéke hozzárendelődik az objektumhoz. Egy struktúra kezdeti értéke vagy egy azonos típusú kifejezés, vagy egy kapcsos zárójelek között elhelyezett, a tagok sorrendjében felsorolt kezdeti értékekből álló lista lehet. A név nélküli bitmező típusú struktúratagokat a fordítóprogram figyelmen kívül hagyja és azok nem kapnak kezdeti értéket. Ha a listában kevesebb kezdeti érték van, mint a tagok száma, akkor a további tagok 0 kezdeti értéket kapnak. A tagok számánál több kezdeti érték nem lehet a listában.
Egy tömb inicializálása kapcsos zárójelben elhelyezett, az egyes elemekhez rendelt kezdeti értékek listájával történhet. Ha a tömb mérete ismeretlen, akkor a kezdeti értékek leszámolásával határozza meg a fordítóprogram a tömb méretét, és a típus így válik teljessé. Ha a tömb rögzített méretű, akkor a listában megadott kezdeti értékek száma nem haladhatja meg a tömb elemeinek számát. Ha a kezdeti értékek száma kisebb, mint a tömbelemek száma, akkor a további tömbelemek 0 kezdeti értéket kapnak.
Speciális esetként egy karakteres tömb karaktersorozattal inicializálható. Ilyenkor a karaktersorozat egymást követő karaktereit rendeli a fordítóprogram a tömb soron következő eleméhez. Hasonló módon egy széles karaktersorozat-állandóval (A2.6.) inicializálható a wchar_t típusú karakteres tömb. Ha a tömb méretét nem ismerjük, akkor a karaktersorozatban lévő és hozzá rendelődő karakterek száma (beleértve a karaktersorozatot lezáró null-karaktert is) határozza meg a tömb méretét. Ha a tömb mérete rögzített, akkor a karaktersorozatban lévő karakterek száma (nem számítva a lezáró null-karaktert) nem haladhatja meg a tömb deklarált méretét.
Egy union egy azonos típusú egyedi kifejezéssel vagy egy kapcsos zárójelbe tett kezdeti értékkel inicializálható, de az inicializálás mindig csak az union első tagjára alkalmazható.
Egy aggregátum olyan összetett objektum, amely struktúra vagy tömb jellegű. Ha egy aggregátum további aggregátum típusú tagokat tartalmaz, akkor az inicializálási szabályok rekurzívan alkalmazhatók. Az inicializálásból a kapcsos zárójelek elhagyhatók a következő szabályok alapján: ha egy aggregátum tagjához, amely maga is aggregátum, tartozó kezdeti értékek bal oldali kapcsos zárójellel kezdődnek, akkor a következő, kezdeti értékek vesszővel elválasztott sorozatából álló lista a részaggregátumok tagjait inicializálja. Ha a kezdeti értékek száma nagyobb a tagok számánál, akkor a fordítóprogram hibát jelez. Amennyiben a részaggregátumokhoz tartozó kezdetiérték-lista nem bal oldali kapcsos zárójellel kezdődik, akkor a fordítóprogram a listából csak a részaggregátum tagjai számának megfelelő elemet vesz figyelembe és a listában fennmaradó elemek azon aggregátum további tagjait fogják inicializálni, amelynek a részaggregátum a része volt.Az elmondottakra nézzünk néhány példát! Az
int x[] = { 1, 3, 5 };
deklarálja az x egydimenziós tömböt és egyben inicializálja is azt. Mivel a tömb mérete nincs megadva és három elem kap kezdeti értéket, így a tömb háromelemű lesz. A
float y[4][3] = { { 1, 3, 5 }, { 2, 4, 6 }, { 3, 5, 7 }, };
egy teljesen kapcsos zárójelezésű inicializálás: az 1, 3 és 5 érték inicializálja az y[0] tömb első sorát, azaz az y[0][0], y[0][1] és y[0][2] elemeket. A következő két értéksor hasonló módon inicializálja az y[1] és y[2] tömböket. Mivel a kezdeti értékek a szükségesnél hamarabb fogynak el (a kezdeti értékek száma kisebb, mint a tömbelemek száma), ezért az y[3] tömb elemei 0 kezdeti értéket kapnak. Pontosan ugyanez a hatás érhető el a
float y[4][3] = { 1, 3, 5, 2, 4, 3, 5, 7};
inicializálással. Itt az y kezdeti értékeit tartalmazó lista kapcsos zárójellel kezdődik, de az y[0] tömbhöz tartozó lista nem. Ezért a listából három elem kerül felhasználásra. A továbbiakban a következő három elem az y[1] tömbhöz, az utolsó három elem pedig az y[2] tömbhöz rendelődik hozzá, y további elemei 0 kezdeti értéket kapnak. A
float y[4][3] = { {1}, {2}, {3}, {4} };
deklaráció y első oszlopát (y-t kétdimenziós tömbként értelmezve) inicializálja és a fennmaradó elemekhez 0 kezdeti értéket rendel. Végül nézzük a következő példát: a
char msg[] = „Szintaktikai hiba a sorban %s\n”;
az msg karakteres tömb elemeit inicializálja egy karaktersorozattal. A tömb méretét a karaktersorozat hossza határozza meg, beleszámítva a karaktersorozatot lezáró null-karaktert is.
A8.8. Típusnevek
Néhány összefüggésben (kényszerített típuskonverzióval specifikált explicit típusmódosításban, függvénydeklarátorokban a paraméterek típusának deklarálásakor, a sizeof argumentumakénti alkalmazásakor) szükség lehet az adattípus nevének megadására. Ez a típusnév felhasználásával érhető el. A típusnév szintaktikailag egy adott típusú objektum olyan deklarációja, amelyből hiányzik az objektum neve. A szintaktikai leírás:
típusnév: specifikátor_minősítő_lista absztrakt_deklarátoropc absztrakt_deklarátor: mutató mutatóopc direkt absztrakt deklarátor direkt_absztrakt__deklarátor: (absztrakt_deklarátor) direkt_absztrakt_deklarátoropc [állandó kifejezésopc] direkt_absztrakt_deklarátoropc (paraméter_ típus_listaopc)
Az absztrakt deklarátorban egyértelműen azonosítható az a hely, ahol az azonosító megjelenhetne, ha a szerkezet egy deklaráción belüli deklarátor lenne. Az így megnevezett típus ilyenkor ugyanaz lesz, mint a hipotetikus azonosító típusa. Néhány példa:
int int * int *[3] int (*) [] int *() int (* []) (void)
Ezek a szerkezetek sorban egymás után „egész”, „egészhez tartozó mutató”, „három, egészekhez tartozó mutatóból álló tömb”, „nem meghatározott számú egész elemből álló tömbhöz tartozó mutató”, „nem meghatározott paraméterlistájú, egészhez tartozó mutatóval visszatérő függvény” és „paraméterlista nélküli, egész értékkel visszatérő függvényekhez tartozó mutatókból álló, nem meghatározott méretű tömb” típusokat neveznek meg.
A8.9. A typedef
Azok a deklarációk, amelyekben a tárolásiosztály-specifikátor a typedef, nem objektumot deklarálnak, hanem egy olyan azonosítót definiálnak, ami a későbbiekben típusnévként használható. Az így definiált azonosítókat typedef neveknek nevezzük. A szintaktikai leírás:
typedef_név azonosító
A typedef deklaráció az egyes, deklarátorokban szereplő nevekhez a szokásos módon (l. A8.6. pontot) hozzárendel egy típust. Ezért az ilyen typedef nevek szintaktikailag egyenértékűek a típusjelző kulcsszóval a megfelelő típushoz rendelt típusnévvel. Például a
typedef long Blokkszam, *Blokkptr; typedef struct { double r, theta; } Complex;
deklarációk után a
Blokkszam b; extern Blokkptr bp; Complex z, *zp;
konstrukciók teljesen legális deklarációk lesznek. A b típusa long, így a bp egy „long típushoz tartozó mutató”; a z egy meghatározott struktúra, zp pedig ezt a struktúrát kijelölő mutató.A typedef nem vezet be új típust, csak a más módon megadott típusok szinonimáit állítja elő. Például az előzőekben deklarált b ugyanolyan típusú, mint bármilyen más long típusú objektum.
A typedef nevek deklarálhatók a belső érvényességi tartományban, de nem üres típusspecifikátor-halmazt kell megadnunk. Például az
extern Blokkszam;
nem deklarálja a Blokkszam-ot, de az
extern int Blokkszam;
már igen.
A8.10. Típusekvivalenciák
Két típusspecifikátor-lista egyenértékű, ha mindegyik a típusspecifikátorok azonos halmazát tartalmazza, figyelembe véve, hogy ugyanazt a specifikátort más módon is megadhatjuk (pl. a long ugyanazt jelenti, mint a long int). A különböző címkéjű struktúrák, unionok és felsorolások különbözőek, és egy címke nélküli struktúra, union vagy felsorolás egy egyedi típust specifikál.Két típus azonos, ha az absztrakt deklarátoruk (A8.8.) az esetleges typedef típusok kifejtése és bármilyen függvényparaméter azonosító törlése után ekvivalens típus-specifikátorlistákat eredményez. A tömbméretek és a függvényparaméter-típusok a típusekvivalencia meghatározásánál lényegesek.
A9. Utasítások
Az utasítások a leírásuk sorrendjében hajtódnak végre, kivéve azt, ahol külön jelezzük. Az utasítások végrehajtása a hatásukban nyilvánul meg és nem rendelkeznek értékkel. Az utasítások számos csoportba sorolhatók, és általános szintaktikai leírásuk:
utasítás: címkézett_ utasítás kifejezésutasítás összetett_utasítás kiválasztó_utasítás iterációs_utasítás vezérlésátadó_ utasítás
A9.1. Címkézett utasítások
Az utasításokhoz előtagként megadott címke tartozhat. A címkézett utasítások szintaktikája:
címkézett_utasítás azonosító : utasítás case állandó_kifejezés : utasítás default : utasítás
A címke egy azonosítóként deklarált azonosítóból áll. Egy azonosító címkét csak a goto utasítás célpontjaként használhatunk. Az azonosító címke érvényességi tartománya az aktuális függvény (az a függvény, amelyben előfordul). Mivel a címkékhez nem tartozik megnevezett tárterület, ezért nem kerülhetnek kapcsolatba más azonosítókkal és nem deklarálhatók újra (l. az A11.1. pontot is).A case és default címkéi a switch utasítással használhatók. A case utáni állandó kifejezésnek egész típusúnak kell lennie.
A címkék önmagukban nem módosítják az utasítások végrehajtásának sorrendjét.
A9.2. Kifejezésutasítások
Az utasítások többsége kifejezésutasítás, amelynek általános alakja:
kifejezésutasítás: kifejezésopc;
Funkcióját tekintve a legtöbb kifejezésutasítás értékadás vagy függvényhívás. A kifejezésutasításban lévő kifejezés összes mellékhatása lezajlik a következő utasítás végrehajtásának kezdete előtt. Ha kifejezésutasításból hiányzik a kifejezés, akkor ezt a konstrukciót null-utasításnak (üres utasításnak) nevezzük, és gyakran használjuk az iterációs utasítások üres ciklusmagjának helyettesítésére vagy címke helyének kijelölésére.
A9.3. Összetett utasítás
Vannak olyan programkörnyezetek, ahol a fordítóprogram csak egyetlen utasítást fogad el. Az összetett utasítás (vagy más néven blokk) ennek a korlátozásnak a megszüntetését és több utasítás egyetlen utasításkénti kezelését teszi lehetővé. Például egy függvénydefiníció magja egyetlen összetett utasítás. Az összetett utasítás szintaktikai leírása:
összetett_utasítás: { deklarációs_listaopc utasítás_listaopc } deklarációs_lista: deklaráció deklarációs_lista deklaráció utasítás_lista: utasítás utasítás_lista utasítás
Ha a deklarációs listában található valamelyik azonosító a blokkon kívüli érvényességi körrel rendelkezik (a blokkon kívül már deklarálva van), akkor a külső deklaráció a blokkba való belépéskor fel lesz függesztve (l. az A11.1. pontot) és csak annak befejeztével nyeri vissza a hatályát. Egy blokkban egy azonosítót csak egyszer lehet deklarálni. Ezeket a szabályokat kell alkalmazni az összes, azonos névtérben lévő azonosítóra (A11.1.); a különböző névtérben lévő azonosítók egymástól különbözőként kezelhetők.Az automatikus tárolási osztályú objektumok inicializálása a blokkba való minden egyes belépéskor, a blokk tetején megtörténik, és ugyanakkor sorban feldolgozza a program a deklarátorokat is. Ha kívülről egy vezérlésátadó utasítással a blokk belsejébe ugrunk, akkor ezek az inicializálások elmaradnak. A static tárolási osztályú objektumok csak egyszer, a program végrehajtásának kezdetén inicializálódnak.
A9.4. Kiválasztó utasítások
A kiválasztó utasítások minden esetben a lehetséges végrehajtási sorrendek egyikét választják ki. Általános szintaktikai leírásuk:
kiválasztó_utasítás: if ( kifejezés ) utasítás if ( kifejezés ) utasítás else utasítás switch ( kifejezés ) utasítás
Az if utasítás mindkét formájában a kifejezés (amelynek aritmetikai vagy mutató típusú kifejezésnek kell lennie) kiértékelődik (beleértve az összes mellékhatást is) és ha az eredmény nem egyenlő nullával, akkor az első alutasítás hajtódik végre. Az if utasítás második alakja esetén a második alutasítás akkor hajtódik végre, ha a kifejezés nulla. Sokszor nem egyértelmű, hogy az else ág melyik if utasításhoz tartozik. Ezt a kétértelműséget a C nyelv azzal oldja fel, hogy egy else mindig az azonos blokkon belüli utolsó else nélküli if utasításhoz kötődik.A switch utasítás hatására a vezérlés a kifejezés értékétől (amelynek egész típusúnak kell lennie) függően több utasítás egyikére adódik át. A switch utasítással vezérelt alutasítások tipikusan összetett utasítások. Az alutasításon belül bármely utasítást címkézhetünk egy vagy több case címkével (A9.1.). A végrehajtás során a vezérlő kifejezésre végbemegy az egész-előléptetés (l. A6.1.), és a case részek állandói az előléptetett típusra konvertálódnak. Ugyanazon switch utasításon belül két case rész állandójának a konverzió után nem lehet azonos értéke. Egy switch utasításhoz legfeljebb egy default címke is tartozhat. A switch utasítások egymásba ágyazhatók; a case és default címkék mindig ahhoz a legbelső switch utasításhoz kapcsolódnak, amely tartalmazza azokat.
A switch utasítás végrehajtásakor a kifejezés az összes mellékhatást beleértve kiértékelődik és összehasonlításra kerül az egyes case részek állandóival. Ha az egyik case rész állandója megegyezik a kifejezés értékével, akkor a vezérlés átadódik a case címkét követő utasításra. Ha egyetlen case rész állandója sem egyezik a kifejezés értékével, és ha a switch utasítás tartalmaz default címkét, akkor a vezérlés a default címke utáni utasításra adódik át. Ha a switch utasításban nincs default rész és egyik case rész állandója sem egyezik meg a kifejezés értékével, akkor egyetlen alutasítás sem hajtódik végre.
A9.5. Iterációs utasítások
Az iterációs utasítások egy ciklust határoznak meg. Általános szintaktikai leírásuk:
iterációs_utasítás: while ( kifejezés ) utasítás do utasítás while ( kifejezés ) ; for (kifejezésopc ; kifejezésopc ; kifejezésopc ) utasítás
A while és a do utasításban a program az alutasításokat ismételten végrehajtja mindaddig, amíg a kifejezés értéke nullától különböző marad. A kifejezésnek aritmetikai vagy mutató típusúnak kell lennie. A while utasítás esetén az ellenőrzés, beleértve a kifejezés kiértékelésekor adódó mellékhatásokat is, az utasítás végrehajtása előtt megy végbe, amíg a do utasítás esetén az ellenőrzés csak az egyes iterációk után történik meg.A for utasításban az első kifejezés csak egyszer értékelődik ki és ez adja a ciklus kezdeti értékét. Az első kifejezés típusára vonatkozóan semmiféle megkötés nincs. A második kifejezésnek aritmetikai vagy mutató típusúnak kell lenni. Ez a kifejezés az egyes iterációk előtt értékelődik ki, és ha az értéke nullává válik, akkor a for ciklus befejeződik. A harmadik kifejezés szintén minden iteráció elején kiértékelődik és az így kapott érték adja a ciklus újbóli kezdő értékét (a ciklusváltozó aktuális értékét). Ennek típusára sincs semmiféle megkötés. Az egyes kifejezések kiértékelésekor adódó mellékhatások a kiértékelés után azonnal teljesen lecsengenek. Ha a ciklus alutasításai között nem szerepel a continue, akkor a
for ( 1.kifejezés ; 2.kifejezés ; 3.kifejezés ) utasítás
szerkezetű ciklus egyenértékű az
1.kifejezés ; while ( 2.kifejezés ) { utasítás 3.kifejezés; }
szerkezetű ciklussal.A három kifejezés bármelyike elhagyható. A második kifejezés hiánya esetén a for ellenőrző része úgy működik, mintha az ellenőrzés egy nem nulla értékű állandóval történne.
A9.6. Vezérlésátadó utasítások
A vezérlésátadó utasítások a vezérlés feltétel nélküli átadására alkalmasak. A szintaktikai leírásuk:
Vezérlésátadó_utasítás: goto azonosító ; continue ; break ; return kifejezésopc ;
A goto utasításban szereplő azonosítónak az aktuális függvényben (amelyben a goto utasítást kiadtuk) lévő címkének kell lennie. Az utasítás hatására a vezérlés átadódik a címkézett utasításra.A continue utasítás a ciklusszervező utasításokban jelenhet meg. A continue utasítás hatására a vezérlés átadódik a legbelső ciklus folytatását vezérlő részre (vagyis a ciklusmagot alkotó utasítás további végrehajtása lezárul és újra elindul a ciklus tesztelése). Pontosabban megfogalmazva a continue utasítás hatása az egyes ciklusszervező utasításokban ugyanaz, mint a goto contin utasításé a következő ciklusokban:
while (...){ do{ for (...) { ... ... ... contin: ; contin: ; contin: ; } } while (...); }
A break utasítás csak ciklusszervező vagy switch utasításokban jelenhet meg. A break hatására befejeződik a ciklusmagot alkotó utasítás végrehajtása, a vezérlés a lezáró utasítást követő utasításra adódik.Egy függvény a hívó eljárásba a return utasítás hatására tér vissza. Amikor a return utasítás után kifejezés áll, annak értékét a függvény visszaadja a hívó eljárásnak. A kifejezés típusa az értékadásnak megfelelően konvertálódik a függvény által meghatározott visszatérési típusra.
Ha a függvény végén nincs return utasítás (a vezérlés „kifolyik” a függvényből), akkor az egyenértékű egy olyan visszatéréssel, mintha nem lenne a return utasítás után kifejezés. Ezekben az esetekben a visszatérési érték nincs definiálva.
A10. Külső deklarációk
A C fordítóprogram számára átadott szöveges bemeneti egységet fordítási egységnek nevezzük. Egy fordítási egység külső deklarációk sorozatából áll, amelyben deklarációk vagy függvénydefiníciók lehetnek. A szintaktikai leírás:
fordítási_ egység: külső_deklaráció fordítási_egység külső_deklaráció külső_deklaráció: függvénydefiníció deklaráció
A külső deklarációk érvényességi tartománya azon fordítási egység végéig tart, amelyben deklarálva voltak, csakúgy, mint ahogy a blokkon belüli deklarációk érvényességi tartománya a blokk végéig tart. A külső deklarációk szintaxisa ugyanaz, mint az összes többi deklarációé, kivéve, hogy függvényeket csak ezen a szinten lehet deklarálni (azaz csak itt adható meg a függvényt alkotó programkód).
A10.1. Függvénydefiníciók
A függvények definíciója a következő alakban adható meg:
függvénydefiníció: deklaráció_specifikátorokopc deklarátor deklarációs listaopc összetett utasítás
A deklarációspecifikátorok közül csak az extern vagy static tárolásiosztály-specifikátor a megengedett, és a közöttük lévő különbségre az A11.2. pontban térünk ki.Egy függvény visszatérési értéke aritmetikai típusú, struktúra, union, mutató vagy void típus lehet; de függvénnyel vagy tömbbel való visszatérés nem lehetséges. A függvénydeklarációban lévő deklarátornak explicit módon meg kell határozni, hogy a deklarált azonosító függvény típusú, vagyis az alábbi alakok egyikét tartalmaznia kell (l. az A8.6.3. pontot):
direkt_deklarátor ( paraméter_típus_lista ) direkt_deklarátor ( azonosító_listaopc )
ahol a direkt deklarátor egy azonosító vagy egy zárójelezett azonosító. Főként nem szabad a függvény típust a typedef-fel deklarálni.A fenti két forma közül az első az új stílusú függvénydefiníció, ami a függvény paramétereit, azok típusával együtt a paramétertípus-listában deklarálja. Ilyen esetben a deklarátor-listát követő függvénydeklarátornak hiányoznia kell. Eltekintve attól az esettől, amikor a paraméterlista csak a void típusjelzést tartalmazza (ami azt jelzi, hogy a függvénynek nincsenek paraméterei), a paramétertípus-lista egyes deklarátorainak azonosítót kell tartalmaznia. Ha a paraméterlista a „ ,… ” karakterekkel végződik, akkor a függvény több argumentummal hívható, mint a megadott paraméterek száma. Ilyen esetekben az <stdarg.h> standard headerben definiált és a B. Függelékben leírt, va_arg makró használatán alapuló eljárással lehet a többletargumentumokhoz hozzáférni. A változó hosszúságú paraméterlistájú függvényeknek kell hogy legyen legalább egy névvel hivatkozott paramétere.
A második alak a régi stílusú definíció: az azonosító lista megnevezi a paramétereket, amíg a deklarációs lista hozzájuk rendeli a típust. Ha a paraméterekre nincs megadva deklaráció, akkor azok típusát a fordítóprogram int-nek tekinti. A deklarációs listának csak a listában megnevezett paramétereket kell deklarálnia, a paraméterek inicializálása nem megengedett, és csak a register tárolásiosztály-specifikátor használható.
Mindkét stílusú függvénydefiníció esetén a paraméterek magától értetődően a függvény magját képező összetett utasítás kezdetén deklarálódnak, és így ugyanazt az azonosítót nem szabad ott újra deklarálni (de természetesen más azonosítókhoz hasonlóan azok szintén újra deklarálhatók a külső blokkban). Ha a paraméter T típusú tömbként lett deklarálva, akkor a deklaráció „T típushoz tartozó mutató” típusúra alakul át, hasonlóan, ha a paraméter „T típusú értékkel visszatérő függvény” típusúnak lett deklarálva, akkor a deklaráció „T típusú értékkel visszatérő függvényhez tartozó mutató” típusra
alakul át. A függvény hívása során az argumentumok típusa a szükséges módon átalakul és értéke értékadással átadódik a paramétereknek.
A következőkben egy teljes példát mutatunk a függvények új stílusú definíciójára.
int max (int a, int b, int c) { int m; m = (a > b) ? a : b; return (m > c) ? m : c; }
Itt int a deklarációspecifikátor; max (int a, int b, int c) a függvény deklarátora és {…} a függvény programkódját tartalmazó blokk. A megfelelő régi stílusú deklaráció a következőképpen nézne ki:
int max (a, b, c) int a, b, c; { }
Itt most int max (a, b, c) a deklarátor és int a, b, c; a paraméterek deklarációs listája.
A10.2. Külső deklarációk
A külső (external) deklarációk objektumok, függvények és más azonosítók jellemzőit specifikálják. A külső megnevezés a függvényen kívüli elhelyezkedésre utal és nincs közvetlen kapcsolata az extern kulcsszóval. A külsőleg deklarált objektumok tárolásiosztály-meghatározása üresen hagyható, vagy extern, ill. static típusúnak adható meg.Adott fordítási egységen belül ugyanannak az azonosítónak számos külső deklarációja létezhet, ha azok típusa és csatolása megegyezik és ha az azonosítónak létezik legfeljebb egy definíciója.
Egy objektum vagy függvény kétféle deklarációval megadott típusa akkor egyezik, ha teljesülnek az A8.10. pontban leírt szabályok. Ehhez kiegészítésül még egy szabály tartozik: ha a deklarációk azért különböznek, mert az egyik típus egy nemteljes struktúra, union vagy felsorolás (A8.3.) és a másik az ugyanolyan címkéjű, megfelelő, teljessé tett típus, akkor a típusok megegyeznek. Mi több, ha az egyik egy nemteljes tömb típus, a másik pedig egy teljes tömb típus (A8.6.2.), akkor a típusok – ha máskülönben azonosak – szintén megegyeznek. Végül, ha az egyik típus egy régi stílusú függvényt határoz meg, a másik pedig – egy különben azonos – új stílusú függvényt, a megfelelő paraméterdeklarációkkal, akkor a két típus megegyezik.
Ha egy függvény vagy objektum első külső deklarációja tartalmazza a static specifikátort, akkor az azonosítónak belső csatolása van, máskülönben pedig külső csatolású. A csatolás fogalmát és jellegzetességeit az A11.2. pontban tárgyaljuk.
Egy objektumra vonatkozó külső deklaráció egyben definíció is, ha a deklaráció tartalmaz kezdetiérték-adást. Ha egy külső objektum deklarációja nem tartalmaz kezdetiérték-adást és extern specifikátort, akkor az egy ún. próbadefiníció. Ha egy objektum definíciója megjelenik a fordítási egységben, akkor az összes próbadefiníciót a fordítóprogram redundáns deklarációként kezeli. Ha az objektum nincs definiálva a fordítási egységben, akkor az összes rá vonatkozó próbadefiníció egyetlen, nulla kezdeti értékű definícióvá válik.
Minden objektumnak egy és csak egy definíciója kell hogy legyen. A belső csatolású objektumok esetén ez a szabály minden egyes fordítási egységre külön-külön érvényes, mivel a belső csatolású objektumok egy fordítási egységre nézve egyediek. Külső csatolású objektumok esetén ez a szabály a teljes programra vonatkozik.
A11. Érvényességi tartomány és csatolás
Egy C nyelvű programot nem szükséges egyszerre lefordítani: a forrásprogram több, fordítási egységeket tartalmazó állományban tartható és az előre lefordított eljárások a könyvtárakból tölthetők be. A programot alkotó függvények közötti kommunikáció a függvényhívásokon és a külső adatok felhasználásán keresztül megy végbe.Ezért kétféle érvényességi tartományt kell megkülönböztetnünk: egy azonosító lexikális érvényességi tartományát, ami a program szövegének az a része, ahol az azonosító ismert és tulajdonságai meghatározottak, valamint a külső csatolású objektumokkal és függvényekkel kapcsolatos érvényességi tartományt, amely meghatározza a különálló fordítási egységekben lévő azonosítók közötti kapcsolatot.
A11.1. Lexikális érvényességi tartomány
Az azonosítók számos névtér valamelyikébe sorolhatók és ezek a névterek egymástól függetlenek, az oda tartozó azonosítók nincsenek hatással a másik névtér azonosítóira. Ugyanaz az azonosító különböző célra, azonos érvényességi tartománnyal használható, ha különböző névtérbe tartozik. A C nyelvben többféle névtér létezik: objektumok, függvények, typedef nevek, ill. felsorolt állandók; címkék; struktúrák, unionok és felsorolások címkéi; önállóan a struktúrák és unionok tagjai.
Egy objektum vagy függvény azonosítójának lexikális érvényességi tartománya egy külső deklarációban a deklarátor befejezésével (lezárásával) kezdődik és a deklarációt tartalmazó fordítási egység végéig tart. Egy függvénydefinícióban szereplő paraméter érvényességi tartománya a függvénydefiniálás blokkjának kezdetén indul és végig a függvényben érvényes; egy függvénydeklarációban szereplő paraméter érvényességi tartománya viszont csak a deklarátor végéig terjed. Egy blokk fejrészében deklarált azonosító érvényességi tartománya a deklarátor végétől (lezárásától) a blokk végéig terjed. Egy címke érvényességi tartománya a teljes függvény, amelyben megjelenik. Egy struktúra, union vagy felsorolás címkéjének vagy egy felsorolt állandónak az érvényességi tartománya a típusspecifikátorban való megjelenésével kezdődik és a fordítási egység végéig (külső szintű deklaráció esetén) vagy a blokk végéig (függvényen belüli deklaráció esetén) tart.Ha egy azonosítót explicit módon deklarálunk egy blokk fejrészében, beleértve a függvényt alkotó blokkot is, akkor az azonosító bármilyen blokkon kívüli deklarációja a blokk végéig fel lesz függesztve.
A11.2. Csatolás
Egy fordítási egységen belül ugyanazon belső csatolású objektum vagy függvény azonosítójának minden deklarációja ugyanazt a dolgot jelenti, és az objektum vagy függvény a fordítási egységre vonatkoztatva egyedi. Ugyanazon objektum vagy függvény külső csatolású azonosítójára vonatkozó összes deklaráció szintén ugyanazt a dolgot jelenti, és az objektum vagy függvény a teljes programban, bármely eljárás számára használható.Ahogyan ezt már az A10.2. pontban elmondtuk, egy azonosító első külső deklarációja static tárolásiosztály-specifikátor alkalmazása esetén belső csatolást, minden más specifikátor esetén pedig külső csatolást eredményez. Ha egy azonosító blokkon belüli deklarációja nem tartalmazza az extern tárolásiosztály-specifikátort, akkor az azonosítónak nincs csatolása és a függvényre vonatkoztatva egyedi. Ha a deklaráció tartalmazza az extern tárolásiosztály-specifikátort és egy, az azonosítóra vonatkozó külső deklaráció aktív a környező blokkra vett érvényességi tartományban, akkor az azonosító ugyanolyan csatolású, mint a külső deklaráció, és ugyanazt az objektumot vagy függvényt jelenti. Ha az érvényességi tartományban nincs külső deklaráció, akkor a csatolás külső.
A12. Az előfeldolgozó rendszer
Az előfeldolgozó rendszer feladata a makrók kifejtése és behelyettesítése, a feltételes fordítás vezérlése, valamint a megnevezett állományok programba építése. A forrásprogram # jellel kezdődő (előtte üreshely-karakterek használhatók) sorai az előfeldolgozó rendszernek szóló információkat tartalmaznak. Ezeknek a soroknak a szintaxisa független a C nyelv többi részétől, a programban bárhol megjelenhetnek és hatásuk (az érvényességi tartománytól függetlenül) a fordítási egység végéig tart. A sorokra tördelés lényeges, mivel az előfeldolgozó rendszer minden sort egyedileg analizál (bár több sor is összekapcsolható az A12.2. pontban leírtak szerint). Az előfeldolgozó rendszer számára bármilyen nyelvi szintaktikai egység (token) egy önálló szintaktikai egységet alkot és egy karaktersorozat egy állománynevet ad meg (mint pl. az #include direktívában, l. az A12.4. pontot). A feldolgozás során minden karakter, ami nincs másképpen definiálva, szintén szintaktikai egységet képez. Az előfeldolgozó rendszernek szóló sorokban lévő, szóköztől és a vízszintes tabulátortól különböző üreshely-karakterek jelentése (hatása) nincs definiálva.Az előfeldolgozás több, egymástól elkülönülő, logikailag egymásra épülő fázisból áll, amelyek a konkrét gépi megvalósításban összevonhatók. A feldolgozás fázisai:
- Az előfeldolgozó rendszer az A12.1. pontban leírt trigráf karaktersorozatokat helyettesíti a megfelelő karakterekkel. Amennyiben az operációs rendszer megköveteli, akkor újsor-karakterek épülnek be a forrásállomány egyes sorai közé.
- Minden egyes backslash karakterből (\) és az azt követő újsor-karakterből álló kombináció törlődik, amivel az összetartozó sorok egy sorrá egyesülnek (A12.2.).
- A program felbontása üreshely-karakterekkel elválasztott szintaktikai egységekre (tokenekre); a magyarázó szövegek helyettesítése egyetlen szóközzel. Ekkor az előfeldolgozást vezérlő direktívák végrehajtódnak és megtörténik a makrók (A12.3. – A.12.10.) kifejtése.
- A karakteres és karaktersorozat-állandókban lévő escape-sorozatokat a rendszer helyettesíti a megfelelő karakterekkel (A2.5.2., A2.6.) és a szomszédos karaktersorozatállandók konkatenálódnak.
- Az így előkészített program fordítása, ill. más programokkal és könyvtárakkal való összeszerkesztése. Ez a szükséges programok és adatok kigyűjtése, valamint a külső függvényekre és objektumokra való hivatkozások definíciójukkal történő összekapcsolása alapján megy végbe.
A12.1. Trigráf karaktersorozatok
A C nyelvű forrásprogramok a hétbites ASCII karakterkészletet használják, de létezik az ISO 646-1983 Invariáns Kódrendszer bővített változata is. Azért, hogy a programok a redukált karakterkészlettel is ábrázolhatók legyenek, a következő trigráf karaktersorozatokat a megfelelő karakterrel kell helyettesíteni:
??= # ??( [ ??< { ??/ \ ??) ] ??> } ??' ^ ??! | ??- ~
A helyettesítés minden más feldolgozási lépést megelőzően történik, és más ilyen helyettesítés nem fordul elő.
A12.2. Sorok egyesítése
A sor végén elhelyezett backslash karakter (\) azt jelzi, hogy az előfeldolgozó rendszernek szánt információ a következő sorban folytatódik. Az előfeldolgozó rendszer a program szintaktikai egységekre bontása előtt a \ karakter és az azt követő újsor-karakter törlésével ezeket a sorokat egyesíti.
A12.3. Makrók definíciója és kifejtése
A programban előforduló
#define azonosító token_sorozat
alakú vezérlősor arra utasítja az előfeldolgozót, hogy az azonosító következő előfordulását helyettesítse a szintaktikai egységek (tokenek) megadott sorozatával. A szintaktikai egységeket megelőző és záró üreshely-karakterek törlődnek. Ugyanerre az azonosítóra vonatkozó második #define sor hibajelzést eredményez, kivéve, ha a másodiknak megadott szintaktikai egység sorozat megegyezik az elsővel, az összes üreshely-karakterből álló elválasztásokat is figyelembe véve.A
#define azonosító ( azonosító_lista ) token_sorozat
alakú sor (ahol az első azonosító és a kezdő kerek zárójel között nincs szóköz!) egy makrót definiálnak az azonosítólistában megadott paraméterekkel. Csakúgy, mint az első alak esetén, a szintaktikai egységek előtt és után lévő üreshely-karakterek törlődnek, és a makrót csak úgy lehet újradefiniálni, ha az új és régi definícióban a paraméterek száma és leírása, valamint a tokenek sorozata megegyezik.Az
#undef azonosító
alakú vezérlősor hatására az előfeldolgozó rendszer „elfelejti” az azonosító korábbi, #define sorral megadott definícióját. Egy ismeretlen azonosítóra kiadott #undef direktíva nem jelent hibát.Amikor a másodiknak megadott forma szerint definiálunk egy makrót, akkor annak hívása a makró azonosítójából, az azt követő kerek nyitó zárójelből, szintaktikai egységek vesszővel elválasztott sorozatából és egy kerek végzárójelből áll. (Természetesen üreshely-karakterek megengedettek.) A hívás argumentumait szintaktikai egységek vesszővel elválasztott sorozata alkotja, a szövegben lévő vesszőket aposztrófok vagy zárójelek között kell elhelyezni, hogy ne elválasztó jelként hassanak. Az előfeldolgozó rendszer információgyűjtési fázisában az argumentumokra még nincs makrókifejtés. Híváskor az argumentumok számának meg kell egyezni a definícióban lévő paraméterek számával. Az argumentumok szétválasztása után az előfeldolgozó rendszer eltávolítja a bevezető és záró üreshely-karaktereket. Mindezek után az előfeldolgozó az egyes argumentumokból így előállított token-sorozattal helyettesíti a megfelelő paraméterazonosító minden egyes nem aposztrófok közötti előfordulását a makró helyettesítő token-sorozatában. Hacsak a helyettesítősorozatban a paramétert nem # előzi meg, vagy ## van előtte és utána, akkor az argumentum tokeneket a makróhívás szempontjából újra megvizsgálja, és ha szükséges, akkor a helyettesítő szöveg beiktatása előtt megtörténik a makrókifejtés.
A helyettesítési folyamatot két speciális operátor befolyásolja. Az első esetben, ha a helyettesítő token-sorozatban egy paraméter előfordulását közvetlenül a # előzi meg, akkor a megfelelő paraméter elé és után az idézőjel-karakter kerül, és mind a #, mind a paraméterazonosító helyettesítődik az idézőjelek közötti argumentummal. Minden egyes idézőjel vagy az argumentumban lévő karakteres állandóban, ill. karaktersorozat-állandóban előforduló \ karakter elé egy \ karakter iktatódik.
A második esetben, ha valamelyik makró definíciója egy ## operátort tartalmaz, akkor a paraméter helyettesítése után az egyes ## jelek törlődnek (együtt annak két oldalán lévő üreshely-karakterekkel), aminek hatására a behelyettesített szöveg konkatenálódik a szomszédos tokennel és egy új tokent eredményez. Ha ezzel a módszerrel érvénytelen token jön létre, ill. ha az eredmény függ a ## operátorok feldolgozási sorrendjétől, akkor a hatás definiálatlan. A ## nem jelenhet meg egy helyettesítő token-sorozat kezdetén vagy végén.
Mindkét fajta makró feldolgozásakor a rendszer a helyettesített token-sorozatot ismételten átvizsgálja, hogy megtalálja az előfeldolgozó az esetleges további definiálatlan azonosítókat. Mindenesetre, ha egyszer egy azonosító egy adott kifejezésben helyettesítődött, akkor az nem helyettesítődik újra az újbóli átvizsgálás során, hanem változatlan marad.
Ha egy makrókifejtés végső értéke # jellel kezdődik, akkor azt az előfeldolgozó rendszer már nem tekinti direktívának.
Például ez a lehetőség kihasználható a „manifesztálódott” állandók létrehozásánál, mint
#define TABMERET 100 int tabla[TABMERET];
esetén. A
#define ABSDIFF(a, b) ((a) > (b) ? (a) - (b) : (b) - (a))
definíció egy makrót definiál, amely visszatér az argumentumai közötti különbség abszolút értékével. Eltérően egy ugyanezen feladatot ellátó függvénytől, itt az argumentumok és a visszatérési érték tetszőleges aritmetikai típusú vagy mutató lehet. További előnye a makrónak, hogy az argumentumok, amelyek mellékhatásokat okozhatnak, kétszer lesznek kiértékelve: egyszer az ellenőrzéshez és másodszor az érték előállításához. A
#define tempfile(dir) #dir "/%s"
definíció a tempfile(/usr/tmp) makróhívást eredményezi, amelyben az
"/usr/trap" "/%s"
egymást követő karaktersorozatok egyetlen karaktersorozattá olvadnak össze (konkatenálódnak). A
#define cat(x, y) x ## y
definíció után a cat(var, 123) makróhívásban az argumentum varl23 lesz. Viszont a cat(cat (1,2), 3) makróhívás definiálatlan eredményt ad, mivel a ## operátor jelenléte megvédi a külső makróhívás argumentumait a makrókifejtéstől. Így ez az elöfeldolgozó rendszerrel feldolgozva a
cat (1, 2) 3
token karaktersorozatot eredményezi, és ebben a ) 3 (ami az első argumentum utolsó és a második argumentum első tokenjének összekapcsolódása) nem legális token. Ha bevezetjük a
#define xcat(x, y) cat(x, z)
második szintű makródefiníciót, akkor a helyzet egyszerű lesz, az xcat (xcat (1, 2), 3) már valóban az 123 eredményt adja, mivel az xcat kifejtése nem tartalmazza a ## operátort.Hasonló módon az ABSDIFF (ABSDIFF(a, b), c) a várt, teljesen kifejtett eredményt adja.
A12.4. Állományok beépítése
A
#include <állománynév>
alakú vezérlősor hatására a sor helyettesítődik a megadott nevű állomány teljes tartalmával. Az állománynévben nem szerepelhet a > vagy az újsor-karakter, és ha a “, ‘, \ vagy var eucookielaw_data = {"euCookieSet":"1","autoBlock":"0","expireTimer":"0","scrollConsent":"0","networkShareURL":"","isCookiePage":"","isRefererWebsite":""};