Search
Podklady přednášek pro aktuální akademický rok 2023/2024. 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é slouží také jako prostředek 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 nebo novější vydání. Před přednáškou je doporučeno pročíst si odkazované kapitoly. Alternativně je doporučena učebnice jazyka C K.N. King: C Programming A Modern Approach, Second Edition. W. W. Norton & Company, Inc. 2008 nebo novější.
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 2024/03/25 06:50
2020: Na přednášce zmíněná funkce fgets() pro řešení načítání celého řádku nemusí být zas až tak výhodná a mnohem výhodnější může být využití funkce getline(), která však pracuje pouze s dynamicky alokovanou pamětí.
fgets()
getline()
Příklad z přednášky
Příklad na přednášce demonstrující použití kompilace s debug, např. clang -g a valgrind pro rychlé nalezení problémového místa nám pomohl ukázat na zápis mimo přidělenou paměť na řádku 13, tj. sieve[i*j] = 0;
clang -g
valgrind
sieve[i*j] = 0;
10 for (int i = 2; i < 1000000; ++i) { 11 if (sieve[i]) { 12 for (int j = i; i*j < 1000000; ++j) { 13 sieve[i*j] = 0; 14 } 15 } 16 }
Přestože řádek vypadá relativně neškodně, je nutné uvažovat jakou hodnotu bude mít výraz i*j pokud jsou obě proměnné typu int. Zcela jistě se totiž hodnota výrazu, např. 47000 * 47000, do rozsahu typu int reprezentovaného 32-bity nevejde. Možných řešení je více, jedno z nich může být využít automatické typové konverze na větší z typů, např.
i*j
int
10 for (int i = 2; i < 1000000; ++i) { 11 if (sieve[i]) { 12 for (long j = i; i*j < 1000000; ++j) { 13 sieve[i*j] = 0; 14 } 15 } 16 }
Rozdíl je na řádku 12 v použití typu long pro řídicí proměnnou j. V tomto případě je hodnota výrazu i*j typu long a problém je vyřešen.
long
j
Každopádně mnohonásobné použití magické konstanty 1000000 lze vhodně nahradit např.
1000000
#define SIEVE_SIZE 1000000
Dotazy z přednášky
Q: Co udělá realloc() při zmenšení velikost nebo nastavení velikosti na 0?
realloc()
Zmenší alokované místo a v případě 0, např. realloc(ptr, 0); uvolní paměť a odpovídá tak volání free(ptr);.
realloc(ptr, 0);
free(ptr);
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.
— Jan Faigl 2024/02/11 13:55
— Jan Faigl 2024/03/25 06:06
Q: Jakým způsobem mohou přetypovat dynamicky alokovaný souvislý blok paměti na dvourozměrné pole o cols sloupcích?
cols
Předně je nutné zajistit, že ukazatel odkazuje na souvislý blok paměti o příslušné velikost. Následně lze definovat proměnnou se specifikací počtu sloupců např. int(*matrix)[cols] = (int(*)[cols])m;, kde m je ukazatel na souvislý blok int hodnot. V kontextu programu to může vypadat, následovně.
int(*matrix)[cols] = (int(*)[cols])m;
m
void *alloc(size_t size) { void *ret = malloc(size); if (ret == NULL) { fprintf(stderr, "Malloc fail!"); exit(-1); } return ret; } void print_matrix(int rows, int cols, int m[][cols]) { for (int r = 0; r < rows; ++r) { for (int c = 0; c < cols; ++c) { printf("%2d ", m[r][c]); } printf("\n"); } } int rows = 2; int cols = 3; int *m = alloc(rows * cols * sizeof(int)); for (int i = 0; i < rows * cols; ++i) { m[i] = i + 1; } int(*matrix)[cols] = (int(*)[cols])m; print_matrix(rows, cols, matrix); free(m);
Můžeme toho například využít při řešení HW05, kdy si nejdříve odladíme funkce pro dvojrozměrné pole, které následně můžeme přímo využít pro rozšířenou implementaci s využitím dynamické alokace.
Q: Je nutné v případě dynamicky alokované matice, jako pole ukazatelů na pole hodnot, postupně paměť uvolňovat podobně jako při alokaci?
Ano je. Nejdříve je nutné postupně uvolnit dílčí pole reprezentující sloupce v jednotlivých řádcích a následně pak uvolnit pole ukazatelů (na řádky). S výhodou je možné si definovat funkce pro alokaci a uvolnění paměti, např.
void *alloc(size_t size) { void *ret = malloc(size); if (ret == NULL) { fprintf(stderr, "Malloc fail!"); exit(-1); } return ret; } int **matrix_alloc(int rows, int cols) { int **matrix = alloc(rows * sizeof(int *)); for (int r = 0; r < rows; ++r) { matrix[r] = alloc(cols * sizeof(int)); } return matrix; } void matrix_free(int rows, int cols, int **matrix) { if (matrix) { for (int r = 0; r < rows; ++r) { if (matrix[r]) { free(matrix[r]); } } free(matrix); } }
const int ROWS = 2; const int COLS = 2; int **matrix = matrix_alloc(ROWS, COLS); matrix_free(ROWS, COLS, matrix);
Pragmatičtější však může být použití složeného typu struct.
struct
— Jan Faigl 2024/03/26 21:54
Q: Jak funguje Quick-Sort?
Jedná se o algoritmus řazení na principu rozděl a panuj, který je například popsán na https://cs.wikipedia.org/wiki/Quicksort nebo znázorněn na Quick-sort with Hungarian (Küküllőmenti legényes) folk dance.
— Jan Faigl 2024/04/01 18:11
— Jan Faigl 2024/03/06 08:28
— Jan Faigl 2024/04/13 15:55
Q: Proč při nastavení terminálu do raw režimu nedochází k tisku nového řádku na začátku?
Při nastavení raw režimu cfmakeraw() je vypnuto zpracování výstupních znaků (output postprocessing), které automaticky nahrazuje konec řádku za “návrat vozíku a nový řádek” (NL na CR-NL). Vyřešit lze buď explicitním uvedením znaku '\r', což ale není příliš kompatibilní nebo povolením output processing (OPOST) např.
cfmakeraw()
static struct termios tio; tcgetattr(STDIN_FILENO, &tio); cfmakeraw(&tio); // nastavení raw režimu viz man termios tio.c_oflag |= OPOST; //zapnutí output processing tcsetattr(STDIN_FILENO, TCSANOW, &tio);
— Jan Faigl 2024/04/13 19:48
Dotazy / připomínky z přednášky
C: Při přepnutí terminálu do raw režimu příkazem stty raw stále dochází k tisku stisknuté klávesy
stty raw
Pokud tomu tak je (např. v Linuxu), je nutné ještě vypnout echo například stty -noecho příkaz pro nastavení termínálu tak může být
stty -noecho
stty raw -echo
stty -raw echo
— Jan Faigl 2024/04/30 15:34
Q: Jak C$+ +$ řešení vícenásobnou dědičnost v tzv. diamond probému?
K tomuto problému dochází při vycenásobné dědičnosti, kdy je třída odvozena od dvou tříd se společným předkem. Použitím dynamické vazby (virtual) jsou vlastnosti předka pouze jednou, v opačném případě může dojít k nejednoznačnosti, vlastnosti předka z jedné a z druhé třídy. https://www.makeuseof.com/what-is-diamond-problem-in-cpp/
virtual
— Jan Faigl 2024/05/07 23:47
— Jan Faigl 2024/05/06 23:43