#include #include #include #include #include // Comme uint64_t est un peu pénible à taper, on utilise // un typedef : typedef uint64_t ui; #define HEAP_SIZE 32 ui heap[HEAP_SIZE]; // Cette fonction convertit un pointeur (qui doit être issu de // malloc_ui) en un indice dans le tableau heap. // Vous en aurez besoin pour écrire les différentes versions // de free_ui (juste un appel au début, ensuite on ne manipule plus // que des indices), mais il est complètement normal de ne pas // comprendre comment elle fonctionne : c'est de l'arithmétique des // pointeurs, qui est hors programme. ui heap_index(ui *p) { return p - heap; } // Cette fonction initialise le tas à une valeur particulière, que // vous avez peu de chance d'utiliser par hasard. Cela nous // permettra en pratique de repérer les cases dont la valeur n'a // jamais été modifiée quand on affiche le contenu du tas. // Elle est destinée à être appelée une unique fois, tout au début // de l'exécution du programme. void pre_initialize_heap(void) { for (ui i = 0; i < HEAP_SIZE; i++) { heap[i] = 0xFFFFFFFF; } } // La fonction suivante affiche le contenu du tas. Les cases // identifiées comme n'ayant jamais été modifiées sont affichées // de manière particulière. void print_heap(void) { for (ui i = 0; i < HEAP_SIZE; i++) { ui x = heap[i]; if (x == 0xFFFFFFFF) { printf("... "); } else { printf("%3" PRIu64 " ", x); } } printf("\n"); } void set_memory(ui *p, ui size, ui value) { for (ui i = 0; i < size; i++) { p[i] = value; } } const uint64_t prologue = 2; bool is_free(uint64_t i) { return heap[i - 1] % 2 == 0; } uint64_t read_size(uint64_t i) { return heap[i - 1] - (heap[i - 1] & 0b1); } void set_free(uint64_t i, uint64_t size) { size += ((size & 0b1) << 1); heap[i - 1] = size - (size & 0b1); heap[i + size] = size - (size & 0b1); } void set_used(uint64_t i, uint64_t size) { size += ((size & 0b1) << 1); heap[i - 1] = size | 0b1; heap[i + size] = size | 0b1; } // À ne pas appeler sur l'épilogue uint64_t next(uint64_t i) { return i + read_size(i) + 2; } // À ne pas appeler sur le prologue uint64_t previous(uint64_t i) { return i - (heap[i - 2] - (heap[i - 2] % 2)) - 2; } void init_heap(void) { heap[0] = 4; heap[1] = 1; heap[2] = 1; heap[3] = 1; heap[4] = 1; } // Complexité : O(n) uint64_t *malloc_ui64(uint64_t size) { if (size == 0) { return NULL; } size += ((size & 0b1) << 1) - (size & 0b1); ui i = 2; while (i < heap[0]) { ui rs = read_size(i); if (is_free(i) && rs >= size) { set_used(i, size); if (rs >= size + 2) { set_free(i + size + 2, rs - size - 2); } return &heap[i]; } else { i = next(i); } } if (i + size + 2 >= HEAP_SIZE) { return NULL; } else { set_used(i, size); set_used(i + size + 2, 0); heap[0] = i + size + 2; return &heap[i]; } } // Complexité : O(1) void free_ui64(uint64_t *p) { uint64_t i = heap_index(p); ui size = read_size(i); ui pre = previous(i); ui nex = next(i); if (is_free(pre)) { i = pre; size += read_size(pre) + 2; } if (is_free(nex)) { size += read_size(nex) + 2; } if (nex == heap[0]) { set_used(i, 0); heap[0] = i; } else { set_free(i, size); } } // Q13 : Pour qu'une allocations future n'ai pas à additionner les capacités // pour savoir si elle peut allouer une grosse zone // Q14 : first fit est plus efficace en temps, best fit est plus efficace en // espace. int main(void) { pre_initialize_heap(); // Pour tester, une fois que les fonctions sont implémentées init_heap(); ui *p = malloc_ui64(8); set_memory(p, 8, 33); ui *q = malloc_ui64(2); set_memory(q, 2, 44); free_ui64(p); ui *r = malloc_ui64(7); set_memory(r, 7, 55); print_heap(); free_ui64(r); free_ui64(q); uint64_t *p1 = malloc_ui64(6); uint64_t *p2 = malloc_ui64(7); uint64_t *p3 = malloc_ui64(1); set_memory(p1, 6, 42); set_memory(p2, 7, 52); set_memory(p3, 1, 62); print_heap(); free_ui64(p2); print_heap(); uint64_t *p4 = malloc_ui64(2); set_memory(p4, 2, 72); print_heap(); free_ui64(p3); print_heap(); return EXIT_SUCCESS; }