460 votes

Résoudre l'alignement de la mémoire dans C question d'entrevue qui m'a bloqué

Je viens de terminer un test dans le cadre d'un entretien d'embauche, et une question m'a bloqué - même en utilisant google pour référence. J'aimerais voir ce que l'équipe de stackoverflow peut faire avec:

La fonction "memset_16aligned" requiert un pointeur aligné de 16 octets qui lui est passé, sinon il va planter.

a) Comment allouer 1024 octets de mémoire, et l'aligner à une limite de 16 octets?
b) Libérer la mémoire après l'exécution du memset_16aligned.

 {

   void *mem;

   void *ptr;

   // answer a) here

   memset_16aligned(ptr, 0, 1024);

   // answer b) here

}
 

633voto

Jonathan Leffler Points 299946

Réponse originale à cette question

{
void *mem = malloc(1024+16);
void *ptr = ((char *)mem+16) & ~ 0x0F;
memset_16aligned(ptr, 0, 1024);
free(mem);
}

Fixe répondre

{
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 disque de secours, juste au cas où. Depuis le mémoire doit être de 16 octets alignés (ce qui signifie que le premier octet de l'adresse doit être un multiple de 16), l'ajout de 16 octets supplémentaires garanties que nous avons assez d'espace. Quelque part dans les 16 premiers octets 16 octets aligné pointeur. (À noter qu' malloc() est censé renvoyer un pointeur qui est suffisamment bien alignés pour tout but. Cependant, la signification de "toute" est principalement pour des choses comme les types de base de long, double, long double, long de long. Quand vous faites plus de choses spécialisées, comme jouer avec des systèmes graphiques, ils peuvent avoir besoin de plus strictes de l'alignement que le reste du système, d'où les questions et les réponses de ce genre.)

La prochaine étape est de convertir le vide pointeur vers un pointeur de char; GCC malgré tout, vous n'êtes pas censé faire de l'arithmétique des pointeurs sur des pointeurs void (et GCC possède des options d'alerte pour vous avertir lorsque vous en abuser). Puis ajouter 16 au début de pointeur. Supposons malloc() retour, un incroyablement mal alignées pointeur: 0x800001. Ajout du 16 donne 0x800011. Maintenant, je veux arrondir à l'16-frontière d'octet - donc, je veux réinitialiser les 4 derniers bits à 0. 0x0F a les 4 derniers bits de l'un; par conséquent, ~ 0x0F a tous les bits mis à part le dernier de quatre. Anding qu'avec 0x800011 donne 0x800010. Vous pouvez itérer sur les autres compensations et de voir que la même arithmétique des œuvres.

La dernière étape, free(), c'est facile: vous pour toujours, et uniquement, de retour à l' free() d'une valeur que l'un d' malloc(), calloc() ou realloc() retourné pour vous - tout le reste est une catastrophe. Vous avez correctement fournies mem à juger que la valeur - je vous remercie. Les versions libres.

Enfin, si vous savez sur le fonctionnement interne de votre système malloc package, vous pouvez deviner qu'il pourrait bien revenir sur 16 octets données alignées (ou il pourrait être alignée 8 octets). Si elle était de 16 octets alignés, alors vous auriez pas besoin de dink avec les valeurs. Cependant, ce qui est douteux et non portable -- autres malloc de paquets qui ont minimum des alignements, et, par conséquent, en supposant une chose quand il fait quelque chose de différent conduirait à des core dumps. Dans de larges limites, cette solution est portable.

Quelqu'un d'autre a mentionné posix_memalign() comme une autre façon d'obtenir l'alignement mémoire, qui n'est pas disponible partout, mais peut souvent être mise en œuvre à l'aide de cette base. Notez qu'il est commode que l'alignement est une puissance de 2; d'autres alignements sont messier.

Encore un commentaire - ce code n'est pas de vérifier que la répartition réussi.

Amendement

Programmeur informatique remarquer que vous ne pouvez pas faire de masque de bits des opérations sur les pointeurs, et, en effet, GCC (3.4.6 et 4.3.1 testé) ne se plaignent comme ça. Donc, une version modifiée de la base de code est converti en un programme principal, de ce qui suit. J'ai aussi pris la liberté d'ajouter 15 au lieu de 16, comme cela a été souligné. Je suis à l'aide d' uintptr_t depuis C99 a été autour assez longtemps pour être accessible sur la plupart des plateformes. Si ce n'était pas pour l'utilisation de l' PRIXPTR dans la printf() des déclarations, il serait suffisant d' #include <stdint.h> au lieu d'utiliser #include <inttypes.h>. [Ce code inclut le correctif a été souligné par C. R., qui a été réitérant un point en premier lieu par le projet de Loi K un certain nombre d'années, j'ai réussi à l'ignorer jusqu'à maintenant.]

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

static void memset_16aligned(void *space, char byte, size_t nbytes)
{
    assert((nbytes & 0x0F) == 0);
    assert(((uintptr_t)space & 0x0F) == 0);
}

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", mem, ptr);
    memset_16aligned(ptr, 0, 1024);
    free(mem);
    return(0);
}

Et voici un peu plus de version généralisée, qui va travailler pour les tailles qui sont une puissance de 2:

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

static void memset_16aligned(void *space, char byte, size_t nbytes)
{
    assert((nbytes & 0x0F) == 0);
    assert(((uintptr_t)space & 0x0F) == 0);
}

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", mem, 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() dans un objectif général de fonction d'allocation, la seule valeur de retour de l'allocateur aurait pour encoder la libération de l'adresse, comme plusieurs personnes nous ont indiqué dans leurs réponses.

Problèmes avec les enquêteurs

Uri a commenté: Peut-être que je vais avoir [un] problème de compréhension de la lecture ce matin, mais si la question de l'entrevue spécifiquement dit: "Comment voulez-vous allouer 1024 octets de mémoire" et de toute évidence, vous affecter plus que ça. Ce ne serait pas un échec automatique de l'interviewer?

Ma réponse ne rentre pas dans l'un des 300 caractère de commentaire...

Ça dépend, je suppose. Je pense que la plupart des gens (moi y compris) ont pris la question à se dire "Comment voulez-vous allouer un espace dans lequel 1024 octets de données peuvent être stockées, et où l'adresse de base est un multiple de 16 octets". Si l'interviewer signifiait vraiment comment pouvez-vous attribuer 1024 octets (uniquement) et de l'avoir 16 octets alignés, les options sont plus limitées.

  • Clairement, il est possible d'allouer de 1024 octets et ensuite donner l'adresse de l'alignement de traitement"; le problème avec cette approche est que l'espace disponible n'est pas bien déterminée (l'espace utilisable est entre 1008 et 1024 octets, mais il n'y avait pas un mécanisme disponible pour spécifier la taille), ce qui la rend moins utile.
  • Une autre possibilité est que vous êtes censé écrire un plein allocateur de mémoire et de s'assurer que le bloc de 1 024 octets de votre retour est correctement aligné. Si c'est le cas, vous probablement jusqu'à la fin de faire une opération assez similaire à ce que la solution proposée n', mais vous cacher à l'intérieur de l'allocateur.

Toutefois, si l'intervieweur devrait l'un de ces réponses, je m'attends à reconnaître que cette solution répond à un étroitement liés à la question, puis de recadrer leur question de point de la conversation dans la bonne direction. (De plus, si l'enquêteur a vraiment stroppy, alors je ne voudrais pas le travail; si la réponse à une insuffisamment précis exigence est abattu en flammes, sans correction, alors que l'interviewer n'est pas quelqu'un pour qui il est sécuritaire pour le travail.)

63voto

Steve Jessop Points 166970

Trois des réponses légèrement différentes selon la façon dont vous regardez la question:

1) assez Bon pour la question exacte posée est Jonathan Leffler de solution, sauf que le round 16-alignés, vous n'avez besoin que de 15 octets supplémentaires, pas 16.

Un:

/* allocate a buffer with room to add 0-15 bytes to ensure 16-alignment */
void *mem = malloc(1024+15);
ASSERT(mem); // some kind of error-handling code
/* round up to multiple of 16: add 15 and then round down by masking */
void *ptr = ((char*)mem+15) & ~ (size_t)0x0F;

B:

free(mem);

2) Pour une plus générique de la fonction d'allocation mémoire, l'appelant ne voulez pas avoir à garder une trace de deux pointeurs (une et une seule de libre). Si vous stocker un pointeur vers le "vrai" tampon au-dessous de l'alignement de la mémoire tampon.

Un:

void *mem = malloc(1024+15+sizeof(void*));
if (!mem) return mem;
void *ptr = ((char*)mem+sizeof(void*)+15) & ~ (size_t)0x0F;
((void**)ptr)[-1] = mem;
return ptr;

B:

if (ptr) free(((void**)ptr)[-1]);

Notez que, contrairement aux (1), où seulement 15 octets ont été ajoutés à mem, ce code pourrait effectivement réduire l'alignement si votre mise en œuvre qui se passe pour la garantie de 32 octets de l'alignement de malloc (peu probable, mais, en théorie, un C mise en œuvre pourrait avoir un 32 octets aligné type). Ce n'est pas grave si tout ce que vous faire est d'appeler memset_16aligned, mais si vous utilisez la mémoire pour une structure (struct), alors il pourrait avoir son importance.

Je ne suis pas sûr de la main gauche ce qu'est un bon correctif est pour cela (autres que pour avertir l'utilisateur que le tampon de retour n'est pas forcément adapté pour arbitraire des structures) car il n'y a aucun moyen de déterminer par programmation à ce que la mise en œuvre spécifique de l'alignement de garantie. Je pense au démarrage, vous pourrait affecter deux ou plus de 1 octet, tampons, et à supposer que le pire de l'alignement que vous voyez est la garantie de l'alignement. Si vous vous trompez, vous perdez la mémoire. Quelqu'un à une meilleure idée, merci de le dire...

[Ajouté: La 'norme' astuce consiste à créer une union devrait être au maximum de alignées types " pour déterminer la condition d'alignement. Le maximum aligné types sont susceptibles d'être (en C99) 'long long', 'long double', 'void *"ou"void (*)(void)'; si vous incluez <stdint.h>, vous pourriez probablement utiliser 'intmax_t"à la place de long long (et, sur le Pouvoir 6 (AIX) les machines, intmax_t serait de vous donner un cryptage de 128 bits type entier). L'alignement des exigences pour que l'union peut être déterminé en l'intégrant dans une structure avec un seul char, suivi par l'union:

struct alignment
{
    char     c;
    union
    {
        intmax_t      imax;
        long double   ldbl;
        void         *vptr;
        void        (*fptr)(void);
    }        u;
} align_data;
size_t align = (char *)&align_data.u.imax - &align_data.c;

Ensuite, vous devez utiliser la plus grande de demande d'alignement (dans l'exemple, 16) et l' align de la valeur calculée ci-dessus.

(64-bit) Solaris 10, il semble que la base de l'alignement pour le résultat de l' malloc() est un multiple de 32 octets.
]

Dans la pratique, aligné allocateurs souvent prendre un paramètre pour l'alignement plutôt que ce soit câblé. Afin que l'utilisateur passe à la taille de la structure qu'ils se soucient de (ou du moins la puissance de 2 supérieure ou égale à) et tout ira bien.

3) Utiliser ce que votre plate-forme fournit: posix_memalign de POSIX, _aligned_malloc sur Windows.

40voto

florin Points 6891

Vous pouvez aussi essayer posix_memalign (sur les plateformes POSIX, bien sûr).

20voto

Andrew Points 2578

Voici une autre approche de la partie «arrondir». Ce n'est pas la solution la plus brillamment codée, mais le travail est fait, et ce type de syntaxe est un peu plus facile à retenir (en plus, cela fonctionnerait pour les valeurs d'alignement qui ne sont pas une puissance de 2). La distribution uintptr_t était nécessaire pour apaiser le compilateur; L'arithmétique du pointeur n'aime pas beaucoup la division ou la multiplication.

 void *mem = malloc(1024 + 15);
void *ptr = (void*) ((uintptr_t) mem + 15) / 16 * 16;
memset_16aligned(ptr, 0, 1024);
free(mem);
 

20voto

Shao Points 99

Malheureusement, en C99, il semble assez difficile pour garantir l'alignement de toute sorte de manière à être utilisable sur tout C mise en œuvre conforme à la C99. Pourquoi? Parce qu'un pointeur n'est pas garanti d'être le "adresse de l'octet", on peut imaginer qu'avec un modèle de mémoire fixe. Ni est la représentation de la "uintptr_t' donc garanti, qui lui-même est un type d'option, de toute façon.

On apprend à connaître de certaines implémentations qui utilisent une représentation de 'void *' (et par définition, aussi 'char *') qui est une simple adresse de l'octet, mais en C99, il est opaque, à nous, les programmeurs. Une mise en œuvre pourrait représenter un pointeur par un ensemble {segment, offset} où "compensées" pourrait avoir qui-sait-ce que l'alignement "dans la réalité." Pourquoi, un pointeur peut-être même une certaine forme de table de hachage valeur de recherche, ou même une liste liée valeur de recherche. Il pourrait coder les limites de l'information.

Dans une récente C1X projet pour une Norme C, nous voyons le " _Alignas ce mot-clé. Qui pourrait aider un peu.

La seule garantie C99 nous donne est que les fonctions d'allocation de mémoire retournera un pointeur adapté pour l'affectation d'un pointeur pointant sur tout type d'objet. Puisque nous ne pouvons pas spécifier l'alignement des objets, nous ne pouvons pas mettre en œuvre nos propres fonctions d'allocation de la responsabilité de l'alignement dans des conditions bien définies, de manière portable.

Il serait bon de se tromper au sujet de cette allégation.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X