C (programovací jazyk)
Programovací jazyk C je štandardný programovací jazyk vyvinutý začiatkom sedemdesiatych rokov. Autorom jazyka je Dennis Ritchie. Pôvodne bol určený pre použitie na operačných systémoch UNIX. Odvtedy sa rozšíril na mnohé iné operačné systémy a je jedným z najpoužívanejších programovacích jazykov. C sa cení vďaka jeho efektívnosti a je najpopulárnejším jazykom na písanie systémového softvéru, hoci sa používa aj na tvorbu aplikačného softvéru. Tiež sa bežne používa pri výuke programovania, hoci nie je určený pre úplných začiatočníkov.
C | |
| |
Druh | imperatívny (procedurálny), štruktúrovaný |
---|---|
Dátum vzniku | 1972 |
Použitie | systémové a aplikačné programovanie |
Typová kontrola | statická, slabá, nominatívna |
Tvorca | Dennis Ritchie |
Implementácie | GCC, Clang, Intel C, MSVC, Pelles C, Watcom C |
Dialekty | Cyclone, Unified Parallel C, Split-C, Cilk, C* |
Používané prípony | .c, .h |
Vlastnosti
Prehľad
C je pomerne minimalistický tzv. imperatívny programovací jazyk na úrovni blízkej hardvéru a je podobnejší strojovo orientovaným jazykom ako väčšina jazykov vyššej úrovne. O jazyku C sa niekedy hovorí ako o "prenosnom strojovom jazyku" (portable assembler). Na rozdiel od strojovo orientovaných jazykov je kód v jazyku C možné skompilovať (preložiť do strojového kódu) pre takmer každý počítač. Táto vlastnosť spolu s tým, že samotný prekladač jazyka C i väčšina jadra UNIXu boli prepísané do C, uľahčilo ich rozšírenie na rôzne počítače. Z týchto dôvodov nazvali jazyk C jazykom strednej úrovne.
Hlavným dôvodom vytvorenia jazyka C bolo uľahčiť písanie programov s menším množstvom chýb procedurálnym spôsobom, ale bez dodatočnej záťaže na kompilátor, ktorého tvorbu komplikujú komplexné vlastnosti jazyka. Z tohto dôvodu má C nasledujúce dôležité vlastnosti:
- Jednoduché jadro jazyka s dôležitou funkcionalitou ako matematické funkcie alebo obsluha súborov presunutou do množiny knižničných rutín namiesto toho, aby bola súčasťou jazyka
- Zameranie na paradigmu procedurálneho programovania s prvkami pre programovanie štruktúrovaným štýlom
- Jednoduchý systém typov, ktorý predchádza ich chybnému použitiu
- Použitie preprocesorového jazyka, C preprocesor, pre úlohy ako definícia makier a viacnásobné vkladanie (include) zdrojového súboru
- Nízkoúrovňový prístup k pamäti počítača pomocou ukazovateľov (smerníkov)
- Minimalistická množina kľúčových slov
- Parametre sa odovzdávajú hodnotou, odovzdávanie parametrov odkazom sa realizuje pomocou mechanizmu ukazovateľov.
- Ukazovatele na funkcie, ktoré umožňujú zárodočnú formu uzáveru a polymorfizmu
- Zmena lexikálneho významu premennej podľa miesta výskytu
- Záznamy, alebo užívateľsky definované zložené údajové typy (
struct
,union
), ktoré umožňujú kombináciu súvisiacich údajov a manipuláciu s nimi ako s celkom - Čo najmenej iného
Niektoré vlastnosti, ktoré chýbajú C a nachádzajú sa v ostatných jazykoch:
- Prísna typová kontrola
- Automatické čistenie pamäti
- Triedy alebo objekty (pozri objekovo orientované programovanie)
- Rozvinutý systém typov
- Uzávery
- Vnorené funkcie (hoci kompilátor GCC ich obsahuje ako rozšírenie)
- Generické programovanie
- Preťažovanie funkcií a preťažovanie operátorov
- Metaprogramovanie
- Prirodzená podpora multithreadingu a sietí. V jazyku C sa na tieto účely používajú knižnice podľa štandardov POSIX (pre multithreading) a implementácie tzv. socketov pre sieťové programovanie.
- Spracovanie zoznamov
Hoci je zoznam vlastností, ktoré jazyku C chýbajú, dlhý, neovplyvnilo to jeho prijatie. Jeho hlavnou výhodou je ľahké písanie kopmpilátorov pre nové platformy a to, že umožňuje programátorovi do veľkej miery riadiť činnosť programu. To často umožňuje efektívnejší beh programu v porovnaní s inými jazykmi. Zvyčajne iba ručne optimalizovaný kód v strojovom jazyku beží rýchlejšie, keďže má úplný prístup k strojovému vybaveniu, ale pokroky v optimalizácii kompilátorov spolu s komplexnosťou moderných procesorov zúžili tento rozdiel.
Jeden z dôsledkov širokého prijatia a efektívnosti jazyka C je, že kompilátory, knižnice a interpretery iných vysokoúrovňových jazykov sú často implementované v jazyku C.
Príklad "hello, world"
Nasledujúca jednoduchá aplikácia sa objavila v prvom vydaní K&R, a stala sa štandardným úvodným programom väčšiny kníh zaoberajúcich sa výučbou programovania, nezávisle od použitého jazyka. Program vypíše "hello, world" na štandardný výstup, čo je zvyčajne terminál alebo obrazovka počítača. Môže to však byť aj súbor alebo nejaké iné hardvérové zariadenie, v závislosti od toho, ako je mapovaný štandardný výstup v čase vykonania programu.
main()
{
printf("hello, world\n");
}
Hore uvedený program sa korektne skompiluje na väčšine moderných kompilátorov, ktoré nie sú v kompatibilnom režime. Kompilátory podľa štandardu ANSI C však vypíšu niekoľko varovných hlásení. Navyše sa kód neskompiluje, ak kompilátor striktne dodržiava štandard C99, keďže návratová hodnota typu int
sa nedoplní. Tieto hlásenia je možné eliminovať niekoľkými malými zmenami originálneho programu:
#include <stdio.h>
int main(void)
{
printf("hello, world\n");
return 0;
}
Nasleduje analýza už uvedeného programu po jednotlivých riadkoch:
#include <stdio.h>
Prvý riadok programu je direktíva preprocessora #include
. Hovorí preprocesoru – prvému nástroju, ktorým prechádza zdrojový kód počas kompilácie – aby nahradil uvedený riadok celým textom súboru alebo inou entitou, na ktorú odkazuje. V tomto prípade sa riadok nahradí hlavičkovým súborom stdio.h
, ktorý obsahuje deklarácie funkcií štandardného vstupu a výstupu. Lomené zátvorky okolo stdio.h
naznačujú, že súbor je možné nájsť na jednom z miest, ktorých zoznam preprocesoru dávame pomocou search path pre hlavičkové súbory.
int main(void)
Tento ďalší riadok hovorí, že definujeme funkciu zvanú main
. Funkcia main
má v jazyku C špeciálny význam. Po spustení programu je main prvá funkcia, ktorá sa volá. int
znamená, že návratová hodnota – hodnota, na ktorú sa vyhodnotí funkcia main
– je typu integer. (void)
znamená, že funkcia main
neočakáva argumenty od kódu, ktorý ju volá. Pozri aj void.
{
Táto ľavá zložená zátvorka označuje začiatok bloku funkcie main
.
printf("hello, world\n");
Tento riadok volá funkciu s názvom printf
(odovzdá riadenie jej kódu), ktorá bola deklarovaná vo vloženom hlavičkovom súbore stdio.h
. Počas tohto volania sa funkcii printf
poskytne jediný argument, reťazcový literál "hello, world\n"
. Jeho časť \n
je tzv. escape sekvencia, ktorá sa preloží na znak EOL, end-of-line, ktorý posunie kurzor v termináli na začiatok nasledujúceho riadku. Návratová hodnota funkcie printf
je typu int
, ale volajúci kód ju nepoužíva, a preto sa ignoruje.
return 0;
Tento riadok ukončuje vykonávanie funkcie main
a priraďuje jej návratovej hodnote nulu.
}
Táto pravá zložená zátvorka označuje koniec bloku funkcie main
.
Typy
C má systém typov podobný iným nasledovníkom ALGOLu ako Pascal, hoci sa odlišuje niekoľkými spôsobmi. Existujú typy pre:
- celé čísla rôznych veľkostí (
int
,short
,long
), pre každú veľkosť typ so znamienkom (signed
) a bez znamienka (unsigned
) - čísla s plávajúcou desatinnou čiarkou s jednoduchou (
float
) a dvojitou (double
) presnosťou - znaky (
char
) - vymenované typy (
enum
) - záznamy (
struct
) - únie (
union
) - špeciálny prázdny typ (
void
), ktorý je možné použiť na indikáciu, že funkcia nevracia hodnotu, respektíve nemá žiadne parametre a na označenie ukazovateľa na ľubovoľný typ (void *
).
Ukazovatele (smerníky, pointery)
C veľmi často využíva smerníky (ukazovatele, pointery), veľmi jednoduchý typ odkazu, ktorý ukladá adresu pamäťového miesta. Premenná, ktorá reprezentuje pointer na hodnotu sa deklaruje so znakom *
pred názvom premennej. Pointer je možné dereferencovať (pomocou operátora *
), aby sa sprístupnili údaje uložené na danej adrese. Adresu je možné meniť bežným priradením a aritmetikou ukazovateľov.
Adresu ľubovoľnej premennej (ale aj funkcie) je možné získať použitím operátora získania adresy (&
). Výsledkom je pointer na typ premennej s hodnotou adresy premennej, v prípade funkcií je to adresa funkcie. Príklad zaobchádzania s pointermi:
Definujeme celočíselné premenné s názvami A a B:
int A, B;
Nastavíme hodnotu premennej A na hodnotu 5:
A = 5;
Definujeme premnennú s názvom pA typu pointer na celočíselnú premennú:
int *pA;
Hodnoty premenných budú po tomto kroku nasledovné:
A = 5
B = <náhodná hodnota>
pA = <náhodná adresa>
Priradíme adresu premennej A do premennej pA. Získanie adresy sa uskutoční operátorom &
:
pA = &A;
Hodnoty premenných budú po tomto kroku nasledovné:
A = 5
pA = <adresa premennej A>
Priradíme hodnotu na ktorú odkazuje premenná pA do premennej B. Keďže premenná pA obsahuje ADRESU premennej A, musíme získať hodnotu na tejto adrese. To docielime dereferencovaním adresy uloženej v premennej pA na hodnotu pomocou operátora *
.
B = *pA;
Hodnoty premenných budú po tomto kroku nasledovné:
A = 5
pA = <adresa premennej ''A''>
*pA = 5
B = 5
Zmeníme hodnotu premennej A na hodnotu 10:
A = 10;
Keďže premenná pA ukazuje na premennú A, táto zmena sa prejaví aj pri dereferencovaní premennej pA. Premenná B však obsahuje priamo hodnotu 5, teda táto zmena sa jej nedotkne:
A = 10
pA = <adresa premennej ''A''>
*pA = 10
B = 5
Počas behu programu, ukazovateľ predstavuje pamäťovú adresu. V čase kompilácie je to zložený typ reprezentujúci adresu aj typ údajov. To umožňuje typovú kontrolu pointerov. Pointery sa v jazyku C používajú na rozličné účely, napríklad textové reťazce s premenlivou dĺžkou sú bežne reprezentované pointerom na prvý znak reťazca. Dynamická alokácia pamäte, ktorá je popísaná nižšie, sa uskutočňuje pomocou pointerov.
Null pointer je vyhradená hodnota, ktorá neukazuje na platné pamäťové miesto (respektíve ukazuje na neexistujúce miesto v pamäti). Je užitočná na indikáciu špeciálnych prípadov ako pointer na ďalší uzol posledného uzla spojového zoznamu. Dereferencovanie null pointera spôsobuje nepredvídateľné správanie (najčastejšie chybu programu a jeho ukončnie). Pointery na typ void
tiež existujú a ukazujú na objekty neznámeho typu. To je obzvlášť užitočné pri generickom programovaní . Keďže veľkosť a typ objektov, na ktoré ukazujú nie je známy, nie je možné ich dereferencovať, ale je možné ich konvertovať na iné typy pointerov pomocou pretypovania.
Polia
Polia v jazyku C sú pevnej statickej veľkosti známej v čase kompilácie; v praxi to nespôsobuje žiadne nedostatky vďaka tomu, že je možné alokovať bloky pamäte počas behu programu použitím štandardnej knižnice a zaobchádzať s nimi ako s poľami. Polia sa definujú nasledovne:
int pole[5];
čo definuje pole celých čísel s piatimi prvkami. Prvky poľa sú číslované v intervale , teda hodnotu prvého a posledného prvku poľa získame volaním:
int prvyPrvok = pole[0];
int poslednyPrvok = pole[4];
C pri kompilácii nekontroluje hranice polí, teda je možné napísať kód, ktorý sa odkazuje za pole a bude preložiteľný kompilátorom. Pri behu programu sa však s najväčšou pravdepodobnosťou vyskytne chyba adresácie pamäte (Segmentation fault).
S poľom môžeme previesť iba dve operácie:
- Zistiť jeho veľkosť (pomocou operátora
sizeof
) v bajtoch. - Získať jeho adresu (pomocou operátora
&
)
Všetky ďalšie operácie ako nastavenie a získanie hodnoty sa potom realizujú ako operácie s ukazovateľom na začiatok poľa a aritmetikou ukazovateľov. Bližšie pozri kapitolu Rozdiel medzi poľami a pointermi.
C tiež poskytuje viacrozmerné polia. Sémanticky tieto polia fungujú ako polia polí, ale fyzicky sú uložené ako jednorozmerné pole s offsetmi, ktoré sa počítajú. Pole je teda uložené v pamäti zaradom, pričom ako prvé sa menia indexy od najvyššieho rozmeru (radenie podľa riadkov).
Pretypovanie
C sa často používa na nízkoúrovňové systémové programovanie, kde môže byť potrebné zaobchádzať s celým číslom ako s pamäťovou adresou, reálnym číslom s dvojitou presnosťou ako s celočíselným typom alebo s jedným typom pointera ako s iným. Pre tieto prípady C poskytuje pretypovanie, ktoré vynúti explicitnú typovú konverziu hodnoty z jedného typu na druhý. Použitie pretypovania znamená čiastočné obetovanie typovej bezpečnosti. Pretypovanie sa uskutočňuje uvedením požadovaného typu do okrúhlych zátvoriek pred hodnotu, ktorú chceme pretypovať:
float desatinneCislo = 5.6F;
int celeCislo = (int) desatinneCislo;
Mimo explicitných konverzií je prekladač schopný poskytnúť aj tzv. implicitnú typovú konverziu medzi blízkymi typmi. Oproti iným, silnejšie typovaným jazykom ako Java alebo C# je práve pomerne flexibilná implicitná konverzia zdrojom mnohých chýb v kóde. Nasledovný kód je platný, pretože sa uplatňuje implicitná konverzia z rozmerovo menších typov na rozmerovo väčšie:
double d;
long l;
int i;
if (d > i)
d = i;
if (i > l)
l = i;
if (d == l)
d *= 2;
Ukladanie údajov
Jedna z najdôležitejších funkcií programovacieho jazyka je poskytnutie prostriedkov na správu pamäte a objektov v nej uložených. C poskytuje tri rozdielne spôsoby alokácie objektov:
- Statická alokácia pamäte: priestor pre objekty je poskytovaný v binárnom odtlačku programu počas kompilácie; tieto objekty majú životnosť takú dlhú ako binárny kód, ktorý ich obsahuje
- Automatická alokácia pamäte: dočasné objekty je možné uložiť do zásobníka, tento priestor sa automaticky uvoľňuje a je možné ho znovu použiť potom, ako vykonávanie programu opustí blok, v ktorom je objekt definovaný
- Dynamická alokácia pamäte: bloky pamäte ľubovoľnej veľkosti je možné vyžiadať počas behu programu použitím knižničných funkcií
malloc()
,alloc()
,realloc()
afree()
z oblasti pamäte zvanej halda (hromada) ; tieto bloky je možné znova použiť potom, ako sa nad nimi zavoláfree()
.
Tieto tri prístupy majú svoje výhody v rôznych situáciách a majú rôzne nevýhody. Napríklad statická alokácia pamäte nemá žiadne dodatočné náklady na alokáciu (no pri nej sa nedá meniť rozsah alokovanej pamäte), automatická alokácia má malé náklady počas inicializácie a dynamická alokácia má potenciál veľkých nákladov na alokáciu aj uvoľnenie. Na druhej strane priestor zásobníka je zvyčajne oveľa obmedzenejší ako statická pamäť alebo priestor haldy a iba dynamická alokácia umožňuje alokáciu objektov, ktorých veľkosť je známa až počas behu programu. Väčšina programov v C využíva všetky tri spôsoby.
Keď je to možné, preferuje sa zvyčajne automatická alebo statická alokácia, pretože ukladanie spravuje kompilátor, čím sa programátor odbremeňuje od procesu manuálnej alokácie a uvoľňovania pamäte, ktorý je náchylný na chyby. Nanešťastie veľkosť mnohých údajových štruktúr môže počas behu rásť; keďže automatická a statická alokácia vyžadujú známu veľkosť v čase kompilácie, sú mnohé prípady, kedy musí byť použitá dynamická alokácia. Polia s premenlivou veľkosťou sú bežným príkladom (pozri príklad dynamicky alokovaného poľa v článku "malloc").
Syntax
Hlavný článok: Syntax jazyka C
Na rozdiel od jazykov ako Fortran 77 má C voľnú formu, čím umožňuje programátorovi používať tzv. biele znaky (whitespaces) na grafickú úpravu zdrojového kódu. Komentáre je možné vkladať buď uzatvorením medzi /*
a */
alebo vložením //
na začiatok riadku (podporované v norme C99).
Každý zdrojový súbor obsahuje deklarácie a definície funkcií. Definície funkcií zasa obsahujú definície premenných a príkazy. Definície premenných buď definujú nové typy pomocou kľúčových slov ako struct
, union
a enum
, alebo priraďujú typ a úložný priestor pre nové premenné, zvyčajne napísaním názvu typu nasledovaného názvom premennej. Kľúčové slová ako char
a int
, ako aj symbol pointera *
, predstavujú vstavané typy. Časti kódu uzatvorené v zložených zátvorkách {
a }
indikujú rozsah, na ktorý sa vzťahujú definície a riadiace štruktúry.
Príkazy vykonávajú činnosti ako zmena hodnoty premennej alebo výstup textu. Riadiace štruktúry slúžia na podmienené alebo cyklické vykonávanie a skladajú sa z kľúčových slov ako if
, else
, switch
, while
, do while
, for
a takmer ľubovoľné skoky sú možné použitím goto
. Činnosť cyklov môžeme ovplyvniť pomocou break
a continue
. Rôzne vstavané operátory vykonávajú základné aritmetické, logické, porovnávacie, bitové operácie, operácie na indexovanie polí a priradenie. Príkazy tiež môžu volať funkcie, vrátane veľkého množstva štandardných knižničných funkcií vykonávajúcich bežné úlohy.
Príklad programu
#include <stdio.h>
int main(void) {
for (int i = 0; i < 5; i++) {
printf("i = %d\n", i);
}
return 0;
}
Každý program v jazyku C musí obsahovať funkciu main
, ktorá vracia celočíselnú hodnotu. Ak program prebehol bez chýb, main
vracia hodnotu 0.
Tento program demonštruje cyklus for
. Používa premennú i, ktorá má počiatočnú hodnotu 0 a táto hodnota sa v každom opakovaní cyklu zväčšuje o 1. Na začiatku každého behu cyklu sa testuje podmienka, či i je menšie ako 5. Ak je táto podmienka splnená, telo cyklu prebehne, ináč sa cyklus skončí. Z toho jasne vyplýva, že náš cyklus bude mať 5 opakovaní. Pre názornosť sa zakaždým vypíše aktuálna hodnota premennej i.
Pozn.: deklarácia premennej vnútri for(;;)
vyžaduje prekladač s podporou štandardu C99.
Výstup programu:
i = 0 i = 1 i = 2 i = 3 i = 4
Problémy
Populárne príslovie, opakované aj takými významnými návrhármi jazykov ako Bjarne Stroustrup, hovorí, že "C ti uľahčí streliť sa do nohy." Inými slovami, C povoľuje viacero operácií, ktoré sú vo všeobecnosti nežiaduce a tak mnohé jednoduché chyby programátora prejdú kompilátorom a zostanú nepovšimnuté aj počas behu. To vedie k programom s nepredvídateľným správaním a bezpečnostnými dierami.
Dôvodom je čiastočne to, že keď bol C jazyk pôvodne navrhnutý, kontroly počas kompilácie a behu boli príliš drahé. Preto namiesto ich umiestnenia do kompilátora bolo potrebné použiť iné nástroje ako lint. Iným dôvodom je snaha udržať C čo najvýkonnejšie a najflexibilnejšie; čím mocnejší je jazyk, tým ťažšie je dokázať veci o programoch v ňom napísaných. Dnes sú dostupné mnohé nástroje umožňujúce programátorovi detegovať a napraviť rôzne bežné problémy, ale iné je nemožné spoľahlivo detegovať kvôli nedostatku obmedzení C programov.
Jedným z problémov je, že automaticky a dynamicky alokované objekty nie sú inicializované; pred prvým použitím majú ľubovoľnú hodnotu, ktorá sa práve nachádzala v im priradenom pamäťovom priestore. Táto hodnota je do veľkej miery nepredvídateľná a môže byť rôzna na dvoch strojoch, medzi dvomi spusteniami programu alebo medzi dvomi volaniami rovnakej funkcie. Ak sa program pokúsi použiť takúto neinicializovanú hodnotu, výsledok je zvyčajne nepredvídateľný. Väčšina moderných kompilátorov deteguje a varuje pred týmto problémom v určitej obmedzenej množine prípadov.
Pointery sú jedným z hlavných zdrojov nebezpečenstva a pretože sú nekontrolované, je možné, aby pointer ukazoval na objekt ľubovoľného typu vrátane kódu a potom naň mohol zapisovať, čo máva nepredvídateľné následky. Hoci väčšina pointerov ukazuje na bezpečné miesta, to sa môže zmeniť použitím pointerovej aritmetiky, pamäť na ktorú ukazujú môže byť dealokovaná alebo už použitá inde (dangling pointers), môžu byť neinicializované (wild pointers) alebo im môže byť priamo priradená ľubovoľná hodnota použitím pretypovania alebo iného poškodeného pointera. Iný problém s pointermi je, že C umožňuje ľubovoľnú konverziu medzi akýmikoľvek dvomi typmi pointerov. Iné jazyky sa pokúšajú tieto problémy riešiť použitím obmedzujúcejších referenčných typov.
Hoci má C natívnu podporu statických polí, neoveruje, či sú indexy polí platné (bounds checking). Napríklad je možné zapísať do šiesteho prvku poľa s piatimi prvkami, čo má vo všeobecnosti nežiaduce následky. Nazývame to buffer overflow (pretečenie buffera) a je to nechvalne známy zdroj množstva bezpečnostných problémov C programov.
Viacrozmerné polia sú potrebné napríklad v numerických algoritmoch (hlavne z aplikovanej lineárnej algebry) na ukladanie matíc (reprezentácií lineárnych zobrazení). Štruktúra poľa v C nie je ani dobre prispôsobená ani vhodná na túto konkrétnu úlohu. Tento problém je rozobraný v knihe Numerical Recipes in C, kap. 1.2, str. 20 ff (). Nájdete tam aj riešenie používané v tejto knihe. Je to zároveň slabina aj silné miesto C, že problém existuje a má v rámci jazyka fungujúce riešenie.
Iný bežný problém je, že pamäť haldy nemôže byť znova použitá, kým nie je explicitne uvoľnená programátorom volaním free()
. Výsledkom je, že ak programátor zabudne uvoľniť pamäť, ale ďalej ju alokuje, bude sa postupne spotrebovávať viac a viac pamäte. Nazývame to memory leak. Naopak, je možné uvoľniť pamäť príliš skoro a potom pokračovať v jej používaní. Pretože alokačný systém môže kedykoľvek znova použiť túto pamäť, výsledkom býva nepredvídateľné správanie. Tieto problémy riešia jazyky s automatickou garbage collection.
Ďalším bežným problémom sú variadické funkcie, ktoré majú premenlivý počet argumentov. Na rozdiel od funkcií s prototypom nie je kontrola argumentov variadických funkcií počas kompilácie vyžadovaná štandardom. Ak sa poskytne zlý údajový typ, výsledok je nepredvídateľný a často fatálny v dôsledku zničenia obsahu zásobníka. Variadické funkcie tiež obsluhujú konštantu nulového pointera neočakávaným spôsobom. Napríklad rodina funkcií printf poskytovaných štandardnou knižnicou, ktoré sa používajú na tvorbu formátovaného textového výstupu je nechvalne známa svojim variadickým rozhraním náchylným na chyby, ktoré sa spolieha na formátovací reťazec, aby špecifikoval počet a typ nasledujúcich argumentov. Typová kontrola variadických funkcií štandardnej knižnice je však vecou kvality implementácie a mnohé moderné kompilátory robia špeciálne kontroly printf volaní a produkujú varovania ak je zoznam argumentov nekonzistentný s formátovacím reťazcom. Treba poznamenať, že nie všetky volania printf je možné kontrolovať staticky (to je zložité keď samotný formátovací reťazec pochádza z ťažko vystopovateľného miesta), a iné variadické funkcie zvyčajne zostávajú nekontrolované.
Boli vytvorené nástroje, ktoré pomáhajú programátorovi vyhnúť sa mnohým z týchto chýb vo viacerých prípadoch. Automatická kontrola zdrojového kódu a auditácia sa hodí v každom programovacom jazyku a pre C existuje množstvo takýchto nástrojov ako napríklad Lint. Bežnou praxou je použiť Lint na detekciu pochybného kódu hneď po napísaní programu. Keď program prejde Lintom, skompiluje ho aj kompilátor C. Existujú tiež knižnice na kontrolu hraníc polí (bounds checking) a obmedzená forma automatickej garbage collection, ale nie sú štandardnou súčasťou jazyka ani knižníc.
C má aj iné problémy, ktoré priamo neústia do chýb, ale sťažujú programátorovi možnosť budovať robustné a jednoducho spravovateľné systémy. Medzi ne patria:
- Krehký systém pre importovanie definícií, ktorý sa spolieha na vkladanie textu pomocou literálu a udržiavanie prebytočných prototypov a definícií funkcií v synchronizácii.
- Neobvyklý model kompilácie, ktorý vyžaduje manuálne sledovanie závislostí a sťažuje optimalizáciu kompilátorom medzi modulmi (s výnimkou optimalizácie v čase linkovania).
- Systém slabých typov, ktorý nechá množstvo jasne chybných programov skompilovať bez chybového hlásenia.
- Obťažnosť vytvárať nepriehľadné štruktúry, ktorá ústi do programov porušujúcich information hiding.
- Neintuitívna deklaračná syntax, obzvlášť pre pointre na funkcie. Slovami odborníka na jazyky Damiana Conwaya o veľmi podobnej deklaračnej syntaxi jazyka C++:
- Špecifikácia typu v C++ je sťažená faktom, že niektoré zo súčastí deklarácie (ako špecifikátor pointera) sú prefixové operátory, kým iné (ako špecifikátor poľa) sú postfixové. Tieto deklaračné operátory majú tiež rôznu precedenciu, čo vynucuje starostlivé uzátvorkovanie pre dosiahnutie požadovanej deklarácie. Navyše, ak sa má na identifikátor použiť typový identifikátor, tento skončí niekde medzi týmito operátormi a tak sa stratí už v stredne zložitých prípadoch (pozri napr. Prílohu A). Výsledkom je veľmi znížená jasnosť takýchto deklarácií.
- Ben Werther & Damian Conway. A Modest Proposal: C++ Resyntaxed. Section 3.1.1. 1996.
História
Skoré začiatky
Začiatky vývoja C siahajú do AT&T v Bellovych laboratóriách medzi rokmi 1969 a 1973; podľa Ritchieho bol najkreatívnejším obdobím rok 1972. Pomenovanie "C" dostal, pretože mnohé vlastnosti boli odvodené od skorišieho jazyka zvaného "B". Názory na pôvod názvu "B" sa líšia: Ken Thompson tvrdí, že zásluha patrí programovaciemu jazyku BCPL, ale vytvoril aj jazyk zvaný Bon na počesť jeho manželky Bonnie.
Ohľadne pôvodu C a súvisiaceho operačného systému Unix koluje veľa legiend, vrátane nasledovnej:
- vývoj jazyka C bol výsledkom túžby programátorov hrať Spacewar. Hrávali ju na mainframe ich spoločnosti, ale keďže býval preťažený a musel slúžiť asi 100 používateľom, Thompson a Ritchie zistili, že nemali dostatočnú kontrolu nad hviezdnou loďou aby sa mohli vyhýbať kolíziám s asteroidmi. Tak sa rozhodli hru portovať na nevyužívaný PDP-7. Ten ale nemal operačný systém, tak sa rozhodli jeden vytvoriť. Nakoniec sa rozhodli tento operačný systém portovať na PDP-11, to ale bolo obťažné, pretože všetok kód bol v assembleri. Rozhodli sa použiť portabilný jazyk vyššej úrovne, aby bolo možné operačný systém jednoducho portovať z jedného počítača na druhý. Skúšali použiť B, ale tomu chýbala funkcionalita, aby mohol využiť niektoré vyspelé schopnosti PDP-11. Tak sa rozhodli vytvoriť nový jazyk C.
- Odôvodnie pre získanie pôvodného počítača bolo na vytvorenie systému na automatizáciu podávania patentov. Originálna verzia Unixu bola napísaná v assembleri. Neskôr bol vyvinutý jazyk C pre účely prepísania operačného systému.
V roku 1973 sa už jazyk C stal dostatočne silný na to, aby väčšina UNIXového kernelu, pôvodne napísaného v assembleri PDP-11/20, mohla byť prepísaná do C. Toto bolo jedno z prvých jadier operačného systému implementovaných v inom jazyku ako assembler, pričom skoršími boli systémy Multics (napísaný v jazyku PL/I), TRIPOS (v BCPL) a MCP (Master Control Program) pre Burroughs B5000 napísaný v ALGOLe v roku 1961.
K&R C
V roku 1978, Dennis Ritchie a Brian Kernighan publikovali prvé vydanie knihy The C Programming Language. Táto kniha známa medzi programátormi ako "K&R", slúžila mnoho rokov ako neformálna špecifikácia jazyka. Verzia C, ktorú opisuje sa bežne nazýva "K&R C." (Druhé vydanie knihy pokrýva neskorší, dolu opísaný štandard ANSI C.)
K&R zaviedli do jazyka nasledovné prvky:
- údajové typy
struct
- údajový typ
long int
- údajový typ
unsigned int
- Operátor
=+
bol zmenený na+=
, atď. (=+
miatol lexikálny analyzátor kompilátora C; napr.i =+ 10
v porovnaní si = +10
).
K&R C sa často považuje za najzákladnejšiu časť jazyka, ktorú má C kompilátor podporovať. Po mnoho rokov, dokonca aj po uvedení ANSI C, bol považovaný za "najmenší spoločný násobok", ktorý C programátori dodržiavali, ak bola potrebná maximálna portabilita, keďže nie všetky kompilátory boli aktualizované na plnú podporu ANSI C a rozumne napísaný K&R C je tiež platným ANSI C.
V týchto skorých verziách C bolo potrebné pred použitím definovať či deklarovať (použitím prototype výrazu) iba funkcie, ktoré nevracali hodnotu typu integer. Funkcia použitá bez akejkoľvek deklarácie bola považovaná za funkciu vracajúcu typ integer. Parametre funkcií nemali typovú kontrolu, hoci niektoré kompilátory vyhlásili varovanie ak bola funkcia zavolaná so zlým počtom argumentov.
V rokoch po publikácii K&R C, bolo pridaných do jazyka niekoľko "neoficiálnych" vlastností, podporovaných kompilátormi od AT&T a niektorými inými. Medzi ne patrili:
- funkcie
void
a údajový typvoid *
- funkcie vracajúce typ
struct
alebounion
- názvy položiek
struct
v oddelenom mennom priestore pre každý typ struct - priradenie pre údajové typy
struct
- kvalifikátor
const
na definovanie objektu len na čítanie - štandardná knižnica s väčšinou funkcionality od rôznych výrobcov
- vymenovaný typ (enum)
- údajový typ
float
s jednoduchou presnosťou
ANSI C a ISO C
Koncom sedemdesiatych rokov začalo C nahrádzať programovací jazyk BASIC ako vedúci programovací jazyk pre mikropočítače. Počas osemdesiatych rokov bol prispôsobený na použitie s IBM PC a jeho popularita výrazne vzrástla. V rovnakom čase Bjarne Stroustrup a iní v Bellovych laboratóriách začali pracovať na pridaní štruktúr objektovo orientovaného programovania do C. Jazyk, ktorý tak vznikol bol nazvaný C++ a dnes je najbežnejším aplikačným programovacím jazykom pre operačný systém Microsoft Windows; C zostáva populárny vo svete Unixu. Iným jazykom vyvinutým v tej dobe bol Objective-C, ktorý tiež pridáva schopnosti OOP. Hoci nie je až taký populárny ako C++, používa sa napríklad na vývoj Cocoa aplikácií pre Mac OS X.
V roku 1983 vytvoril Americký Národný Štandardizačný Inštitút (ANSI) komisiu X3J11 na zostavenie štandardnej špecifikácie C. Po dlhom a únavnom procese bol štandard dokončený v roku 1989 a ratifikovaný ako ANSI X3.159-1989 "Programming Language C". Táto verzia jazyka sa bežne nazýva ANSI C. V roku 1990 bol ANSI C štandard (s niekoľkými drobnými modifikáciami) prijatý Medzinárodnou štandardizačnou Organizáciou (ISO) ako ISO/IEC 9899:1990.
Jedným z cieľov štandardizačného procesu ANSI C bolo vytvoriť nadmnožinu K&R C, ktorá by zahŕňala množstvo následne uvedených neoficiálnych vlastností. Štandardizačná komisia však pridala aj niekoľko nových vlastností ako prototypy funkcií (požičané z C++) a schopnejší preprocessor.
ANSI C je dnes podporovaný takmer všetkými najpoužívanejšími kompilátormi. Väčšina dnes napísaného C kódu je založená na ANSI C. Akýkoľvek program napísaný iba v štandardnom C sa zaručene bude správať korektne na akejkoľvek platforme s konformnou implementáciou C. Boli však napísané mnohé programy, ktoré sa korektne skompilujú iba na istej platforme alebo istom kompilátore, z dôvodu :
- použitia neštandardných knižníc, napr. pre GUI
- nedodržaním ANSI C štandardu alebo jeho nasledovníka niektorými kompilátormi v štandardnom režime
- závislosťou na presnej veľkosti niektorých údajových typov, ako aj na poradí významu bajtov danej platformy.
C99
Po skončení štandardizačného procesu zostala špecifikácia C jazyka po určitú dobu relatívne nemenná, kým C++ sa ďalej vyvíjal. (Normatívny Dodatok 1 vytvoril novú verziu jazyka C v roku 1995, ale táto verzia sa spomína zriedka). Štandard však podstúpil revíziu na konci deväťdesiatych rokov, čo viedlo k vydaniu ISO 9899:1999 v roku 1999. Tento štandard sa bežne nazýva "C99". Bol prijatý za ANSI štandard v marci 2000.
Medzi nové vlastnosti v C99 patria:
- inline funkcie
- premenné je možné deklarovať kdekoľvek (ako v C++), nie len po inej deklarácii alebo začiatku bloku
- niekoľko nových údajových typov vrátane
long long int
(aby sa uľahčil prechod z 32 na 64-bitov), explicitný údajový typ boolean a typcomplex
reprezentujúci komplexné číslo - polia s premenlivou dĺžkou
- podpora jednoriadkových komentárov začínajúcich
//
, ako v BCPL alebo C++, čo mnohé kompilátory podporovali ako rozšírenie - niekoľko nových knižničných funkcií ako
snprintf()
- niekoľko nových hlavičkových súborov ako
stdint.h
Záujem o zahrnutie nových vlastností C99 sa zdá byť rôzny. Kým GCC a niekoľko iných kompilátorov dnes podporuje väčšinu vlastností C99, kompilátory od Microsoftu a Borlandu nie, a tieto spoločnosti neprejavujú záujem o pridanie tejto podpory.
Vzťah k C++
Programovací jazyk C++ bol pôvodne odvodený od C. Ale na rozdiel od všeobecného názoru nie každý C program je platným C++ programom. Ako sa C a C++ nezávisle vyvíjali, nastal nešťastný nárast nekompatibilít medzi týmito dvomi jazykmi . Najnovšia revízia C, C99, vytvorila niekoľko ďalších konfliktných vlastností. Tieto rozdiely sťažujú písanie programov a knižníc, ktoré je možné korektne skompilovať ako C aj C++ kód, a mätú tých, ktorí programujú v oboch jazykoch. Odlišnosti tiež sťažujú adaptáciu vlastností z jedného jazyka do druhého. Bjarne Stroustrup, tvorca C++, opakovane navrhoval , aby sa počet nekompatibilít medzi C a C++ zredukoval na najnižšiu možnú mieru. Iní argumentujú, že keďže C a C++ sú dva odlišné jazyky, kompatibilita medzi nimi je užitočná, ale nie životne dôležitá a podľa tohto tábora by snahy redukovať nekompatibilitu nemali zamedzovať pokusom o vylepšenie každého z jazykov jednotlivo.
Dnes sú primárnymi rozdielmi medzi týmito jazykmi:
- inline funkcie sú v C++ v globálnom pohľade a v C v pohľade súboru (tzv. statický). Zjednodušene povedané to znamená, že v C++ akákoľvek definícia inline funkcie (mimo preťažených funkcií C++) musí spĺňať "One Definition Rule" (ODR) C++, čo vyžaduje, že buď bude jediná definícia každej inline funkcie alebo že všetky definície budú sémanticky ekvivalentné; ale v C môže byť jedna inline funkcia definovaná odlišne v rozličných prekladových moduloch (prekladový modul zvyčajne zodpovedá súboru).
- Kľúčové slovo
bool
v C99 je v oddelenom hlavičkovom súbore,<stdbool.h>
. Predchádzajúce štandardy C nedefinovali typ boolean, a tak boli na jeho simuláciu používané rozličné (vzájomne nekompatibilné) metódy. - Znakové konštanty (uzatvorené v apostrofoch) majú v C veľkosť
int
a V C++ veľkosťchar
. Takže v Csizeof('a') == sizeof(int)
; v C++sizeof('a') == sizeof(char)
. Ale aj tak ani v C nikdy neprekročia hodnotu uchovateľnú typomchar
, takže(char)'a'
je bezpečným pretypovaním. - V C++ boli zavedené ďalšie kľúčové slová, a tieto preto nemôžu byť použité ako identifikátory, ako to je možné v C (napr.
try
,catch
,template
,new
,delete
…). - V C++ vytvára kompilátor automaticky "tag" pre každú
struct
,union
aenum
, takžestruct S {};
v C++ je ekvivalentné stypedef struct S {} S;
v C.
C99 prijalo niektoré ďalšie vlastnosti, ktoré sa po prvýkrát objavili v C++. Medzi ne patria:
- Povinné deklarácie prototypov funkcií
- Kľúčové slovo
inline
- Odstránenie implicitnej návratovej hodnoty typu int
Referencie
- Brian Kernighan, Dennis Ritchie: The C Programming Language. Známa aj ako K&R – The original book on C.
- 1st, Prentice Hall 1978; ISBN 0-13-110163-3. Pre-ANSI C.
- 2nd, Prentice Hall 1988; ISBN 0-13-110362-8. ANSI C.
- British Standard Institute: The C Standard, John Wiley & Sons, ISBN 0-470-84573-2. The official ISO standard (C99) in book form.
- Samuel P. Harbison, Guy L. Steele: C: A Reference Manual. This book is excellent as a definitive reference manual, and for those working on C compilers. The book contains a BNF grammar for C.
- 4th, Prentice Hall 1994; ISBN 0-13-326224-3.
- 5th, Prentice Hall 2002; ISBN 0-13-089592-X.
- Robert Sedgewick: Algorithms in C, Addison-Wesley, ISBN 0-201-31452-5 (Part 1–4) and ISBN 0-201-31663-3 (Part 5)
- William H. Press, Saul A. Teukolsky, William T. Vetterling, Brian P. Flannery: Numerical Recipes in C (The Art of Scientific Computing), ISBN 0-521-43108-5
Pozri aj
- C preprocessor
- Štandardná knižnica C
- Knižnica C
- Syntax C
- Zoznam článkov s C programami
- Objective-C
- Operátory v C a C Plus Plus
- Programovacie nástroje: Cygwin, Dev-C/C++, DJGPP, GNU Compiler Collection, LCC, Linker, make, SPlint, Small-C, C--
Externé odkazy
C
- comp.lang.c Frequently Asked Questions, autor Steve Summit
- The Development of the C Language, autor Dennis M. Ritchie
- Programming in C (kolekcia dokumentov na Lysator)
- International Obfuscated C Competition
- Programming C na Wikibooks
- Computer-Books.us Online books about C
C99
- Open source development using C99 — Is your C code up to standard?, autor Peter Seebach
- Are you Ready For C99?