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:
~(alignment - 1)
accomplitp2
est un double pointeur. Comment se fait-il que nous puissions le renvoyer d'une fonction supposée renvoyer un seul pointeur?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.
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é.
~(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).
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.
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.
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
.
J'ai quelques problèmes avec ce code. Je les ai compilés dans la liste ci-dessous:
p1 = (void*)malloc
Vous ne lancez pas la valeur de retour de malloc.free(((void**)p)[-1]);
Vous ne libérez pas.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.