C (programovací jazyk)

programovací jazyk
(Presmerované z ANSI C)

Programovací jazyk C je štandardný programovací jazyk vyvinutý začiatkom 70. rokov 20. storočia. 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

Druhimperatívny (procedurálny), štruktúrovaný
Dátum vzniku1972
Použitiesystémové a aplikačné programovanie
Typová kontrolastatická, slabá, nominatívna
TvorcaDennis Ritchie
ImplementácieGCC, Clang, Intel C, MSVC, Pelles C, Watcom C
DialektyCyclone, Unified Parallel C, Split-C, Cilk, C*
Používané prípony.c, .h

Programovací jazyk C sa od roku 2000 konzistentne nachádza na jednej z prvých dvoch priečok hodnotenia TIOBE index, ktorý porovnáva popularitu programovacích jazykov.[1]

Vlastnosti

upraviť

Prehľad

upraviť

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:

Niektoré vlastnosti, ktoré chýbajú C a nachádzajú sa v ostatných jazykoch:

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“

upraviť

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.

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)

upraviť

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 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

upraviť

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

upraviť

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() a free() 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").

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

upraviť
#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

upraviť

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. [2] 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í.[3]

História

upraviť

Skoré začiatky

upraviť

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 skorš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.

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í s i = +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ý typ void *
  • funkcie vracajúce typ struct alebo union
  • 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

upraviť

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.

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 typ complex 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++

upraviť

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.[4] 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,[5] 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 C sizeof('a') == sizeof(int); v C++ sizeof('a') == sizeof(char). Ale aj tak ani v C nikdy neprekročia hodnotu uchovateľnú typom char, 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 a enum, takže struct S {}; v C++ je ekvivalentné s typedef 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

upraviť
  1. TIOBE Index [online]. TIOBE, [cit. 2024-01-22]. Dostupné online. (po anglicky)
  2. Numerical recipes in C. Book: William H. Press. Cambridge : Cambridge Univ. Pr, 2002. (2. ed., repr.) Dostupné online. ISBN 978-0-521-43108-8. Archivované 2003-05-01 z originálu.
  3. Ben Werther & Damian Conway. A Modest Proposal: C++ Resyntaxed. Section 3.1.1. 1996.
  4. Incompatibilities Between ISO C and ISO C++ [online]. david.tribble.com, [cit. 2024-01-22]. Dostupné online.
  5. Sibling Rivalry: C and C++ [online]. 2022. Dostupné online. Archivované 2003-04-24 z originálu.

Literatúra

upraviť

Pozri aj

upraviť

Externé odkazy

upraviť