Search
— Jan Faigl 2025/10/30 15:10
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ě je implementace do cca 150 řádků a méně. Nemusíme řešit alokaci/dealokaci a procvičíme si práci s polem.
rows * cols
matrix
cols
int
int *matrix_p = malloc(rows * cols * sizeof(int)); int (*matrix)[cols] = (int (*)[cols]) matrix_p; //přetypování
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)
Vývojové prostředí C definuje makra preprocesoru, kterými lze identifkovat o jakou architekturu se jedná, např. x86_64 nebo aarch64, případně i386 nebo arm, ale také linux.
x86_64
aarch64
i386
arm
linux
Příklad použití maker v programu
#include <stdio.h> int main(void) { #ifdef __x86_64__ printf("Běžíme na 64bitové x86_64 architektuře.\n"); #elif defined(__i386__) printf("Běžíme na 32bitové x86 architektuře.\n"); #elif defined(__arm__) printf("Běžíme na 32bitovém ARM.\n"); #elif defined(__aarch64__) printf("Běžíme na 64bitovém ARM.\n"); #else printf("Neznámá architektura.\n"); #endif return 0; }
Velikost bajtu lze v C zjistit z hodnoty makra CHAR_BIT. Typicky je hodnota 8 bitů.
CHAR_BIT
Příklad použití makra CHAR_BIT
#include <stdio.h> #include <limits.h> int main(void) { printf("1 byte = %d bitů\n", CHAR_BIT); return 0; }
První komerční mikroprocesor na světě je Intel 4004, uvedený v roce 1971, ještě před jazykem C. Pracuje s datovou šířkou 4-bity, ale adresní šířka je 12-bitů. Pracuje přímo s 4-bitovými registry. Je příliš omezený a C kompilátor pro něj nevznikl. Nástupcem Intel 4004 byl osmibitový Intel 8008, ve kterém je bajt (C typ char osmibitový).
char
S příchodem jazyka C se ustálil pojem byte (char) jako 8 bitový datový typ, přestože například ASCI kód byl původně 7-bitový (128 hodnot, 0 až 127). Bajt (8 bitů) se ustálil jako nejmenší adresovatelná datová položka.
Z hlediska programování můžeme rozlišit, velikost datových typů (zjistíme například v C operátorem sizeof), počet bitů v bajtu (CHAR_BIT, typicky 8), adresovou šířkou (velikost ukazatele, např. sizeof(void*)) a konkrétní procesor nebo operační systém, např. makra x86_64 nebo linux.
sizeof
sizeof(void*)
Typicky jsou architektury počítačů v násobcích 8. Teoreticky nic nebrání mít i jiné architektury, jsou spíše školní a akademické, např. 5-bitová achitektura variant Little Man Computer.
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*
malloc
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.
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)
V případě reprezentace dle IEEE-754 je strojová přesnost dána rozdílem mezi 1 a dalším reprezentovatelným číslem, která je 2^{-52}, která je v knihovně <float.h> definována jako symbolická konstanta DBL_EPSILON. S využitím např. https://www.h-schmidt.net/FloatConverter/IEEE754.html uvidíme, že hodnota 0.1 je binárně reprezentovaná jako 0-01111011-10011001100110011001101 (znaménko 1 bit, exponent 8 bitů a mantisa 23 bitů), což odpovídá nejbližšímu reprezentovatelnénu číslu 0.100000001490116119384765625. Chyba reprezentace je tak 1.490116119384765625E-9. Například v případě hodnoty 0.15 je chyba reprezentace 5.9604644775390625E-9, takže chyba reprezentace 0.1 není zas tak zásadně velká. Spíše může být překvapivé, proč číslo 0.1 má takovou chybu, když reprezentujeme hodnotu mantisou a exponentem. Důvod hledejme v reprezentaci v soustavě o základu 2. Binární reprezentaci hodnoty 0.1 najdeme dělením 2, jenže brzo zjistíme, že v rozvoji se opakuje 0011, takže přesnost reprezentace je dána počtem bitů v mantise. Proto 0.1 bude reprezentovano jinak v případně float než double. Obecně tak při přetypování dojde k chybě a proto není příliš moudré spoléhat na porovnání hodnot necelých čísel operátorem ==. Na druhou stranu, pokud do jedné proměnné double a do druhé hodnoty double přiřadíme stejné číslo, tak pochopitelně bude binární reprezentace identická a operátor == zafunguje podle očekávání. Více o reprezentaci číselných hodnot čekejte v předmětu APO.
<float.h>
DBL_EPSILON
0-01111011-10011001100110011001101
0.100000001490116119384765625
1.490116119384765625E-9
5.9604644775390625E-9
0.1
float
double
==
Převod 0.1 do dvojkové soustavy (desetiná čísla dělíme 2^{-i} kde i je desetinná část což odpovídá násobení 2). 0.1 * 2 = 0.2 -> 0 0.2 * 2 = 0.4 -> 0 0.4 * 2 = 0.8 -> 0 0.8 * 2 = 1.6 -> 1 0.6 * 2 = 1.2 -> 1 0.2 * 2 = 0.4 -> 0 0.4 * 2 = 0.8 -> 0 0.8 * 2 = 1.6 -> 1 0.6 * 2 = 1.2 -> 1 0.2 * 2 = 0.4 -> 0 ...