Search
malloc
realloc
read_lines()
free
'\n
\n
struct
Předpokládáme, že máme implementované funkce pro tisk a načtení jednoho řádku.
int print(char *str); //return error value char* read(FILE *fd, int *error);
enum { ERROR_OK = EXIT_SUCCESS, ERROR_IN = 100, ERROR_MEM = 101, ERROR_OUT = 102, };
Dále předpokládáme návrhy funkcí pro tisk a načtení řádků.
int print_lines(size_t n, char **str); char** read_lines(FILE *fd, size_t *n, int *error); void free_lines(size_t n, char ***str);
free_lines
NULL
Příklad implementace free_lines()
void free_lines(size_t n, char ***str) { if (str && *str) { for (int i = 0; i < n; ++i) { free((*str)[i]); } free(*str); } *str = NULL; }
man 3 rand
init
print
permute
void init(unsigned int n, unsigned int a[n]); void print(unsigned int n, unsigned int a[n]); void permute(unsigned int n, unsigned int a[n]);
stdout
#include <stdio.h> #include <stdlib.h> #ifndef MAX_NUM #define MAX_NUM 10 #endif void init(unsigned int n, unsigned int a[n]); void print(unsigned int n, unsigned int a[n]); void permute(unsigned int n, unsigned int a[n]); int main(void) { int ret = EXIT_SUCCESS; const unsigned int n = MAX_NUM; unsigned int a[MAX_NUM]; init(n, a); print(n, a); permute(n, a); printf("Randomized array\n"); print(n, a); return 0; }
S výstupem např.
clang -DMAX_NUM=5 perm.c -o perm && ./perm 0 1 2 3 4 Randomized array 3 0 4 2 1
int main(void) { int ret = EXIT_SUCCESS; size_t n = 0; char **lines = read_lines(&n); unsigned int a[n]; // Počet řádku se musí vejít do paměti (zásobníku) init(n, a); permute(n, a); for (int i = 0; i < n; ++i) { print(lines[a[i]]); } free_lines(n, &lines); return ret; }
Naše výchozí implementace funkce main může vypadat například následovně.
main
int main(void) { int ret = ERROR_OK; size_t n = 0; char **lines = read_lines(&n, &ret); unsigned int a[n]; init(n, a); permute(n, a); for (int i = 0; i < n && ret == ERROR_OK; ++i) { ret = print(lines[a[i]]); } free_lines(n, &lines); return ret; }
Základní strategie návrhu funkce read_lines() vychází z funkce read, kde jsme postupně načítali znaky a v případě potřeby rozšiřovali paměť voláním realloc. Při načítání řádků postupujeme podobně, jen místo volání getchar() budeme volat funkci read(), která může skončit chybou, kdy vrací hodnotu NULL a indikaci chyby v proměnné error. Rámcově tak implementace funkce může vypadat následovně.
read
getchar()
read()
error
char** read_lines(size_t *n, int *error) { sizet_t size = INIT_SIZE; //Počáteční velikost dynamicky alokovaného pole ukazatelů na jednotlivé řádky. char **lines = malloc(...); *n = 0; *error = ERROR_OK; //Předpokládáme, že vše proběhne v pořádku, případné chyby budeme indikovat přepsání hodnoty. if (lines) { char *str; // Ukazatel na načtený řádek while ((str = read(error))) { if (*n == size) { ... //Re-alokace paměti, nebo opuštění cyklu (případně uvolnění paměti a nastavení příznaku chyby } lines[*n] = str; //Řádek se provede pouze pokud je dostatečné místo v lines. *n = *n + 1; } ... /// Ukončení načítání a případně uvolnění paměti pokud nastala chyba načítání } else { *error = ERROR_MEM; //První malloc se nezdařil. Proměnná lines je NULL. } return lines;
V programu budeme dále uvažovat, že počet řádků může být výrazně vyšší než je velikost zásobníku pro uložení permutace celých čísel (např. v Linuxu je zásobník typicky 8 MB), proto výchozí implementaci s VLA nahradíme dynamickou alokací.
unsigned int
int main(void) { int ret = ERROR_OK; size_t n = 0; char **lines = read_lines(&n, &ret); unsigned int *a = malloc(n * sizeof(unsigned int)); if (a) { init(n, a); permute(n, a); for (int i = 0; i < n && ret == ERROR_OK; ++i) { ret = print(lines[a[i]]); } free(a); } else { ret = ERROR_MEM; } free_lines(n, &lines); return ret; }
Program opět můžeme otestovat při definování omezené paměti.
clang -c rand.c -o rand.lnx ulimit -v 5000 ./rand.lnx <in-long.txt; echo $? 101
Při dynamické re-alokaci jsme využili relativně přímočarou strategii rozšiřování alokované paměti na dvojnásobek předchozí hodnoty. Určitě lze zvolit jinou strategii, případně nemusíme řešit, pokud jsou alokovaná pole (řádky) relativně malé. Přesto můžeme na závěr načtení řádku paměť redukovat, podobně jako pole ukazatelů na dynamicky alokované řetězce. Při návrhu funkce můžeme uvažovat, že takovou funkci použijeme jak pro řádky, tak pro pole ukazatelů. Pokusíme se proto funkci reduce() navrhnout obecně.
reduce()
void *realloc(void *ptr, size_t size);
která v případě, že požadavek na paměť size je menší než aktuálně alokovaná paměť na adrese ptr, paměť uvolní, tj. odřízne konec.
size
ptr
reduce
exit()
stderr
man realloc
void*
capacity
void* reduce(size_t size, void *ptr);
“ERROR: Calling reduce() failed!”
void* reduce(size_t size, void *ptr) { void *ret = NULL; if (size > 0 && ptr) { // ret = realloc(ptr, size); } if (!ret) { fprintf(stderr, "ERROR: Calling reduce() failed!\n"); exit(ERROR_MEM); } return ret; // Because of if (!t), it always return non-NULL. }
int *up, **uup, array[] = {5, -6, 0, 8, -9, 3, 1, -4}; up = array; uup = &up; printf("array[1] = %d \n", array[1]); printf("array[1] + 4 = %d \n", array[1] + 4); printf("(array + 1)[2] = %d \n", (array + 1)[2]); printf("*up = %d \n", *up); printf("*up + 4 = %d \n", *up + 4); printf("*(up + 1) = %d \n", *(up + 1)); printf("**uup = %2d \n", **uup); printf("*(*uup + 2) = %2d \n", *(*uup + 2)); printf("**uup + 4 = %2d \n", **uup + 4);