Search
Podklady přednášek pro aktuální akademický rok 2020/2021. Podklady se skládají z promítaných slidů, které jsou také k dispozici ve zkrácených verzích šetrnějších k případnému tisku, bez přechodů mezi snímky a ve formátu čtyř a devíti snímků na stránku.
Podklady tvoří podpůrný materiál a jejich učelem není nahradit vlastní zápisky z přednášky, které jsou prostředkem osvojení si studované problematiky.
Tématicky je náplň přednášek pokryta v knize Stephen G. Kochan: Programming in C (3rd Edition), Sams Publishing, 2005. Před přednáškou je doporučeno pročíst si odkazované kapitoly.
V přednáškách uvedené zdrojové kódy jsou přiloženy v příslušném .zip archivu. Kromě vyzkoušení programů je též vřele doporučeno si složitější úlohy samostatně naprogramovat a přednáškové příklady využít pro inspiraci.
.zip
— Jan Faigl 2020/09/23 14:32
Dotazy z přednášky
Q: Proč je návratový typ programu (funkce main) int, když maximální hodnota předaná interpretu příkazu pouze 255?
int
Je to dáno historicky. Cčko nelimituje rozsah a definuje int, nicméně zaleží na procesu, který program spouští. V našem případě interpret příkazů. Dokonce je to tak, že GNU bash limituje rozsah na 7-bitů, POSIX shell na 8 bitů a třeba Windows na 32-bit unsigned int. V tomto kontextu stojí za zmínku speciální návratové hodnoty.
Q: Jaká je vnitřní reprezentace typu _Bool?
Norma standardu C uvádí, že dostatečná pro uložení hodnot 0 a 1. Prakticky je to char, tj. jeden byte, viz příklad lec02/bool.c.
char
lec02/bool.c
Q: Jak vytisknu znak '%' nebo '\' ve funkci printf()?
printf()
Jako dvojici znaků, tj.
printf("%% a \\ a následně nový řádek \n");
— Jan Faigl 2017/10/20 08:21 Aktualizace: Oprava překlepů, doplnění informace o unárním operátoru přetypování.
— Jan Faigl 2020/10/09 10:03
Doplnění načítání vstupu
Při načítání vstupu funkcí scanf() lze rozlišit tři případy - úspěšné načtení požadované hodnoty, detekce ukončení vstupu a vstup neodpovídající načítané hodnotě, více viz man 3 scanf.
man 3 scanf
#include <stdio.h> int main(void) { int v; int r; int c = 0; while ((r = scanf("%d", &v)) > 0) { c += 1; } if (r == EOF) { printf("End of file detected, no. of parsed values %i\n", c); } else { printf("Error occur during parsing value no. %i\n", c + 1); } return 0; }
Při interaktivním vstupu z klávesnice lze vstup ukončit kontrolním znakem Ctrl+D (nebo Ctrl+Z na win).
Ctrl+D
Ctrl+Z
— Jan Faigl 2020/10/09 10:12
#define BITS 4 //number of bits to print (4 to make it readable) void print_binary(char *prefix, uint8_t n) { printf(prefix, n); int mask = 1<<(BITS-1); // we need to shift 1 to BITS position, thus -1 for (int i = 0; i < BITS; ++i) { putc((n & mask) ? '1' : '0', stdout); mask = mask >> 1; // } printf("\n"); } uint8_t a = 4; print_binary("a dec: %d bin: ", a);
Q: Jak mohu porovnat dva soubory?
Kromě použítí příkazu diff existují další nástroje, např. https://meldmerge.org/, případně konzolový vimdiff nebo grafický gvimdiff editoru vim, více např. vimdiff-nastroj-drsnaku.
diff
vimdiff
gvimdiff
Q: Proč kompilátor hlásí chybu chybějícího znaku nového řádku na konci souboru?
Při kompilaci programu bez znaku konce řádku na konci souboru, např. při kompilaci clang -pedantic -Wall -Werror main.c, dojde k chybě.
clang -pedantic -Wall -Werror main.c
error: no newline at end of file [-Werror,-Wnewline-eof]
Je do dáno historickým rozhodnutím, že neprázdný zdrojový soubor by měl být zakončem znakem nového řádku. Je tak součástí standardu jazyka.
Dobrý důvod proč je vhodné zakončovat soubory koncem řádku je například při použit vložení souborů příkazem preprocesoru ##include, který nevkládá nový řádek, přestože to mnoho moderních kompilátorů dělá. Stále však platí, že starší kompilátory s tím mohou mít potíže a může to být velmi těžko odhalitelná chyba. V novém C++11, již tento požadevek není, stále však zůstává pro C99.
##include
V případě VS Code, podobně jako v celé řadě jiných programů, lze chování editor nastavit, např. Insert New Line at the End of Files.
Q: Je možné umístit návěští pro příkaz goto před cyklus?
goto
V zásadě ano, jediným zásadním omezením je pouze v rámci jedné funkce. Dále je nutné, aby za návěštím byl příkaz, například definice proměnné není příkaz.
int a; // začátek platnosti a outer: for (int i = 0; i < 3; ++i) { goto outer; }
outer: // definice proměnné není příkaz int a; for (int i = 0; i < 3; ++i) { goto outer; }
outer: // po návěští je příkaz ; int a; for (int i = 0; i < 3; ++i) { goto outer; }
Nicméně podstatnou nevýhodou umístění návěští před cyklus je horší čitelnost a tím také vyšší náchylnost k chybě a zacyklení.
— Jan Faigl 2020/10/21 22:18
Q: Jak inicializovat hodnoty pole při definici?
Při definici proměnné typu pole můžeme rovnou nastavit jednotlivé prvky a využít tzv. designated initializers. V takovém případě jsou incializované všechny prvky. Ty uvedené na zadanou hodnotu, jinak je nastavena výchozí hodnota 0.
int a[6] = {10, 12, [3] = 3, 5}; //pole a má 6 prvků s hodnotami {10, 12, 0, 3, 5, 0}, tj. nespecifkované prvky mají hodnotu 0. int b[] = {1, 2, 3}; // pole má pouze 3 prvky {1, 2, 3} int c[6] = {1, 2, 3}; // pole má 6 prvků { 1, 2, 3, 0, 0, 0} int d[4] = {}; // pole má 4 prvky incializované na 0, tj. {0, 0, 0, 0}
Načítání hodnot celých čísel funkcí scanf() ve smyčce while:
scanf()
while
#include <stdio.h> int main(void) { int v; int i = 0; while (scanf("%d", &v) > 0) { printf("%d %d\n", ++i, v); } printf("No. of read values %d\n", i); return 0; }
Skončil v nekonečné smyčce, neboť původní podmínka cyklu
while (scanf("%d", &v)) { ... }
— Jan Faigl 2020/11/05 11:31
Q: Snažím se načíst celé číslo do typu unsigned int funkcí scanf() a když dám na vstup celé záporné číslo, např. -10, nejen, že se hodnota načte, ale hodnota je veliká. Jak je to možné?
unsigned int
Minimální kód programu read_unsigned.c může být například:
read_unsigned.c
#include <stdio.h> int main(void) { unsigned int i; int r = scanf("%+u", &i); printf("Return value %i read value %u\n", r, i); return 0; }
Výstup pak například
clang read_unsigned.c && echo "-10" | ./a.out Return value 1 read value 4294967286
man scanf
NAME scanf, fscanf, sscanf, vscanf, vsscanf, vfscanf — input format conversion LIBRARY Standard C Library (libc, -lc) ... u Matches an optionally signed decimal integer; the next pointer must be a pointer to unsigned int. ...
“%u”
integer
unsigned long
'-
— Jan Faigl 2020/10/02 23:15
Doplnění k HW06 “přetypování” dynamického pole na statické pole
V domácím úkolu 6 na matice je povinné zadání možné realizovat pouze s využitím proměnných typu pole (VLA). V zásadě nám stačí postupně načíst dvě matice a spočítat výsledek. Matice můžeme načíst tak, že nejdříve načteme rozměry matice a následně alokujeme matici na zásobníku jako dvourozměrné VLA pole. Problém tak dekomponujeme na funkce pro
Celkově se vejde implememntace do cca 150 řádků a méně. Nemusíme řešit alokaci/dealokaci a celkově si procvičíme práci s polem VLA.
Při implementaci volitelného zadání vystačíme se čtyřmi proměnnými pro matice a postupně načítáme matice a co je možné spočítat můžeme rovnou vyčíslit, např. v případě tří matice v součtu můžeme první dvě sečít, v případě násobení můžeme dvě matice rovnou vynásobit. Nicméně zde se nám už může hodit dynamická alokace a funkce pro načtení matice nám přímo alokuje paměť na haldě což je pohodlnější, jen pak nesmím zapomenout na uvolnění paměti voláním free(). Jestliže jsme použili pro povinné zadání VLA pole, nemusíme funkce přepisovat, ale můžeme využít faktu, že pole i vícerozměrné je souvislý blok paměti. Proto pokud alokujeme matici dynamicky jako souvislý blok paměti o velikosti počet řádku krát počet sloupců můžeme využít faktu, že proměnná pole ukazuje na první prvek podobně jako hodnota ukazatele dynamického pole.
free()
void print(int rows, int cols, int m[cols][rows]) { for (int r = 0; r < rows; ++r) { for (int c = 0; c < cols; ++c) { printf("%4d", m[r][c]); } print("\n"); } } ... int *matrix_p = malloc(cols * rows * sizeof(int)); int (*matrix)[cols) = (int (*)[cols]) matrix_p; print(rows, cols, matrix); // s proměnnou matrix můžem pracovat jako s dvojrozměrným polem free(matrix_p);
V případně bonusové úlohy, ale také i volitelného zadání lze s výhodou použít složený typ struct, který bude obsahovat položky definující velikost matice a vlastní data. V tomto případě se dynamické alokaci nevyhneme, proto může být výhodné kromě struktury, implementovat též funkce pro alokaci případně dealokaci. Můžete si též vyzkoušet dynamickou alokaci jako jednorozměrnné pole (souvislý blok paměti) nebo dynamickou alokaci dvourozměrného pole jako pole ukazatelů na dynamické pole hodnot. V tomto případě však nebude matice reprezentována jako souvislý blok paměti a celkově je potřeba také více paměti, tj. rows * sizeof(int*) + rows * cols * sizeof(int), protože pole řádku je pole ukazatelů na int.
struct
rows * sizeof(int*) + rows * cols * sizeof(int)
Q: Je nutné nebo vhodné explicitně typovat ukazatel návratové hodnoty z volání funkce malloc()?
malloc
Vyloženě nutné to v současných verzích Cčka není, přestože pro některé kompilátory (zvláště pak před standarem) to nutné bylo. V současné době je typ void* chápan jako generický ukazatel, jehož přetypování na konktrétní typ ukazatel na proměnné příslušné typu je zřejmé dle typu proměnné a není tak nutné explicitní přetypování uvádět. Jestli je vhodné explicitně přetypovat, tak na to se názory různí. Například v knize S.G.Kochan: Programming in C (3rd Edition), Sams Publishing, 2005 je uváděn malloc vždy s explicitním přetypováním:
void*
int *a = (int*)malloc(10 * sizeof(int));
Naproti tomu v knize K.N. King: C Programming A Modern Approach, Second Edition. W. W. Norton & Company, Inc., 2008 je preferována varianta bez přetypování:
int *b = malloc(10 * sizeof(int));
Obě varianty jsou přípustné, argumenty proti explicitnímu přetypování jsou uváděny například: přehlednější kód a je to zbytečné, neboť dochází k přetypování automaticky. Na druhé straně relativně silné argumenty pro explicitní přetypování uvedené v diskusi http://stackoverflow.com/questions/605845/do-i-cast-the-result-of-malloc jsou například:
You do cast, because:
Je to tak spíše věc osobního vkusu, preferencí, případně používaného kódovacího stylu.
Q: Jak funguje ukazatelová aritmetika v případě void*
Přičítá se hodnota jednoho bajtu, například pro hodnotu ukazatele void *a = 0x100; bude je hodnota (a+1) rovna 0x101.
void *a = 0x100;
(a+1)
— Jan Faigl 2020/10/04 15:13
Umístění errno.h
Chybové kódy standardní knihovny C jsou definovány v souboru errno.h, umístění souboru s definicí chyb je však na různých systémch různé. V unixovových systém se jedná zpravidla o /usr/include/errno.h případně /usr/include/sys/errno.h, popis jednotlivých konstant a hodnot lze také najít např. v man errno. V distribucích Linuxu je situace trošku složitější, neboť umístění záleží na konkrétní distribuci, např. lP5oGIqdWEU
errno.h
/usr/include/errno.h
/usr/include/sys/errno.h
man errno
Konkrétní soubor můžeme najít například příkazem
grep -R 17 /usr/include/**/errno*.h /usr/include/asm-generic/errno-base.h:#define EEXIST 17 /* File exists */ /usr/include/asm-generic/errno.h:#define EUCLEAN 117 /* Structure needs cleaning *
jehož výstup nám napoví, že na tomto konkrétním Linuxu jsou rozděleny chybové kódy do souborů errno-base.h (obsahující základní kódy) a errno.h obsahující rozšířené kódy, tj. kódy s vyšší hodnotou.
errno-base.h
Dále je většinou v Linuxu standardní knihovna libc realizována jako GNU libc (glibc ), což přináší některé zajímavé funkce , které na druhou stranu nejsou dostupné na jiných OS, nelinuxového typu, např. OS X.
GNU libc
Jak úsporně napsat dynamickou alokaci bez dvojího zápisu typu proměnné od Ondřeje T.
int *array = malloc(100 * sizeof *array);
sizeof
*array
— Jan Faigl 2020/10/03 22:40
while (!feof(f) && !exit) { -> while (!exit) { if (g->num_edges == g->capacity) { if (g->num_edges == g->capacity) { enlarge_graph(g); enlarge_graph(g); } } edge_t *e = g->edges + g->num_edges; edge_t *e = g->edges + g->num_edges; while (!feof(f) && g->num_edges < g->capacity) { -> while (g->num_edges < g->capacity) {
feof()
TRUE
— Jan Faigl 2017/12/23 08:09 Aktualizace: Zjednoušení návratové hodnoty funkce dijkstra_solve() a zpřehlednění podmínky v pq_down().
dijkstra_solve()
pq_down()
— Jan Faigl 2018/01/08 08:48 Aktualizace: tdijkstra verze 2.3.6. - fix segfault při pokusu testovat řešení většího grafu než je vstupní graf
— Jan Faigl 2018/01/08 08:48 Aktualizace: tdijkstra verze 2.3.7. - fix návrat 0 při chybě
— Jan Faigl 2020/11/11 09:51
— Jan Faigl 2021/01/12 18:39
Zkouškový test
Např. systémy pro správu verzí * prezentace: b0b36prp-lec14-slides.pdf * zkrácená verze: b0b36prp-lec14-handout.pdf * zkrácená verze 2×2: b0b36prp-lec14-handout-2x2.pdf * zkrácená verze 3×3: b0b36prp-lec14-handout-3x3.pdf
— Jan Faigl 2018/09/22 23:59