More actions
Bez shrnutí editace |
Bez shrnutí editace |
||
Riadok 70: | Riadok 70: | ||
Každý skompilovateľný C súbor musí obsahovať funkciu <code>main</code>. Je to funkcia, ktorá sa pri spustení programu v terminály zavolá vždy ako prvá - je to hlavný vstupný bod do programu. Táto funkcia môže potom volať ďalšie funkcie ktoré boli predtým definované, čo nám umožňuje logiku rozkúskovať na viaceré časti, pre lepšiu čitateľnosť a odstránenie opakujúceho sa kódu. | Každý skompilovateľný C súbor musí obsahovať funkciu <code>main</code>. Je to funkcia, ktorá sa pri spustení programu v terminály zavolá vždy ako prvá - je to hlavný vstupný bod do programu. Táto funkcia môže potom volať ďalšie funkcie ktoré boli predtým definované, čo nám umožňuje logiku rozkúskovať na viaceré časti, pre lepšiu čitateľnosť a odstránenie opakujúceho sa kódu. | ||
=== | === Hlavičkové súbory === | ||
Jednou zo špecifických vlastností jazyka C (a taktiež dôvod, prečo je taký rýchly) je fakt, že základná výbava neobsahuje skoro nič, dokonca ani <code>printf</code>, čo je v mnohých iných jazykoch samozrejmosťou). Tieto funkcie musíme v našom programe zahrnúť pomocou direktívy preprocesora <code>#include</code>, ktorý obsahuje cestu k hlavičkovému súboru s deklaráciami funkcií ktoré zahŕňame (špicaté zátvorky hovoria, že kompilátor má hľadať hlavičkový súbor v systémových cestách, napr.: <code>#include <stdio.h></code>). | Jednou zo špecifických vlastností jazyka C (a taktiež dôvod, prečo je taký rýchly) je fakt, že základná výbava neobsahuje skoro nič, dokonca ani <code>printf</code>, čo je v mnohých iných jazykoch samozrejmosťou). Tieto funkcie musíme v našom programe zahrnúť pomocou direktívy preprocesora <code>#include</code>, ktorý obsahuje cestu k hlavičkovému súboru s deklaráciami funkcií ktoré zahŕňame (špicaté zátvorky hovoria, že kompilátor má hľadať hlavičkový súbor v systémových cestách, napr.: <code>#include <stdio.h></code>). | ||
Riadok 158: | Riadok 158: | ||
* <code>#include</code> - vloží obsah iného súboru do aktuálneho súboru; | * <code>#include</code> - vloží obsah iného súboru do aktuálneho súboru; | ||
** špicaté (<code><</code>, <code>></code>) zátvorky hovoria, že chceme zahrnúť hlavičkový súbor ktorý sa nachádza v systémových cestách (jedná sa o systémové knižnice ktoré sa nainštalovali spolu s kompilátorom) | |||
** úvodzovky (<code>""</code>) hovoria, že chceme zahrnúť súbor ktorý sa nachádza v ceste relatívne od aktuálneho adresára (teda adresára, kde sa nachádza súbor ktorý kompilujeme) | |||
** napr.: <syntaxhighlight lang="c" line="1"> | ** napr.: <syntaxhighlight lang="c" line="1"> | ||
#include <nazov_suboru.h> // Systémový súbor | #include <nazov_suboru.h> // Systémový súbor |
Verzia z 09:00, 1. november 2024
Vysvetlíme si ako funguje programovací jazyk C, v čom sa líši od ostatných programovacích jazykov a aké má využitie. Obsahuje taktiež návod pre inštaláciu kompilátorov pre Windows a Mac.
Charakteristika
Programovací jazyk C je jednoznačne jedným z najpopulárnejších jazykov pre nízkoúrovňové programovanie počítačového softvéru. Má široké uplatnenie pre vývoj aplikácií s grafickým používateľským rozhraním, tvorbu hier, operačných systémov, programovanie logických obvodov alebo dokonca tvorbu iných programovacích jazykov vyššej úrovne (napríklad, štandardný interpreter programovacieho jazyku Python je naprogramovaný v C).
V skratke, C sa používa v situáciách keď potrebujeme mať nad nejakým softvérom čo najväčšiu kontrolu (na najnižšej úrovni) a s najvyššou výpočtovou rýchlosťou, s tým že kód zostane pre programátora ľahko čitateľný (napríklad v porovnaní s jazykom Assembly).
Kompilátor
Pre prácu s jazykom C je potrebné nainštalovať kompilátor. Neexistuje štandardný kompilátor ktorý jazyk C poskytuje, preto je potrebné stiahnuť kompilátor od tretej strany. Najčastejšie sa používajú kompilátory GCC a CLang (pre Linux a Mac OS) a MinGW-w64 (pre Windows)[1].
Kompilátor potom môžeme nastaviť v našom integrovanom vývojovom prostredí, napr. Visual Studio Code, a spúšťať aplikácie priamo tam.
Inštalácia MinGW-w64 (pre 64-bitový operačný systém Windows)
Nasledujúci postup bol adaptovaný z dokumentácie pre Visual Studio Code.
Pre inštaláciu GCC kompilátora použijeme softvér MSYS2, čo je sada nástrojov pre vývoj softvéru ktorý beží na operačnom systéme Windows. Pre nás je užitočný manažér balíkov Pacman, ktorý originálne pochádza od Arch Linuxu (poznámka: nejedná sa o slávnu počítačovú hru). Pre stiahnutie MSYS2 inštalátora klikni tu.
Po stiahnutí a otvorení inštalátora softvér nainštalujeme a spustíme. Otvorí sa nám príkazový riadok, do ktorého vložíme nasledovný príkaz pre nainštalovanie MinGW-w64:
pacman -S --needed base-devel mingw-w64-ucrt-x86_64-toolchain
Stlačíme "Enter
". Po výzve, aby sme uskutočnili výber balíkov ktoré sa majú nainštalovať potvrdíme predvolený výber stlačením klávesy "Enter
":

Posledný krát sa nás terminál spýta, či naozaj chceme nainštalovať vybrané balíky. Na klávesnici napíšeme písmeno "Y
" a potvrdíme tlačením "Enter
". Náš C kompilátor by sa mal konečne úspešne nainštalovať.
Avšak, predtým ako môžeme začať kompilátor používať pre vývoj C programov, musíme pridať jeho binárne súbory do systémovej premennej PATH
, aby ich našiel systémový terminál.

Do vyhľadávacieho poľa systému Windows zadáme "systémové premenné" (ak máme nastavený iný jazyk ako slovenčinu, nastavenie bude pod iným menom).
Následne sa otvorí okno ktoré bude v dolnej časti obsahovať tlačidlo "Premenné prostredia...". Kliknutím na tlačidlo sa otvorí ďalšie okno, ktoré bude v hornej časti zobrazovať premenné pre aktuálneho používateľa, a v časti nižšie budú premenné pre systém.
V hornej časti vyberieme v zozname premennú "Path" a klikneme na tlačidlo "Upraviť". Následne môžeme pridať novú položku kliknutím na tlačidlo "Nové". Ako obsah použijeme priečinok bin
v inštalačnej ceste MinGW-w64 (predvolene to je C:\msys64\ucrt64\bin
).
Pre overenie správnosti nastavenia otvoríme príkazový riadok:

Vložíme doň nasledovné príkazy:
gcc --version
g++ --version
gdb --version
Pokiaľ bude výstup podobný tomuto, nastavenia sú správne a kompilátor je možné používať:

Pokiaľ vidíme namiesto výstupu vyššie nejakú chybu, odpoveď sa môže nachádzať v originálnom návode pre inštaláciu, ktorý som upravil a preložil pre potreby tejto stránky.
Ak všetko funguje, môžeme skúsiť napísať náš prvý C program vo Visual Studio Code.
Prvý program C vo VS Code
Spustíme VS Code, vytvoríme nový projekt a nový súbor s názvom main.c
(môžeme zvoliť ľubovoľný názov súboru, ak obsahuje správne definovanú funkciu main
, spustí sa). Vložíme nasledovný kód, ktorý spustíme (predvolená možnosť je debugovanie, my cez šípku zvolíme iba možnosť "Run"):
#include <stdio.h>
int main() {
printf("ahoj");
return 0;
}

Pri spustení prvého programu nás VS Code vyzve pre výber nášho C kompilátora, čo potvrdíme:

Ak všetko prebehlo správne, mal by sa v spodnej časti zobraziť terminál s nasledujúcim výstupom (v závislosti od rýchlosti počítača môže kompilácia a samotné spustenie trvať dlhšie):

Náš prvý program funguje správne a vypíše do terminálu text "ahoj".
Čo ak sa program automaticky nespustí?
Ak sa na terminál nevypíše nič, pravdepodobne sa vytvoril iba build nášho programu (stáva sa to napríklad na Mac OS). V tom prípade musíme zavolať súbor cez terminál manuálne - po úspešnej kompilácií sa vytvorí v aktuálnom adresáry súbor main
alebo main.exe
(v závislosti od operačného systému). Vložením tejto cesty do terminálu program spustíme (napr.: napíšeme do terminálu ./main
alebo ./main.exe
).
Štruktúra C programu
Teraz keď máme vytvorený základný program, môžeme si vysvetliť ako to funguje. Základnou jednotkou C programu je funkcia. Programovací jazyk C neobsahuje triedy, všetka logika je preto procedurálna - to znamená, že používame funkcie pre hierarchické vykonávanie blokov príkazov a definovanie logiky nášho programu.
Každý skompilovateľný C súbor musí obsahovať funkciu main
. Je to funkcia, ktorá sa pri spustení programu v terminály zavolá vždy ako prvá - je to hlavný vstupný bod do programu. Táto funkcia môže potom volať ďalšie funkcie ktoré boli predtým definované, čo nám umožňuje logiku rozkúskovať na viaceré časti, pre lepšiu čitateľnosť a odstránenie opakujúceho sa kódu.
Hlavičkové súbory
Jednou zo špecifických vlastností jazyka C (a taktiež dôvod, prečo je taký rýchly) je fakt, že základná výbava neobsahuje skoro nič, dokonca ani printf
, čo je v mnohých iných jazykoch samozrejmosťou). Tieto funkcie musíme v našom programe zahrnúť pomocou direktívy preprocesora #include
, ktorý obsahuje cestu k hlavičkovému súboru s deklaráciami funkcií ktoré zahŕňame (špicaté zátvorky hovoria, že kompilátor má hľadať hlavičkový súbor v systémových cestách, napr.: #include <stdio.h>
).
Direktíva #include
nefunguje rovnako ako tradičné importy v iných programovacích jazykoch (kde presúvame menný priestor jedného súboru do druhého, a sprístupníme tak definície ktoré obsahuje). Rozdiel je v tom, že #include
prikáže preprocesoru skopírovať celý obsah súboru ktorý zahŕňame a prilepiť ho namiesto #include
- doslova akoby sme manuálne skopírovali obsah jedného súboru a vložili ho do iného. O túto úpravu, ktorá sa vykoná pred samotnou kompiláciou programu (takzvané predspracovanie zdrojového kódu), sa stará preprocesor (viac o ňom nižšie).
Medzi .h
(hlavičkovými) a .c
(zdrojovými) súbormi nie je funkcionálne odlišný význam (v zmysle, že oba súbory obsahujú platný C kód), preto táto direktíva vo všeobecnosti funguje aj pre zahrnutie .c
súborov (avšak niekedy to môže závisieť od druhu C kompilátora - niektoré povoľujú explicitne iba vkladanie hlavičkových súborov). Z hľadiska konvencie by hlavičkové (.h
) súbory mali obsahovať iba deklarácie funkcií alebo premenných, ktoré môžu byť vložené a používané vo viacerých zdrojových (.c
) súboroch (nemali by obsahovať samotné implementácie konkrétneho kódu, ktorý kompilujeme).
Samozrejme, v našom prípade nám iba stačí vedieť, že direktívy #include
píšeme na začiatku nášho zdrojového kódu (tak, ako tradičné importy v iných programovacích jazykoch) a že nám do kódu vložia funkcionality, ktoré by inak neboli dostupné. Konkrétne prehľady najpoužívanejších hlavičkových súborov sa nachádzajú v tabuľke:
Hlavičkový súbor | Direktíva | Čo pridáva? |
---|---|---|
Standard I/O | #include <stdio.h>
|
Funkcie pre vstupné a výstupné operácie (scanf , printf , fgets , fputc , a podobne).
|
Standard library | #include <stdlib.h>
|
Všeobecne užitočné veci:
|
String | #include <string.h>
|
Funkcie pre manipulovanie s reťazcami (poľami znakov), ako napríklad strcpy , strlen , strcat , strcmp a operácie s pamäťou (memcpy , memset ).
|
Character type | #include <ctype.h>
|
Funkcie pre prácu so znakmi (char ), napr. islower , isupper , tolower , toupper a podobne.
|
Mathematics | #include <math.h>
|
Matematické funkcie, ako napríklad cos , sin , sqrt , pow , exp , log a podobne.
|
Time | #include <time.h>
|
Funkcie pre prácu s časovými a dátumovými údajmi (time , clock , timediff , strftime a podobne).
|
File control | #include <fcntl.h>
|
Funkcie pre prácu so súbormi (open , read , write a flagy, napríklad O_RDONLY ).
|
Assertions | #include <assert.h>
|
Poskytuje makro assert , ktoré sa používa pre kontrolu správnosti údajov a ukončenie programu v prípade, ak sú údaje nesprávne.
|
Standard bool | #include <stdbool.sh>
|
Definuje typ bool s konštantnými hodnotami true alebo false (tieto hodnoty aj tak predstavujú iba štandardnú jednotku a nulu, ale môže sa použiť pre lepšiu čitateľnosť kódu).
|
Funkcia main
Všetky funkcie v jazyku C sú definované nasledovne:
<návratový typ> <identifikátor funkcie>([parametre]) {
// príkazy
return <návratový číselný kód>;
}
Napríklad, funkcia main
je definovaná ako:
int main() {
// nejaký C kód...
return 0;
}
Táto funkcia sa spustí vždy ako prvá pri spustení skompilovaného programu. Jej návratovou hodnotou je číslo, ktoré určuje či sa program vykonal správne alebo nastala chyba. Vychádza to z unixovej konvencie pre číselné návratové kódy, kde nula znamená že kód zbehol v poriadku. Akákoľvek nenulová hodnota predstavuje chybu - o akú chybu sa jedná, to predstavuje samotné číslo[3].
Pre naše vzdelávacie potreby budeme v 99 % prípadov používať return 0;
na konci funkcie main
, v zriedkavých prípadoch môžeme použiť return 1;
(napríklad ak vyhodnotíme, že používateľ zadal nesprávne údaje na vstupe a program preto nemôže pokračovať, konkrétne napríklad nemôžme sčítať dva reťazce ako číslo). Jazyk C nemá presne definovanú konvenciu pre to, čo znamenajú všetky ostatné návratové čísla - záleží to od programátora alebo platformy na ktorej program beží.
Podľa môjho názoru sa aj tak jedná o archeologický nález z čias, kedy ešte terminál nevidel poriadne textové chybové hlásenia a teda programátori museli v tabuľkách hľadať čo znamenajú jednotlivé číselné kódy ak ich program nepracoval tak ako má - dnes sú už, samozrejme, chybové hlásenia oveľa detailnejšie a preto sa nekladie až taký dôraz na to aby sa týmto číselným kódom priraďoval nejaký pevne stanovený význam v rámci konvencie. Ale terminál nejaký kód dodnes napriek tomu vyžaduje, tak všeobecná konvencia je: "ak je všetko v poriadku, vráť nulu - inak vráť niečo nenulové, zvyčajne jednotku" (ktorá predstavuje všeobecnú chybu, môžeš ju doplniť o textové hlásenie s detailami, ale dôležité je aby terminál vedel že nastala nejaká chyba).
Proces kompilácie C programu
Kompilovanie programu v C predstavuje súbor viacerých krokov, ktoré sú vykonávané chronologicky. Výsledkom tejto činnosti je binárny spustiteľný súbor, ktorý obsahuje strojové inštrukcie pre beh programu.
Zdrojový kód
Je to jednoducho C kód, ktorý napíše programátor a uloží do súboru ktorý má obvykle príponu .c
.
Každý riadok príkazu musí obsahovať na konci bodkočiarku (;
), aby kompilátor vedel kedy príkaz končí - s výnimkou direktív preprocesora (začínajúcich s mriežkou, #
), tieto nemusia mať na konci bodkočiarku. Bloky (scopes) sa definujú pomocou množinových zátvoriek ({
, }
), za ktorými taktiež nemusí nasledovať bodkočiarka.
Preprocesor
Ako sme spomenuli vyššie, preprocesor má za úlohu pred kompiláciou vyhľadať v zdrojovom kóde direktívy, ktoré začínajú so znakom mriežky (#
), predspracovať tento kód a potom ho v takto spracovanej podobe podať kompilátoru, ktorý skompiluje kód do finálnej podoby.
Preprocesor pozná nasledovné direktívy:
#include
- vloží obsah iného súboru do aktuálneho súboru;- špicaté (
<
,>
) zátvorky hovoria, že chceme zahrnúť hlavičkový súbor ktorý sa nachádza v systémových cestách (jedná sa o systémové knižnice ktoré sa nainštalovali spolu s kompilátorom) - úvodzovky (
""
) hovoria, že chceme zahrnúť súbor ktorý sa nachádza v ceste relatívne od aktuálneho adresára (teda adresára, kde sa nachádza súbor ktorý kompilujeme) - napr.:
#include <nazov_suboru.h> // Systémový súbor #include "nazov_suboru.h" // Vlastný súbor v aktuálnom adresári
- špicaté (
#define
- definuje makrá, ktoré sú v podstate textové náhrady. Môžu mať podobu jednoduchých funkcií alebo konštantných hodnôt.- Parametre v tele funkcie by mali obsahovať zátvorky, aj keď sa to z matematického hľadiska v zápise nevyžaduje: dôvodom je to, že ak by sme makro definovali ako
#define OBSAH_OBDLZNIKA(a, b) (a * b)
, a použili ho akoOBSAH_OBDLZNIKA(1 + 2, 2 - 1)
, tak potom by výsledkom predspracovania bolo1 + 2 * 2 - 1
a teda priorita operátorov by spôsobila nesprávny výsledok (čo by sa nestalo ak by sme použili klasickú funkciu). - Makrá sú iba textové náhrady, neexistujú v pamäti rovnako ako funkcie (taktiež ako v pamäti neexistujú konštantné makrá ako tradičné premenné).
- napr.:
#define PI 3.14159 // Konštantná hodnota #define OBSAH_OBDLZNIKA(a, b) ((a) * (b)) // Jednoduchá funkcia s parametrami
- Parametre v tele funkcie by mali obsahovať zátvorky, aj keď sa to z matematického hľadiska v zápise nevyžaduje: dôvodom je to, že ak by sme makro definovali ako
#undef
- ruší definíciu makra, čo umožňuje opätovné definovanie makra s rovnakým názvom, ak je to potrebné.- napr.:
#define PI 3.14159 #undef PI #define PI 3.14 // Predefinovanie PI s novou hodnotou
- napr.:
- Podmienené direktívy
#ifdef
,#ifndef
- slúžia pre kontrolu, či sa určitá časť kódu má zahrnúť do programu alebo nie.- Užitočné, ak vyvíjame kód pre viacero platforiem kde potrebujeme dynamicky rozhodnúť, ktoré hlavičkové súbory máme zahrnúť (implementácie niektorých funkcií závisia od operačného systému, pretože ich architektúra je iná - napríklad, niečo čo môžeme spraviť vo Windowse nevieme spraviť v Linuxe a naopak, teda musíme dynamicky rozhodnúť ktoré hlavičkové súbory sa majú vložiť).
- napr.:
#define DEBUG #ifdef DEBUG printf("túto správu uvidíš iba ak definuješ 'DEBUG'\n"); #endif #ifndef DEBUG // ladenie nie je povolené #endif
- Podmienky
#if
,#elif
a#else
- podmienené spracovanie podľa hodnoty výrazu.- napr.:
#define VERSION 2 #if VERSION == 1 printf("Verzia 1\n"); #elif VERSION == 2 printf("Verzia 2\n"); #else printf("Iná verzia\n"); #endif
- napr.:
#error
- vytvorí chybu počas predspracovania, ak je táto direktíva dosiahnuteľná.- napr.:
#ifndef PI #error "PI musí byť definované!" #endif
- napr.:
#pragma
- obsahuje pokyny ktoré nie sú v jazyku C štandardizované a sú špecifické pre rôzne kompilátory.- napr.:
#pragma once // Zabraňuje viacnásobnému zahrnutiu súboru (podobne ako #ifndef ochrany) #include <stdio.h> // V poriadku #include <stdio.h> // Toto bude ignorované, obsah súboru nebude vložený dvakrát
- napr.:
Preprocesor funguje na princípe "Ctrl + C
", "Ctrl + V
". Napr.: v prípade #include
iba nájde príslušný hlavičkový (alebo zdrojový) súbor ktorý sa nachádza na disku a jeho obsah skopíruje namiesto #include
v pôvodnom súbore. Rovnako to funguje aj s makrami a konštantami.