Heap Memory: espace de 16 octets pour une structure de 8 octets

J’utilise le code suivant pour créer et insérer un nouveau nœud dans une liste chaînée, pour ensuite les libérer.

// the node struct node { int data; struct node *next; }; // returns a pointer to a new node with data struct node *new_node(int data) { struct node *node; node = malloc(sizeof(struct node)); node->data = data; node->next = NULL; return node; } // insert node at front of list void push(struct node **head, int data) { struct node *node = new_node(data); node->next = *head; *head = node; } // free the list, deallocate each node's memory void delete_list(struct node** head) { if(*head == NULL) return; struct node *next = NULL; next = (*head)->next; while(next != NULL) { next = (*head)->next; // print address of the memory released printf("Freeing %d\n", (int)*head); free(*head); *head = next; } } 

Maintenant, la structure est de 8 octets sur ma machine ( int 4 octets et pointeur de 4 octets). Maintenant, je suis un peu incertain sur ce qui suit, alors aidez-moi s’il vous plaît:

  1. Lorsque j’appelle push() en séquence, la mémoire est-elle allouée de manière contiguë? Est-ce toujours le cas? Je suppose que cela ne peut pas être, car la mémoire dans le tas peut être fragmentée.

  2. Supposons que la mémoire allouée soit contiguë. Les octets sont-ils espacés de 8 octets puisque la taille de la struct est de 8 octets? Sur ma machine, lorsque j’imprime l’adresse de la mémoire libérée, les adresses de la mémoire imprimées sont séparées de 16 octets à chaque exécution. Pourquoi?
    Freeing 148025480 Freeing 148025464 Freeing 148025448 Freeing 148025432 Freeing 148025416 Freeing 148025400 Freeing 148025384 Freeing 148025368 Freeing 148025352

  3. Maintenant, si la mémoire n’était PAS allouée de manière contiguë pour notre tableau entier (le tas était très fragmenté et la mémoire requirejse était assez grande), et nous avons utilisé une arithmétique de pointeur pour traiter chaque élément du tableau en incrémentant l’adresse de 4 à chaque fois (ou quelle que soit la taille de int ), ne devrions-nous pas nous heurter à une mémoire non réservée par notre programme, qui la jetterait? Ou bien l’environnement d’exécution est-il assez intelligent pour s’en occuper, comme le compilateur ne le peut pas, car il ne sait pas comment la mémoire sera allouée. Est-ce que le système d’exploitation s’en occupe?

Chaque fois que vous appelez new_node , il appelle malloc() .

Vous ne pouvez pas (ou ne devriez pas) prédire où malloc() trouvera votre mémoire. Cela dépend du système d’exploitation et de l’exécution.

En cours d’exécution sur un système d’exploitation donné, dans certaines circonstances, vous remarquerez peut-être que les allocations provenant d’appels consécutifs à malloc() sont contiguës. Cependant, ce comportement peut changer avec la charge, ou avec une mise à jour du kernel, une modification de l’implémentation de libc ou dans toutes sortes de conditions.

Vous pouvez supposer que la quantité de mémoire allouée par un seul appel à malloc() est contiguë (du moins en ce qui concerne les pointeurs vus par votre programme). Votre programme ne doit rien supposer de plus sur la contiguïté.


Si cela vous dérange vraiment, vous pouvez prendre en charge davantage de gestion de la mémoire dans votre propre code – au lieu d’appeler malloc() pour chaque nœud, appelez-le au début et obtenez une plus grande quantité de mémoire. Les appels suivants à new_node peuvent utiliser une partie de ce bloc. Si vous manquez d’espace dans ce bloc, vous pouvez soit malloc() un autre bloc (qui ne sera probablement pas contigu au premier), soit realloc() pour l’étendre (et probablement le déplacer).

Vous constaterez probablement que tout cela rend votre code plus compliqué – et c’est à vous de décider s’il existe des avantages pour y remédier. Les auteurs de la machine virtuelle Java Hotspot font essentiellement ceci: ils malloc() un gros bloc de mémoire au début de l’exécution, puis plutôt que d’appeler malloc() et free() lorsque le programme Java veut de la mémoire, il utilise ses propres routines pour: allouer des parties de ce bloc.

En ce qui concerne le point 2, une des raisons pour lesquelles les appels à malloc ne sont pas exactement contigus peut être une métadonnée: lorsque vous demandez x octets, les implémentations de malloc peuvent allouer un peu de mémoire supplémentaire pour les processus de comptabilité interne (marquage de la taille du bloc, pointeurs vers autres blocs gratuits, etc.). Ainsi, une demande de 8 octets peut en réalité entraîner l’allocation interne de 16 octets, d’où l’espacement de 16 octets entre les atsortingbutions successives.

Si vous allouez de la mémoire en utilisant malloc , la mémoire renvoyée sera toujours contiguë.

Si vous appelez malloc deux fois, rien ne garantit que les deux atsortingbutions seront placées l’une à côté de l’autre, pas même si les deux appels malloc sont situés l’un à côté de l’autre.