{{indexmenu_n>10}} ====== 10 - Binární soubory ====== * [[courses:b0b36prp:internal:tutorialinstruction:11|pro vyučující]] ===== Cíle cvičení ===== * Ukládaní a načítání binárních souborů. * Serializace. ==== Materiály ==== /* * {{ :courses:b0b36prp:labs:lab11-materials.zip | Materiály ke cvičení }} */ * Funkce: FILE *fopen(const char *path, const char *mode); size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream); int fclose( FILE *stream ); * Kód ke cvičení ++++ book.h | #ifndef __BOOK_H__ #define __BOOK_H__ #include #define MAX_NAME_SIZE 19 struct Book { int year; char title[MAX_NAME_SIZE]; char* abstract; }; typedef struct Book Book; struct BookList { Book** books; size_t size; }; typedef struct BookList BookList; Book* create_book(int year, const char* title, const char* abstract); void destroy_book(Book* b); void print_book(Book* b); BookList* create_book_list(); void destroy_book_list(BookList* bl); void add_book(BookList* bl, Book* b); void print_book_list(BookList* bl); #endif ++++ ++++ book.c | #include #include #include #include "book.h" Book* create_book(int year, const char* title, const char* abstract) { Book* p = (Book*)malloc(sizeof(Book)); if (p == NULL) { printf("Error: malloc failed in create_book\n"); return NULL; } p->year = year; for (size_t i = 0; i < MAX_NAME_SIZE; i++) { p->title[i] = '\0'; // erase "dangerous" leftover data } for (size_t i = 0; i < MAX_NAME_SIZE-1; i++) { p->title[i] = title[i]; if (title[i] == '\0') { break; } } if (abstract == NULL) { p->abstract = NULL; } else { p->abstract = (char*)malloc(strlen(abstract) + 1); if (p->abstract == NULL) { printf("Error: malloc abstract failed in create_book\n"); free(p); return NULL; } strcpy(p->abstract, abstract); } return p; } void destroy_book(Book* p) { free(p->abstract); free(p); } void print_book(Book* p) { printf("Title: %s, Publication year: %d, Abstract: %s\n", p->title, p->year, p->abstract); } BookList* create_book_list() { BookList* pl = (BookList*)malloc(sizeof(BookList)); if (pl == NULL) { printf("Error: malloc failed in create_book_list\n"); return NULL; } pl->books = NULL; pl->size = 0; return pl; } void add_book(BookList* pl, Book* p) { Book** people_ = (Book**) realloc(pl->books, (pl->size + 1) * sizeof(Book*)); if (people_ == NULL) { printf("Warning: Adding failed, no modification is being done.\n"); return; } pl->books = people_; (pl->books)[pl->size] = p; pl->size++; } void destroy_book_list(BookList* pl) { for (size_t i = 0; i < pl->size; i++) { destroy_book(pl->books[i]); pl->books[i] = NULL; } free(pl->books); free(pl); } void print_book_list(BookList* pl) { for (size_t i = 0; i < pl->size; i++) { printf("Book %zu:\n", i); print_book(pl->books[i]); } } ++++ ++++ main.c | #include #include #include #include "book.h" BookList* get_example_list(); void test_list_contents(); int main() { test_list_contents(); return 0; } BookList* get_example_list() { BookList* pl = create_book_list(); Book* p1 = create_book(2005, "Programming in C", "Programming in C will teach you how to write programs in the C programming language."); Book* p2 = create_book(161, "Meditations", NULL); Book* p3 = create_book(-1500, "Poor Man of Nipur", ""); add_book(pl, p1); add_book(pl, p2); add_book(pl, p3); return pl; } void test_list_contents() { BookList* pl = get_example_list(); print_book_list(pl); destroy_book_list(pl); } ++++ ++++ run | $ gcc -Wall -pedantic -O3 -g main.c book.c -o app $ ./app Book 0: Title: Programming in C, Publication year: 2005, Abstract: Programming in C will teach you how to write programs in the C programming language. Book 1: Title: Meditations, Publication year: 161, Abstract: (null) Book 2: Title: Poor Man of Nipur, Publication year: -1500, Abstract: ++++ ===== Z operační paměti do souboru ===== Poskytnutá knihovna pracuje s kolekcí struktur ''Book'', která uchovává informace o jednotlivých svazcích. Naším úkolem je tuto kolekci uložit do (a načíst z) binárního souboru. Z paměťového hlediska chceme překopírovat bajty rozmístěné v operační paměti do sekvence (stream) bajtů reprezentované [[https://en.cppreference.com/w/c/io/FILE|FILE]]. ==== Úkoly - Struktury a Stream bajtů ==== * Načtěte ze vstupu seznam čísel a uložte je do binárního souboru. Poté soubor načtěte a vypište seznam čísel. Prohlédněte si uložený soubor programem ''hexdump -C'' nebo otevřením ve VScode (vyberte Hex Editor). * Naplňte strukturu ''Book'', uložte ji jako celek do binárního souboru a prohlédněte si obsah uloženého souboru. ++ Hint | BookList *pl = get_example_list(); Book *p = pl->books[0]; fwrite(p, sizeof(Book), 1, f); ++ * Zodpovězte si na následující otázky: * Ze souboru lze vyčíst hodnotu ''.title'' ale ''.abstract'' je nečitelná. Jak to? ++ Hint | //Datový typ atributu ''.title'' je jiný než u ''.abstract''.// ++ * V souboru rozlište části reprezentující hodnoty atributů ''.year'', ''.title'' a ''.abstract'', odpovídá počet zapsaných bytů součtu velikostí jednotlivých atributů? ++ Hint | //Viz manual [[https://www.gnu.org/software/c-intro-and-ref/manual/html_node/Structure-Layout.html|Structure Layout]]. Na následující stránce [[https://www.gnu.org/software/c-intro-and-ref/manual/html_node/Packed-Structures.html|Packed Structure]] je způsob jak struct učinit kompaktní, vyzkoušejte si jej a porovnejte výsledný soubor s předchozím.// ++ * Místo ukládání struktury jako celku, uložte hodnoty jejích atributů postupně v sérii. * Zařiďte ukládání řetězce ''.abstract'', tak aby ji bylo možné načíst a přečíst. Uložení a načtení by mělo uchovat informaci zda-li ''.abstract=""'' či ''.abstract=NULL''. Řetězec může být libovolné délky. * Soubor zas načtěte do struktury a pro ověření vypište. * Diskutujte praktické rozdíly s ukládáním struktury jako celku. ==== Úkoly - De/Serializace dynamicky alokovaných struktur ==== Použijte nově nabité znalosti k implementaci knihovny: ++++ book_serializer.h | #ifndef __BOOK_SERIALIZER_H__ #define __BOOK_SERIALIZER_H__ #include #include "book.h" void serialize_book(FILE* f, Book* p); Book* deserialize_book(FILE* f); void serialize_book_list(FILE* f, BookList* pl); BookList* deserialize_book_list(FILE* f); #endif ++++ - Implementujte nejdřív ''serialize_book'' a ''deserialize_book''. Podobně jako u předpřipravené funkce ''test_list_contents()'', vytvořte si v ''main.c'' další testovací funkce pro serializaci a deserializaci. - Funkce na serializaci jedné knihy a funkce z rozhraní ''book.h'' pak použijte v ''serialize_book_list'' a ''deserialize_book_list''. ===== Pokročilé úlohy - Závislost na platformě ===== V [[https://www.gnu.org/software/c-intro-and-ref/manual/html_node/Structure-Layout.html|Structure Layout]] se dočteme, že rozložení dat ve struktuře závisí na lokální platformě (typicky na HW architektuře (např. x86-64) a operačním systému). Ve struktuře mohou být vloženy úseky nevyužitého místa (padding), které zajistí, že jednotlivé prvky se nachází na tzv. zarovnaných adresách (dělitelných specifickou konstantou). Lišit se také může lišit pořadí bytů celočíselných hodnot (tzv, [[https://cs.wikipedia.org/wiki/Endianita|endianita]]). Pokud budeme přesouvat data mezi různými platformami, musíme se "dohodnout" na společné reprezentaci, se kterou budou seznámeny obě strany. Pro komunikaci přes síť (např. v [[https://www.rfc-editor.org/rfc/rfc1700|IP]]) bylo bajtové pořadí má být big-endian (tedy platforma která přijímá data ze sítě očekává big-endian). Počítače v laboratoři naopak používají little endian (tedy obrácené pořadí). === Úkolu === * Naprogramujte funkce ''le_to_be'', která převede integer z little endian na big endian. (Pro uint32_t budete potřebovat ''stdint.h''.) uint32_t le_to_be(uint32_t v); * Vytvořte funkci ''uint32_t host_to_net32(uint32_t v)'', která bude převolávat ''le_to_be'' pokud hostovská platforma (zařízení na kterém je program spuštěn) používá little endian jako pořadí bajtů. * Řešení může být přes makro nebo lze endianitu zjišťovat dynamicky za běhu. * Funkčnost můžete otestovat ++ porovnáním s | uint32_t htonl(uint32_t hostlong); . ++ * Nyní uložte a načtěte strukturu ''Book'' s možným přepínáním endianity. * Diskutujte, jak uložit hodnoty typu ''float'' nezávisle na architektuře. /* RS: Reorganizovano ===== Úkoly ===== * Načtěte ze vstupu seznam čísel a uložte je do binárního souboru. Poté soubor načtěte a vypište seznam čísel. Prohlédněte si uložený soubor programem ''xxd'' nebo ''hexdump -C''. * Naplňte strukturu ''test'', uložte ji do binárního souboru, binární soubor načtěte do struktury a její obsah vypište. struct test { int ival; char cval; }; typedef struct test test_t; * Totéž udělejte se strukturou ''test2'', do které uložíte i pointer na řetězec. Zamyslete se, jak správně uložit řetězec v položce ''.str'', jak ho správně načíst a jak rozlišit, zda ''.str'' obsahuje NULL nebo odkaz na prázdný řetězec. struct test2 { int ival; char cval; char *str; }; typedef struct test2 test2_t; * Udělejte totéž s polem struktur ''test'' a ''test2''. * Jak to bude, pokud použijete atribute ''packed''? Jak se změní velikost struktury a proč? Musíte něco změnit ve vašem kódu? Jak ukládání a načítání naprogramovat, aby byl nezávislý na tomto atributu? __attribute__((packed)) * Naprogramujte funkce ''le_to_be()'', která převede integer z little endian na big endian. (Pro uint32_t budete potřebovat ''stdint.h''.) uint32_t le_to_be(uint32_t v); * Nyní uložte a načtěte struktury ''test'' a ''test2'' nezávisle na architektuře. Diskutujte limity vašeho řešení. * Diskutujte, jak uložit hodnoty typu ''float'' nezávisle na architektuře. */ /* RS: zamereni predevsim na praci se soubory ---- * Načítejte čísla ze stdin a postupně je ukládejte do BST. Nakonec vyhledejte některá čísla a vypište, kolik kroků bylo potřeba k vyhledání. * Výsledný BST pravděpodobně není vyvážený. Jak upravíte načítání, aby byl strom vyvážený? * Pokud jste použili strukturu z přednášky, napadá vás, jak tuto úlohu vyřešit úsporněji bez pointerů ''.left'' a ''.right''? */