Réponse originale
{
void *mem = malloc(1024+16);
void *ptr = ((char *)mem+16) & ~ 0x0F;
memset_16aligned(ptr, 0, 1024);
free(mem);
}
Réponse fixe
{
void *mem = malloc(1024+15);
void *ptr = ((uintptr_t)mem+15) & ~ (uintptr_t)0x0F;
memset_16aligned(ptr, 0, 1024);
free(mem);
}
Explication comme demandé
La première étape consiste à allouer suffisamment d'espace libre, au cas où. Étant donné que la mémoire doit être alignée sur 16 octets (ce qui signifie que l'adresse d'octet de tête doit être un multiple de 16), l'ajout de 16 octets supplémentaires garantit que nous avons suffisamment d'espace. Quelque part dans les 16 premiers octets, il y a un pointeur aligné de 16 octets. (Notez que malloc()
est censé renvoyer un pointeur qui est suffisamment bien aligné pour tout . But Cependant, le sens de « tout » est principalement pour des choses comme les types de base - long
, double
, long double
, long long
., Et des pointeurs vers des objets et des pointeurs vers des fonctions Lorsque vous êtes faire des choses plus spécialisées, comme jouer avec des systèmes graphiques, ils peuvent avoir besoin d'un alignement plus strict que le reste du système - d'où des questions et réponses comme celle-ci.)
L'étape suivante consiste à convertir le pointeur void en un pointeur char; Malgré GCC, vous n'êtes pas censé faire de l'arithmétique des pointeurs sur les pointeurs vides (et GCC a des options d'avertissement pour vous dire quand vous en abusez). Ajoutez ensuite 16 au pointeur de départ. Supposons que malloc()
vous ayez renvoyé un pointeur incroyablement mal aligné: 0x800001. L'ajout du 16 donne 0x800011. Maintenant, je veux arrondir à la limite de 16 octets - je veux donc réinitialiser les 4 derniers bits à 0. 0x0F a les 4 derniers bits mis à un; par conséquent, ~0x0F
tous les bits sont définis sur un, à l'exception des quatre derniers. Et cela avec 0x800011 donne 0x800010. Vous pouvez parcourir les autres décalages et voir que la même arithmétique fonctionne.
La dernière étape, free()
est facile: vous toujours, et seulement, le retour à free()
une valeur que l' un des malloc()
, calloc()
ou realloc()
retourné à vous - tout est bien une catastrophe. Vous avez correctement fourni mem
de conserver cette valeur - merci. Le libre le libère.
Enfin, si vous connaissez les éléments internes du malloc
package de votre système , vous pourriez deviner qu'il pourrait bien renvoyer des données alignées sur 16 octets (ou qu'il pourrait être aligné sur 8 octets). S'il était aligné sur 16 octets, vous n'auriez pas besoin de vous arrêter aux valeurs. Cependant, c'est douteux et non portable - d'autres malloc
packages ont des alignements minimaux différents, et donc supposer une chose quand il fait quelque chose de différent conduirait à des vidages de mémoire. Dans de larges limites, cette solution est portable.
Quelqu'un d'autre a mentionné posix_memalign()
une autre façon d'obtenir la mémoire alignée; qui n'est pas disponible partout, mais pourrait souvent être implémenté en utilisant cela comme base. Notez qu'il était pratique que l'alignement soit une puissance de 2; d'autres alignements sont plus compliqués.
Encore un commentaire - ce code ne vérifie pas que l'allocation a réussi.
Amendement
Le programmeur Windows a souligné que vous ne pouvez pas effectuer d'opérations de masquage de bits sur des pointeurs, et, en effet, GCC (testé 3.4.6 et 4.3.1) se plaint comme ça. Ainsi, une version modifiée du code de base - converti en un programme principal, suit. J'ai également pris la liberté d'ajouter seulement 15 au lieu de 16, comme cela a été souligné. J'utilise uintptr_t
depuis que C99 existe depuis assez longtemps pour être accessible sur la plupart des plateformes. Si ce n'était pas pour l'utilisation de PRIXPTR
dans les printf()
instructions, ce serait suffisant pour #include <stdint.h>
au lieu d'utiliser #include <inttypes.h>
. [Ce code inclut le correctif signalé par CR , qui réitère un point soulevé par Bill K il y a plusieurs années, que j'ai réussi à ignorer jusqu'à présent.]
#include <assert.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static void memset_16aligned(void *space, char byte, size_t nbytes)
{
assert((nbytes & 0x0F) == 0);
assert(((uintptr_t)space & 0x0F) == 0);
memset(space, byte, nbytes); // Not a custom implementation of memset()
}
int main(void)
{
void *mem = malloc(1024+15);
void *ptr = (void *)(((uintptr_t)mem+15) & ~ (uintptr_t)0x0F);
printf("0x%08" PRIXPTR ", 0x%08" PRIXPTR "\n", (uintptr_t)mem, (uintptr_t)ptr);
memset_16aligned(ptr, 0, 1024);
free(mem);
return(0);
}
Et voici une version légèrement plus généralisée, qui fonctionnera pour les tailles qui sont une puissance de 2:
#include <assert.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static void memset_16aligned(void *space, char byte, size_t nbytes)
{
assert((nbytes & 0x0F) == 0);
assert(((uintptr_t)space & 0x0F) == 0);
memset(space, byte, nbytes); // Not a custom implementation of memset()
}
static void test_mask(size_t align)
{
uintptr_t mask = ~(uintptr_t)(align - 1);
void *mem = malloc(1024+align-1);
void *ptr = (void *)(((uintptr_t)mem+align-1) & mask);
assert((align & (align - 1)) == 0);
printf("0x%08" PRIXPTR ", 0x%08" PRIXPTR "\n", (uintptr_t)mem, (uintptr_t)ptr);
memset_16aligned(ptr, 0, 1024);
free(mem);
}
int main(void)
{
test_mask(16);
test_mask(32);
test_mask(64);
test_mask(128);
return(0);
}
Pour convertir test_mask()
en une fonction d'allocation à usage général, la valeur de retour unique de l'allocateur devrait coder l'adresse de libération, comme plusieurs personnes l'ont indiqué dans leurs réponses.
Problèmes avec les enquêteurs
Uri a commenté: Peut-être que j'ai un problème de compréhension de la lecture ce matin, mais si la question d'entrevue dit spécifiquement: "Comment alloueriez-vous 1024 octets de mémoire" et vous allouez clairement plus que cela. Ne serait-ce pas un échec automatique de l'intervieweur?
Ma réponse ne rentre pas dans un commentaire de 300 caractères ...
Cela dépend, je suppose. Je pense que la plupart des gens (y compris moi) ont pris la question pour signifier "Comment alloueriez-vous un espace dans lequel 1024 octets de données peuvent être stockés, et où l'adresse de base est un multiple de 16 octets". Si l'intervieweur voulait vraiment savoir comment allouer 1024 octets (uniquement) et l'aligner sur 16 octets, alors les options sont plus limitées.
- De toute évidence, une possibilité consiste à allouer 1 024 octets, puis à donner à cette adresse le «traitement d'alignement»; le problème avec cette approche est que l'espace disponible réel n'est pas correctement déterminé (l'espace utilisable est compris entre 1008 et 1024 octets, mais il n'y avait pas de mécanisme disponible pour spécifier quelle taille), ce qui le rend moins qu'utile.
- Une autre possibilité est que vous êtes censé écrire un allocateur de mémoire plein et vous assurer que le bloc de 1024 octets que vous renvoyez est correctement aligné. Si tel est le cas, vous finissez probablement par faire une opération assez similaire à celle de la solution proposée, mais vous la cachez dans l'allocateur.
Cependant, si l'intervieweur s'attendait à l'une de ces réponses, je m'attendrais à ce qu'il reconnaisse que cette solution répond à une question étroitement liée, puis à recadrer sa question pour orienter la conversation dans la bonne direction. (De plus, si l'intervieweur est devenu très bâclé, alors je ne voudrais pas le travail; si la réponse à une exigence insuffisamment précise est abattue dans les flammes sans correction, alors l'intervieweur n'est pas quelqu'un pour qui il est sûr de travailler.)
Le monde continue
Le titre de la question a changé récemment. C'est Résoudre l'alignement de la mémoire dans la question d'entrevue C qui m'a déconcerté . Le titre révisé ( Comment allouer la mémoire alignée uniquement en utilisant la bibliothèque standard? ) Exige une réponse légèrement révisée - cet addendum le fournit.
C11 (ISO / IEC 9899: 2011) fonction ajoutée aligned_alloc()
:
7.22.3.1 La aligned_alloc
fonction
Synopsis
#include <stdlib.h>
void *aligned_alloc(size_t alignment, size_t size);
Description
La aligned_alloc
fonction alloue de l'espace à un objet dont l'alignement est spécifié par alignment
, dont la taille est spécifiée par size
et dont la valeur est indéterminée. La valeur de alignment
doit être un alignement valide pris en charge par la mise en œuvre et la valeur de size
doit être un multiple entier de alignment
.
Renvoie
La aligned_alloc
fonction renvoie soit un pointeur nul, soit un pointeur sur l'espace alloué.
Et POSIX définit posix_memalign()
:
#include <stdlib.h>
int posix_memalign(void **memptr, size_t alignment, size_t size);
LA DESCRIPTION
La posix_memalign()
fonction doit allouer des size
octets alignés sur une frontière spécifiée par alignment
et retourner un pointeur vers la mémoire allouée dans memptr
. La valeur de alignment
doit être une puissance de deux multiples de sizeof(void *)
.
En cas de réussite, la valeur indiquée par memptr
doit être un multiple de alignment
.
Si la taille de l'espace demandé est 0, le comportement est défini par l'implémentation; la valeur renvoyée memptr
doit être soit un pointeur nul, soit un pointeur unique.
La free()
fonction doit désallouer la mémoire qui a été précédemment allouée par posix_memalign()
.
VALEUR DE RETOUR
En cas de réussite, posix_memalign()
doit retourner zéro; sinon, un numéro d'erreur doit être renvoyé pour indiquer l'erreur.
L'un ou l'autre ou les deux peuvent être utilisés pour répondre à la question maintenant, mais seule la fonction POSIX était une option lorsque la question a été répondue à l'origine.
Dans les coulisses, la nouvelle fonction de mémoire alignée fait à peu près le même travail que celui décrit dans la question, sauf qu'elle a la possibilité de forcer l'alignement plus facilement et de garder une trace du début de la mémoire alignée en interne afin que le code ne avoir à traiter spécialement - il libère juste la mémoire retournée par la fonction d'allocation qui a été utilisée.