web-dev-qa-db-fra.com

explication à la mise en œuvre alignée malloc

Ce ne sont pas des devoirs, cela est purement pour ma propre éducation personnelle.

Je n'arrivais pas à comprendre comment mettre en place un malloc aligné, j'ai donc cherché en ligne et trouvé ce site Web . Pour faciliter la lecture, je posterai le code ci-dessous:

#include <stdlib.h>
#include <stdio.h>

void* aligned_malloc(size_t required_bytes, size_t alignment)
{
    void* p1; // original block
    void** p2; // aligned block
    int offset = alignment - 1 + sizeof(void*);
    if ((p1 = (void*)malloc(required_bytes + offset)) == NULL)
    {
       return NULL;
    }
    p2 = (void**)(((size_t)(p1) + offset) & ~(alignment - 1));
    p2[-1] = p1;
    return p2;
}

void aligned_free(void *p)
{
    free(((void**)p)[-1]);
}

void main (int argc, char *argv[])
{
    char **endptr;
    int *p = aligned_malloc (100, strtol(argv[1], endptr, 10));

    printf ("%s: %p\n", argv[1], p);
    aligned_free (p);
}

La mise en œuvre fonctionne, mais honnêtement, je ne peux pas comprendre comment cela fonctionne.

Voici ce que je ne peux pas comprendre:

  1. Pourquoi avons-nous besoin d'une compensation?
  2. Qu'est-ce que anding with ~(alignment - 1) accomplit
  3. p2 est un double pointeur. Comment se fait-il que nous puissions le renvoyer d'une fonction supposée renvoyer un seul pointeur?
  4. Quelle est l'approche générale pour résoudre ce problème?

Toute aide est vraiment appréciée.

EDIT

Ceci n'est pas une copie de Comment allouer de la mémoire alignée uniquement à l'aide de la bibliothèque standard? parce que j'ai aussi besoin de savoir comment libérer la mémoire alignée.

11
flashburn
  1. Vous avez besoin d'un décalage si vous souhaitez prendre en charge des alignements allant au-delà de la fonction malloc() de votre système. Par exemple, si votre système malloc() s'aligne sur des limites de 8 octets et que vous souhaitez vous aligner sur 16 octets, vous demandez 15 octets supplémentaires afin de savoir avec certitude que vous pouvez déplacer le résultat pour l'aligner comme demandé. Vous ajoutez également sizeof(void*) à la taille passée à malloc() afin de laisser de la place pour la comptabilité.

  2. ~(alignment - 1) est ce qui garantit l'alignement. Par exemple, si alignement est 16, soustrayez 1 pour obtenir 15, alias 0xF, puis le nier donne 0xFF..FF0 qui est le masque dont vous avez besoin pour satisfaire à l'alignement de tout pointeur renvoyé de malloc(). Notez que cette astuce suppose que l'alignement est une puissance de 2 (ce qui serait normalement le cas, mais il devrait y avoir une vérification).

  3. C'est un void**. La fonction retourne void*. Ceci est correct car un pointeur sur void est "Un pointeur sur n'importe quel type" et dans ce cas ce type est void*. En d'autres termes, la conversion de void* vers et à partir d'autres types de pointeurs est autorisée et un pointeur double est toujours un pointeur.

  4. Le schéma général consiste ici à stocker le pointeur d'origine avant celui renvoyé à l'appelant. Certaines implémentations de la norme malloc() font la même chose: cachent les informations de tenue de livres avant le bloc renvoyé. Il est ainsi facile de savoir combien d’espace à récupérer lorsque free() est appelé.

Cela étant dit, ce genre de chose n’est généralement pas utile, car la règle malloc() renvoie le plus grand alignement sur le système. Si vous avez besoin d'un alignement au-delà, il peut y avoir d'autres solutions, notamment des attributs spécifiques au compilateur.

10
John Zwinck

la mise en œuvre fonctionne

Peut-être, mais je ne serais pas trop sûr. OMI, vous feriez mieux de travailler à partir des premiers principes. Dès le départ, 

p1 = (void*)malloc

est un drapeau rouge. malloc renvoie void. En C, n'importe quel pointeur peut être assigné à partir de void *. Lancer à partir de malloc est généralement considéré comme une mauvaise forme, car ses effets ne peuvent être que mauvais. 

Pourquoi nous avons besoin d'une compensation

Le décalage fournit de la place pour cacher le pointeur renvoyé par malloc, utilisé plus tard par free

p1 est extrait de malloc. Plus tard, il doit être fourni à free pour être publié. aligned_malloc réserve sizeof(void*) octets à p1, y cache p1 et renvoie p2 (la première adresse "alignée" du bloc pointé par p1). Plus tard, lorsque l'appelant passe p2 à aligned_free, il convertit p2 en effet en void *p2[] et extrait le p1 d'origine en utilisant -1 comme index. 

Que fait-on avec ~ (alignement - 1) accomplit-il

C'est ce qui met p2 sur la limite. Disons que l'alignement est 16; alignment -1 est 15, 0xF. ~OxF correspond à tous les bits sauf le dernier 4. Pour tout pointeur P, P & ~0xF sera un multiple de 16. 

p2 est un double pointeur.

pointeur schmointer . malloc renvoie void*. C'est un bloc de mémoire. vous l'adressez comme vous voudrez. Vous ne cligneriez pas des yeux 

char **args = calloc(7, sizeof(char*));

allouer un tableau de 7 pointeurs char *, voulez-vous? Le code sélectionne une position "alignée" d'au moins sizeof(void*) octets dans p1 et, aux fins de free, le traite comme void **

Quelle est l'approche générale

Il n'y a pas une réponse. Le mieux est probablement d'utiliser une bibliothèque standard (ou populaire). Si vous construisez au-dessus de malloc, allouer suffisamment pour garder le "vrai" pointeur et renvoyer un pointeur aligné est assez standard, bien que je le code différemment. Syscall mmap renvoie un pointeur aligné sur la page, qui satisfera à la plupart des critères "alignés". Selon les besoins, cela pourrait être meilleur ou pire que le fait de se laisser porter sur malloc

2
James K. Lowden

J'ai quelques problèmes avec ce code. Je les ai compilés dans la liste ci-dessous:

  1. p1 = (void*)malloc Vous ne lancez pas la valeur de retour de malloc.
  2. free(((void**)p)[-1]); Vous ne libérez pas.
  3. if ((p1 = (void*)malloc(required_bytes + offset)) == NULL) Ne placez pas une tâche dans la comparaison d'une instruction if. Je sais que beaucoup de gens le font, mais dans mon esprit, c'est une mauvaise forme qui rend le code plus difficile à lire.

Ce qu'ils font ici, c'est stocker le pointeur d'origine à l'intérieur du bloc alloué. Cela signifie que seul le pointeur aligné est renvoyé à l'utilisateur. Le pointeur réel renvoyé par malloc, l'utilisateur ne le voit jamais. Vous devez cependant garder ce pointeur, car free en a besoin pour dissocier le bloc de la liste allouée et le mettre sur la liste. À la tête de chaque bloc de mémoire, Malloc y insère des informations de gestion. Des choses telles que les pointeurs next/prev, la taille, l'état d'allocation, etc. Certaines versions de mise au point de malloc utilisent des mots de garde pour vérifier si quelque chose a débordé dans la mémoire tampon. L'alignement transmis à la routineDOITêtre une puissance de 2.

Lorsque j'ai écrit ma propre version de malloc pour une utilisation dans un allocateur de mémoire en pool, la taille de bloc minimale utilisée était de 8 octets. Donc, y compris l'en-tête d'un système 32 bits, le total était de 28 octets (20 octets pour l'en-tête). Sur un système 64 bits, il s’agissait de 40 octets (32 octets pour l’en-tête). La plupart des systèmes ont amélioré les performances lorsque les données sont alignées sur une valeur d'adresse (4 ou 8 octets sur les systèmes informatiques modernes). La raison en est que la machine peut saisir le mot entier en un cycle de bus s'il est aligné. Si ce n'est pas le cas, il faut deux cycles de bus pour obtenir le mot entier, puis le construire. C'est pourquoi les compilateurs alignent des variables sur 4 ou 8 octets. Cela signifie que les 2 ou 3 derniers bits du bus d'adresses sont à zéro.

Je sais qu'il existe des contraintes matérielles qui nécessitent un alignement supérieur à celui par défaut de 4 ou 8. Le système CUDA de Nvidia, si je me souviens bien, nécessite des éléments alignés sur 256 octets ... et c'est une exigence matérielle.

Cela a déjà été demandé auparavant. Voir: Comment allouer de la mémoire alignée uniquement à l'aide de la bibliothèque standard?

J'espère que cela t'aides.

0
Daniel Rudy