Deklarací se identifikátoru přisoudí jeho význam v kontextu programu. Ve většině případů je to paměťová třída a typ objektu, v deklaraci typu konstrukcí typedef jde o přejmenování typů.
Paměťové třídy objektů jsou auto, static, extern a register, odst. 5.2.3. Dodejme zde, že paměťová třída nemusí být explicitně uvedena a pak je určena z kontextu. Objekty deklarované uvnitř bloku některé funkce jsou třídy auto, objekty deklarované mimo funkce jsou třídy extern. Tak např. v úryvku programu
int i; int f(x); int x; { int y; ... }
Objektu smí být explicitně přisouzena nejvýše jedna paměťová třída. Typ objektu je dán konstrukcí, která následuje za případným určením paměťové třídy. Může to být základní typ (se specifikací délky), struktura nebo unie, výčtový typ nebo jméno typu vytvořeného mechanismem typedef.
Pro určení základního typu se užívají klíčová slova: char, short, int, long, unsigned, float, double, void. Typ void (prázdný) se užívá v deklaracích funkcí a označuje funkci, která nevrací žádnou hodnotu - tedy proceduru v pascalském smyslu. Klíčová slova short a long se mohou využít jako specifikace délky typu int, přitom short int má stejný význam jako short, long int má stejný význam jako long.
Chybí-li totiž určení základního typu, považuje se za int. Přípustné je rovněž long float s významem double a long double pro čtyřnásobnou přesnost.
Samostatné klíčové slovo unsigned má význam unsigned int, lze je však použít pro všechny celočíselné typy, tedy unsigned char, unsigned short int, unsigned long apod.
Specifikace délky, resp. unsigned není povolena v kombinaci se jménem typu vzniklého mechanismem typedef.
Za určením paměťové třídy a typu následuje seznam deklarovaných objektů. Ve většině programovacích jazyků jde o seznam identifikátorů objektů, v jazyku C je situace poněkud složitější.
Identifikátor I je povolen a označuje objekt určené paměťové třídy a typu. Jde však o nejjednodušší případ deklarátoru označeného např. D, jehož konstrukce se tvoří podle schématu:
D: I *D D() D[] (D)
Čteme po řadě takto: deklarátor je identifikátor, ukazatel na objekt určeného typu, funkce vracející určený typ, pole objektů určeného typu. Poslední řádek schématu povoluje uzávorkování pro složitější konstrukce. Každý deklarátor musí obsahovat jediný identifikátor, kterému se deklarací přisoudí jeho význam.
Tak deklarace
int i, *pi, fi(), ai[], *fpi(), (*pfi)();
Myšlenkou, stojící za komplikovanou a nepřehlednou formou deklarací, je stejný zápis ve výrazech, které deklarované objekty používají. Tak např. přiřazení:
i = (*pfi)();
Pole může být vytvořeno z objektů základních typů, ukazatelů, struktur nebo unií a polí (vícerozměrná pole). V lomených závorkách je uveden konstantní výraz s kladnou hodnotou vyhodnocený v době kompilace a určující počet objektů v poli. Tento výraz může chybět pouze v prvním rozměru pole. To je užitečné opatření v případě externích polí, kdy skutečná deklarace pole, alokující paměť, je umístěna v jiném programovém modulu. První rozměr pole může rovněž chybět v případě, kdy se dá určit z počtu iniciálních hodnot pole (odst. 5.4.5). Například
double ad[100], *pad[20];
int x[2][4][6];
x, x[i], x[i][j], x[i][j][k]
Struktura je agregovaný objekt sestávající z posloupnosti pojmenovaných složek (pascalský záznam). Unie je výběrový objekt, obsahující vždy jednu z několika pojmenovaných složek, tj. bitový vzorek uložený v paměti pro unii se interpretuje shodně s typem vybrané složky (pascalské variantní složky záznamu). Složky struktury nebo unie mohou být libovolného typu. V deklaraci struktury nebo unie je podstatná šablona (srov. s odst. 5.1.2), která pouze určí, jak budou objekty tohoto typu vypadat, např.
struct rodcislo { short den; short mesic; short rok; int kod; }
Objekt struktury rodcislo sestává ze tří složek short a jedné složky typu int. Za šablonou může ihned následovat seznam deklarátorů objektů:
struct rodcislo { short den; short mesic; short rok; int kod; } dd, *pdd;
Identifikátor dd označuje objekt typu struct rodcislo, identifikátor pdd ukazatel na objekt typu struct rodcislo. Je-li struktura použita v programu vícekrát, je výhodnější použít šablonu jako definici nového typu struct rodcislo a později deklaraci tvaru
struct rodcislo dd, *pdd;
Pvolené výrazy jsou
dd.rok = 1950; pdd = ⅆ pdd->den = 1;
++pdd->den
Obdobně můžeme deklarovat šablonu
struct osoba { char jmeno[15]; char prijmeni[20]; struct rodcislo rc; }
struct osoba sapo[60], *pos;
Objekt sapo je polem šedesáti struktur typu osoba; každá sestává ze dvou polí char a struktury rodcislo, obsahující tři objekty typu short a jeden objekt typu int.
Chceme-li např. naplnit rok narození i-té osobě sapo, můžeme tak učinit výrazem
sapo[i].rc.rok = 1900;
pos = &sapo[i]; pos->rc.rok = 1900;
Použijeme nyní znovu strukturu uzel z odst. 5.1.2:
struct uzel{ char text[10]; int op; struct uzel *levy; struct uzel *pravy; }
struct uzel u, *pu;
Pak výraz
pu->op
u.levy
u.pravy->text[0]
Složkou struktury, resp. unie může být i bitové pole, jehož počet bitů reprezentace je explicitně určen kladnou konstantou oddělenou dvojtečkou od deklarátoru. Tak např. ve struktuře
struct hs { unsigned hlava :5; :3; unsigned sektor :8; }
Složky struktury obecně (nejen bitová pole) se postupně ukládají ve smyslu vzrůstajících adres. Přitom se dbá na potřebná zarovnání. Složka tvořená nepojmenovaným bitovým polem nulové délky, tedy :0 je požadavkem o explicitní zarovnání následující složky struktury nebo unie na hranici závislou na implementaci.
Jazyk neomezuje typ bitových polí, ale většina implementací podporuje pouze celočíselné pole, a to ještě typu unsigned.
Unie si můžeme představit jako struktury, jejichž všechny složky mají stejnou (nulovou) adresu od počátku přidělené paměti. Velikost paměti se volí tak, aby stačila pro uchování nejdelší složky. Například s použitím deklarace
union diskadr { int ca; struct hs, da; } bulhar;
bulhar.ca
bulhar.da
S jednotlivými složkami se pak pracuje takto:
bulhar.da.hlava = 1; bulhar.da.sektor = 3;
Je rovněž myslitelná varianta
bulhar.ca = 1 << 11 | 3;
Deklarací výčtového typu určíme celočíselný podtyp. Stejně jako v deklaraci struktury, resp. unie se využívá mechanismu šablony, např.
enum den { PO,UT,ST,CT,PA,SO,NE };
enum den dnes, *d;
Jsou pak přípustné výrazy
dnes = UT; d = &dnes; switch(*d) { case PO: ... case UT: ... ... }
enum barva { bila, cervena=15, modra } odstin;
Objektu může být v deklaraci přisouzena počáteční hodnota, může být inicializován. Inicializace má formálně tvar přiřazovacího výrazu, ale její interpretace závisí na paměťové třídě objektu. Počáteční hodnoty statických a externích objektů musí být konstantní výrazy, aby je bylo možno vyhodnotit v době kompilace. Těmto objektům je počáteční hodnota totiž přisouzena pouze jednou, a to v okamžiku zavádění programu do paměti.
Automatické a registrované objekty mohou být inicializovány libovolnými výrazy, i ty se totiž vyhodnocují při každém vstupu do bloku, v němž jsou deklarace těchto objektů umístěny.
Statické a externí proměnné bez inicializace mají zaručenou nulovou počáteční hodnotu. Počáteční hodnotu automatické a registrované proměnné bez inicializace nelze určit, protože obsahuje pozůstatky předchozí historie procesu.
Inicializace jednoduchého objektu (proměnné, ukazatele) se provede jedním výrazem:
int i = 0; double d = 3.141592;
float x[4] = { 1,2,3 }; double y[] = { 1,2,3,4,5 };
V případě vícerozměného pole se inicializace provádí buď s využitím úplného závorkování
long z[2][3] = { {1,2,3}, {4,5,6} };
long z[2][3] = {1,2,3,4,5,6};
Speciálním případem je inicializace znakového pole
char text[] = "Zprava pro uzivatele\n";
Inicializace struktur probíhá rovněž po složkách, např.
struct rodcislo dd = {1,1,1950,755};
struct osoba on = {"Josef", "Novak", {1,1,1950,755}};
Program v C sestává z posloupností externích definic. Externí definice přisoudí identifikátoru paměťovou třídu extern (implicitně) a specifikovaný typ (implicitně int).
Externí definice má tvar deklarací s tím, že je to jediné místo, kde lze zadat kód (tělo) funkcí. Definice jsou dvojí, a to funkcí a dat.
Definice funkce sestává z deklarátoru funkce se seznamem formálních parametrů, popř. seznamu specifikací formálních parametrů a těla funkce, tj. složeného příkazu, resp. bloku. Blok je složený příkaz s deklaracemi lokálních objektů (odst. 5.5.2). Povolené paměťové třídy funkcí jsou extern a static. Zápis
int min(x,y,z) int x,y,z; { int m; m = (x < y) ? x:y; return ((m < z) ? m:z); }
V seznamu specifikací se smí objevit specifikace právě těch identifikátorů, které jsou v deklarátoru funkce uvedeny jako formální parametry. Pokud chybí specifikace některého z nich, chápe se jako int. Jedinou povolenou paměťovou třídou je zde třída registr (implicitně mají třídu auto). Je-li to možné, je skutečný argument předán v registru.
Externí definice dat se zapisuje jako jejich deklarace. Povolenou paměťovou třídou je extern (implicitně) a static. Například
extern char zprava[35]; static double pi = 3.141592;
Program v C nemusí být kompilován vcelku. Posloupnost externích definic může být rozdělena do několika souborů, některé funkce mohou být doplněny z knihoven. Návaznost funkcí v jednom programu je zajištěna buď vzájemným explicitním voláním, nebo předáváním externích dat. Cílem pravidel, která určují rozsah platnosti identifikátorů, je zajistit spolupráci jednotlivých částí programu.
Pravidla jsou dvojího druhu. První druh pravidel se týká lexikálního rozsahu platnosti a jak jednotlivé části programu řadit za sebou, aby nedocházelo k hlášením kompilátoru typu "nedefinovaný identifikátor". Druhý druh pravidel řídí návaznost externích identifikátorů a je mu věnován následující odstavec.
Lexikální rozsah platnosti identifikátoru z externí definice je od místa jeho definice do konce zdrojového souboru s touto definicí. Lexikální rozsah platnosti formálních parametrů funkce končí s koncem těla funkce. Lexikální rozsah platnosti identifikátorů, deklarovaných v bloku, končí s koncem tohoto bloku. Lexikální rozsah platnosti návěští je tělo funkce, v němž je uvedeno.
Poznamenejme ještě, že pro použití stejných identifikátorů ve vnořených blocích má jazyk C obvyklé řešení ve formě "zneviditelnění" vnějšího významu. V úryvku programu
int z; f(z) double z; { z = 2.78; }
Posledním pravidlem lexikální návaznosti je, že identifikátory proměnných, šablon a složek struktur nebo unií patří do tří různých kategorií, takže nemůže dojít ke konfliktu. Lze pak psát např.
struct proc { char status; ... } proc[NPROC];
Cílem pravidel pro externí identifikátory je, aby odkazy na týž externí identifikátor vedly k témuž objektu. Jestliže funkce odkazuje na identifikátor deklarovaný jako extern, pak v jiné části programu (zdrojovém souboru, knihovní funkci) musí existovat alespoň jedna externí definice tohoto identifikátoru. Přirozeným požadavkem je, aby typ a velikost odkazovaného objektu byly ve všech definicích stejné. Dále je povolena nejvýše jedna explicitní inicializace externího objektu.
Je však povolena více než jedna definice datového externího identifikátoru (pro funkce to nemá smysl). V Unixu se násobné definice externích dat vyřeší na úrovni sestavování. Externí datové objekty pak připomínají spíše fortranskou pojmenovanou COMMON oblast.
Podle návrhu normy jazyka C má klíčové slovo extern dodatečný význam. Je-li použito explicitně (a bez inicializace) v externí definici identifikátoru, rozšiřuje lexikální rozsah tohoto identifikátoru do uvažovaného zdrojového souboru, paměť je objektu přidělena jinde. Dále se vyžaduje, aby v celém programu existovala právě jedna definice tohoto identifikátoru (bez klíčového slova extern).
Tak v implementaci pro Unix je přípustná situace:
soubor a.c soubor b.c extern int errno; extern int errno; extern char errmsg[30]; extern char errmsg[30]; extern void f(); main() void f(x) { ... int x; errno = 0; { f(15); if(x > 10){ if(errno != 0) errno = 3; printf("%s", errmsg); errmsg = "ARG. > 10\n"; ... } } ... }
V situaci, kdy klíčové slovo extern má dodatečný význam, musí programátor postupovat např. takto:
soubor a.c soubor b.c int errno; extern int errno; char errmsg[30]; extern char errmsg[]; extern void f(); void f(x); main() int x; { { ... ... } }
V obou případech budou objekty programu umístěny v paměti podle obr. 5.2.
Statické objekty, tj. objekty explicitně deklarované klíčovým slovem static, mají dvojí povahu. Jsou-li uvedeny mezi externími definicemi, mají lexikální platnost externích identifikátorů, nejsou však vidět z jiných zdrojových souborů. Statické objekty deklarované v těle funkce mají lexikální rozsah platnosti jako automatické proměnné, ale na rozdíl od nich trvají po celou dobu provádění programu. Představují tedy možnost stálé a přitom lokální paměti pro funkci. Například v části programu umístěném v jednom zdrojovém souboru:
static int x; int f() { static int z; ... } static int y; int q() { ... } int h() { ... }