web-dev-qa-db-fra.com

Comment allouer de la mémoire alignée uniquement à l'aide de la bibliothèque standard?

Je viens de terminer un test dans le cadre d'un entretien d'embauche, et une question m'a laissé perplexe, même en utilisant Google comme référence. J'aimerais voir ce que l'équipe de StackOverflow peut en faire:

La fonction memset_16aligned requiert un pointeur aligné sur 16 octets, sinon elle tombera en panne.

a) Comment alloueriez-vous 1024 octets de mémoire et l'aligneriez-vous sur une limite de 16 octets?
b) Libérez 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    
}
400
JimDaniel

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 à la demande

La première étape consiste à allouer suffisamment d’espace disponible, 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 disposons de suffisamment d'espace. Quelque part dans les 16 premiers octets, il existe un pointeur aligné sur 16 octets. (Notez que malloc() est supposé renvoyer un pointeur suffisamment bien aligné pour le but any. Cependant, la signification de 'any' concerne principalement des types tels que les types de base - long, double, long double, long long et des pointeurs sur des objets et des pointeurs sur des fonctions. Lorsque vous effectuez des tâches plus spécialisées, comme jouer avec des systèmes graphiques, elles peuvent nécessiter un alignement plus strict que le reste de la système - d’où des questions et réponses comme celle-ci.)

L'étape suivante consiste à convertir le pointeur vide en un pointeur de caractère. Nonobstant GCC, vous n'êtes pas censé faire de l'arithmétique de pointeur sur des pointeurs vides (et GCC a des options d'avertissement pour vous avertir lorsque vous en abusez). Ajoutez ensuite 16 au pointeur de départ. Supposons que malloc() vous ait renvoyé un pointeur incroyablement mal aligné: 0x800001. L'ajout du 16 donne 0x800011. Maintenant, je veux arrondir à la limite de 16 octets - donc je veux réinitialiser les 4 derniers bits à 0. 0x0F a les 4 derniers bits définis à un; Par conséquent, ~0x0F a tous les bits mis à un sauf les 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 simple: vous devez toujours retourner à free() une valeur renvoyée par malloc(), calloc() ou realloc(); toute autre chose est un désastre. Vous avez correctement fourni mem pour conserver cette valeur - merci. Le libre le libère.

Enfin, si vous connaissez les composants internes du paquetage malloc de votre système, vous pourriez deviner qu'il pourrait bien renvoyer des données alignées sur 16 octets (ou peut-être être aligné sur 8 octets). Si elle était alignée sur 16 octets, vous n’auriez pas besoin de dinker avec les valeurs. Cependant, ceci est douteux et non portable - les autres packages malloc ont des alignements minimaux différents, et par conséquent, supposer qu'une chose différente entraînerait des vidages de mémoire. Dans de larges limites, cette solution est portable.

Quelqu'un d'autre a mentionné posix_memalign() comme un autre moyen d'obtenir la mémoire alignée; ce n'est pas disponible partout, mais pourrait souvent être mis en œuvre en utilisant cela comme base. Notez qu'il était pratique que l'alignement soit une puissance de 2; les autres alignements sont plus compliqués.

Encore un commentaire - ce code ne vérifie pas que l'allocation a réussi.

Amendement

Programmeur Windows a fait remarquer que vous ne pouvez pas effectuer d'opérations de masquage de bits sur les pointeurs. En effet, GCC (testé en 3.4.6 et 4.3.1) se plaint de ce fait. Donc, une version modifiée du code de base - converti en 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 puisque C99 existe depuis assez longtemps pour être accessible sur la plupart des plateformes. Sans l'utilisation de PRIXPTR dans les instructions printf(), il suffirait de #include <stdint.h> au lieu d'utiliser #include <inttypes.h>. [Ce code inclut le correctif indiqué par CR , qui rappelait un point soulevé pour la première fois par Bill K il y a plusieurs années, que j'ai réussi à ignorer jusqu'à maintenant.]

#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 ont 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 d'usage général, la valeur de retour unique provenant de l'allocateur devrait coder l'adresse de publication, comme plusieurs personnes l'ont indiqué dans leurs réponses.

Problèmes avec les intervieweurs

ri commenté: Peut-être ai-je un problème de compréhension de lecture ce matin, mais si la question de l'entrevue indique spécifiquement: "Comment alloueriez-vous 1024 octets de mémoire" et que vous allouiez 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-même) ont compris la question comme suit: "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, les options sont plus limitées.

  • Clairement, une possibilité consiste à allouer 1024 octets et à donner ensuite à cette adresse le "traitement d'alignement"; Le problème avec cette approche est que l’espace disponible réel n’est pas déterminé correctement (l’espace utilisable est compris entre 1008 et 1024 octets, mais il n’existait aucun mécanisme permettant de spécifier quelle taille), ce qui le rend moins utile.
  • Une autre possibilité est que vous soyez censé écrire un allocateur de mémoire complète et vous assurer que le bloc de 1024 octets que vous renvoyez est correctement aligné. Si tel est le cas, vous finirez probablement par effectuer une opération assez similaire à celle proposée par la solution proposée, mais vous la cacherez à l'intérieur de l'allocateur.

Cependant, si l'intervieweur s'attendait à l'une ou l'autre de ces réponses, je m'attendrais à ce qu'il reconnaisse que cette solution réponde à une question étroitement liée, puis reformule sa question pour orienter la conversation dans la bonne direction. (En outre, si l'intervieweur était vraiment décontenancé, je ne voudrais pas du travail; si la réponse à une exigence insuffisamment précise est anéantie par des flammes sans correction, alors l'enquêteur n'est pas quelqu'un pour qui il est prudent de travailler.)

Le monde avance

Le titre de la question a changé récemment. C'était Résoudre l'alignement de la mémoire dans la question de l'interview C qui m'a laissé perplexe. Le titre révisé (Comment allouer de la mémoire alignée en utilisant uniquement la bibliothèque standard?) appelle une réponse légèrement modifiée - cet addenda le fournit.

C11 (ISO/IEC 9899: 2011) a ajouté la fonction aligned_alloc():

7.22.3.1 La fonction aligned_alloc

Synopsis

#include <stdlib.h>
void *aligned_alloc(size_t alignment, size_t size);

Description
La fonction aligned_alloc alloue de la place pour 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.

Retourne
La fonction aligned_alloc renvoie un pointeur null ou 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 fonction posix_memalign() doit allouer size octets alignés sur une limite spécifiée par alignment et renvoyer un pointeur sur la mémoire allouée dans memptr. La valeur de alignment doit être une puissance de deux multiples de sizeof(void *).

En cas de succès, 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 dans memptr doit être un pointeur null ou un pointeur unique.

La fonction free() doit libérer la mémoire allouée précédemment par posix_memalign().

Valeur de retour

En cas de succès, posix_memalign() doit retourner zéro; sinon, un numéro d'erreur doit être renvoyé pour indiquer l'erreur.

Vous pouvez utiliser l'un ou l'autre ou les deux pour répondre à la question maintenant, mais seule la fonction POSIX était une option lorsque la question avait été répondue à l'origine.

En coulisse, la nouvelle fonction de mémoire alignée remplit sensiblement le travail décrit dans la question, à l'exception du fait qu'elle permet de forcer l'alignement plus facilement et de garder une trace du début de la mémoire alignée en interne afin d'éviter le code avoir à traiter spécialement - il libère simplement la mémoire retournée par la fonction d'allocation qui a été utilisée.

565
Jonathan Leffler

Trois réponses légèrement différentes selon votre perception de la question:

1) La solution de Jonathan Leffler convient assez bien à la question exacte, sauf que pour arrondir un maximum de 16, vous n'avez besoin que de 15 octets supplémentaires et non de 16.

UNE:

_/* 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 fonction d’allocation de mémoire plus générique, l’appelant ne souhaite pas avoir à garder trace de deux pointeurs (un à utiliser et un à libérer). Vous stockez donc un pointeur sur le tampon "réel" sous le tampon aligné.

UNE:

_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 à (1), où seulement 15 octets ont été ajoutés à mem, ce code pourrait en réalité réduire l'alignement si votre implémentation garantit 32 octets. alignement de malloc (improbable, mais en théorie, une implémentation en C pourrait avoir un type aligné sur 32 octets). Cela n'a pas d'importance si vous appelez simplement memset_16aligned, mais si vous utilisez la mémoire pour une structure, cela pourrait avoir de l'importance.

Je ne suis pas sûr que ce soit un bon correctif (sauf pour avertir l'utilisateur que le tampon renvoyé n'est pas nécessairement approprié pour des structures arbitraires) car il est impossible de déterminer par programme ce qu'est la garantie d'alignement spécifique à l'implémentation. Je suppose qu'au démarrage, vous pouvez allouer deux ou plusieurs mémoires tampons de 1 octet, et supposer que le pire alignement que vous voyez est l'alignement garanti. Si vous vous trompez, vous perdez de la mémoire. Quiconque a une meilleure idée, s'il vous plaît dites-le ...

[ Ajouté : L'astuce "standard" consiste à créer une union de "types susceptibles d'être alignés au maximum" afin de déterminer l'alignement requis. Les types alignés au maximum sont susceptibles d'être (en C99) '_long long_', '_long double_', '_void *_' ou 'void (*)(void)'; si vous incluez _<stdint.h>_, vous pouvez probablement utiliser "_intmax_t_" à la place de _long long_ (et, sur les machines Power 6 (AIX), _intmax_t_ vous donnerait un 128 - bit de type entier). Les exigences d'alignement pour cette union peuvent être déterminées en l'intégrant dans une structure avec un seul caractère suivi de 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;
_

Vous utiliseriez alors le plus grand des alignements demandés (dans l'exemple 16) et la valeur align calculée ci-dessus.

Sous Solaris 10 (64 bits), il semble que l'alignement de base du résultat de malloc() soit un multiple de 32 octets.

En pratique, les allocateurs alignés prennent souvent un paramètre pour l'alignement plutôt que d'être câblé. Ainsi, l'utilisateur passera dans la taille de la structure dont il se préoccupe (ou la moindre puissance de 2 supérieure ou égale à celle-ci) et tout ira bien.

3) Utilisez ce que votre plate-forme fournit: _posix_memalign_ pour POSIX, __aligned_malloc_ sous Windows.

4) Si vous utilisez C11, l'option la plus propre - portable et concise - consiste à utiliser la fonction de bibliothèque standard aligned_alloc introduite dans cette version de la spécification de langue.

56
Steve Jessop

Vous pouvez également essayer posix_memalign() (sur les plates-formes POSIX, bien sûr).

37
florin

Voici une autre approche de la partie "arrondir". Ce n'est pas la solution la plus brillamment codée, mais elle fait le travail, et ce type de syntaxe est un peu plus facile à retenir (cela fonctionnerait aussi pour des 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 de 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);
19
An̲̳̳drew

Malheureusement, en C99, il semble assez difficile de garantir un quelconque alignement de manière portable dans toute implémentation C conforme à C99. Pourquoi? Parce qu’un pointeur n’est pas forcément "l'adresse d'octet", on pourrait l'imaginer avec un modèle de mémoire à plat. La représentation de intptr_t n'est pas non plus garantie, ce qui est quand même un type optionnel.

Nous pourrions connaître certaines implémentations qui utilisent une représentation pour void * (et par définition, également char *) qui est une simple adresse sur un octet, mais qui, par C99, est opaque nous, les programmeurs. Une implémentation peut représenter un pointeur par un ensemble { segment , offset } où offset pourrait avoir un alignement qui-sait-quoi "dans la réalité". Pourquoi, un pointeur pourrait même être une forme de valeur de recherche dans une table de hachage, ou même une valeur de recherche dans une liste liée. Il pourrait encoder des informations sur les bornes.

Dans un brouillon récent C1X pour une norme C, nous voyons le mot-clé _ Alignas. Cela pourrait aider un peu.

La seule garantie offerte par C99 est que les fonctions d’allocation de mémoire renverront un pointeur approprié à une affectation à un pointeur pointant sur tout type d’objet. Étant donné que nous ne pouvons pas spécifier l'alignement des objets, nous ne pouvons pas implémenter nos propres fonctions d'allocation avec la responsabilité de l'alignement d'une manière portable bien définie.

Il serait bon de se tromper au sujet de cette revendication.

18
Shao

Sur le front de remplissage entre 16 et 15 octets, le nombre réel à ajouter pour obtenir un alignement de N est max (0, NM) où M est l’alignement naturel de l’allocateur de mémoire (et les deux sont des puissances de 2).

Comme l'alignement minimal en mémoire de tout allocateur est de 1 octet, 15 = max (0,16-1) est une réponse prudente. Cependant, si vous savez que votre allocateur de mémoire va vous donner des adresses alignées sur 32 bits (ce qui est assez courant), vous auriez pu utiliser 12 comme pad.

Ce n'est pas important pour cet exemple, mais cela pourrait être important sur un système embarqué avec 12 Ko de RAM où chaque int enregistré compte.

Le meilleur moyen de l'implémenter si vous voulez réellement enregistrer tous les octets possibles est d'utiliser une macro pour que vous puissiez l'alimenter avec votre alignement de mémoire natif. Encore une fois, cela n’est probablement utile que pour les systèmes embarqués où vous devez sauvegarder chaque octet.

Dans l'exemple ci-dessous, sur la plupart des systèmes, la valeur 1 convient parfaitement à MEMORY_ALLOCATOR_NATIVE_ALIGNMENT. Toutefois, pour notre système intégré théorique avec des allocations alignées sur 32 bits, les éléments suivants pourraient économiser une infime mémoire précieuse:

#define MEMORY_ALLOCATOR_NATIVE_ALIGNMENT    4
#define ALIGN_PAD2(N,M) (((N)>(M)) ? ((N)-(M)) : 0)
#define ALIGN_PAD(N) ALIGN_PAD2((N), MEMORY_ALLOCATOR_NATIVE_ALIGNMENT)
15
Adisak

Peut-être auraient-ils été satisfaits de la connaissance de memalign ? Et comme le souligne Jonathan Leffler, il existe deux nouvelles fonctions préférables à connaître.

Oops, Florin m'a battu. Cependant, si vous lisez la page de manuel vers laquelle je me suis connecté, vous comprendrez probablement l'exemple fourni par une affiche antérieure.

8
Don Wakefield

Je suis surpris que personne n'ait voté Shao 's réponse que, si je comprends bien, il est impossible de faire ce que demande la norme C99, car la conversion d'un pointeur en une intégrale type formellement est un comportement indéfini. (Mis à part le standard permettant la conversion de uintptr_t <-> void*, mais le standard ne semble pas autoriser aucune manipulation de la valeur uintptr_t puis la reconvertir.)

5
Lutorm

Nous faisons ce genre de chose tout le temps pour Accelerate.framework, une bibliothèque très vectorisée pour OS X/iOS, où nous devons faire attention à l'alignement tout le temps. Il existe plusieurs options, dont une ou deux que je n'ai pas vues mentionnées ci-dessus.

La méthode la plus rapide pour un petit tableau comme celui-ci est simplement de la coller sur la pile. Avec GCC/Clang:

 void my_func( void )
 {
     uint8_t array[1024] __attribute__ ((aligned(16)));
     ...
 }

Non gratuit () requis. Il s’agit généralement de deux instructions: soustrayez 1024 au pointeur de pile, puis à AND le pointeur de pile avec -alignment. On peut supposer que le demandeur avait besoin des données sur le tas, car sa durée de vie dépassait la pile, la récursivité est au travail ou l'espace de la pile est très important.

Sous OS X/iOS, tous les appels vers malloc/calloc/etc. sont toujours alignés sur 16 octets. Si vous aviez besoin de 32 octets alignés pour AVX, par exemple, vous pouvez utiliser posix_memalign:

void *buf = NULL;
int err = posix_memalign( &buf, 32 /*alignment*/, 1024 /*size*/);
if( err )
   RunInCirclesWaivingArmsWildly();
...
free(buf);

Certaines personnes ont mentionné l'interface C++ qui fonctionne de manière similaire.

Il ne faut pas oublier que les pages sont alignées sur une grande puissance de deux, de sorte que les tampons alignés sur une page sont également alignés sur 16 octets. Ainsi, mmap () et valloc () et d'autres interfaces similaires sont également des options. mmap () présente l'avantage que le tampon peut être alloué pré-initialisé avec quelque chose de différent de zéro, si vous le souhaitez. Comme ils ont une taille alignée sur la page, vous ne recevrez pas l'allocation minimale de ceux-ci et il sera probablement sujet à une erreur VM la première fois que vous le toucherez.

Cheesy: Allumez la garde malloc ou similaire. Les tampons dont la taille est n * 16 octets, comme celui-ci, seront alignés sur n * 16 octets, car VM est utilisé pour corriger les dépassements et ses limites se situent au niveau des limites de page.

Certaines fonctions Accelerate.framework utilisent un tampon temporaire fourni par l'utilisateur à utiliser comme espace de travail. Ici, nous devons supposer que le tampon qui nous a été transmis est extrêmement mal aligné et que l’utilisateur essaie activement de rendre notre vie difficile par dépit. (Nos scénarios de test collent une page de garde juste avant et après le tampon temporaire pour souligner le dépit.) Nous renvoyons ici la taille minimale dont nous avons besoin pour garantir un segment aligné de 16 octets quelque part, puis alignons manuellement le tampon par la suite. Cette taille est taille désirée + alignement - 1. Donc, dans ce cas, 1024 + 16 - 1 = 1039 octets. Puis alignez comme suit:

#include <stdint.h>
void My_func( uint8_t *tempBuf, ... )
{
    uint8_t *alignedBuf = (uint8_t*) 
                          (((uintptr_t) tempBuf + ((uintptr_t)alignment-1)) 
                                        & -((uintptr_t) alignment));
    ...
}

L'ajout de alignement-1 déplacera le pointeur au-delà de la première adresse alignée puis de ANDing avec -alignment (par exemple, 0xfff ... ff0 pour l'alignement = 16) le ramènera à l'adresse alignée.

Comme décrit par d’autres publications, sur d’autres systèmes d’exploitation sans garantie d’alignement sur 16 octets, vous pouvez appeler malloc avec la taille la plus grande, mettre de côté le pointeur gratuitement (), puis aligner comme décrit ci-dessus et utiliser le pointeur aligné. décrit pour notre cas de tampon temporaire.

Quant à aligné_memset, c'est plutôt idiot. Il vous suffit de boucler jusqu'à 15 octets pour atteindre une adresse alignée, puis de poursuivre avec les magasins alignés, avec éventuellement un code de nettoyage possible à la fin. Vous pouvez même effectuer les bits de nettoyage en code vectoriel, soit en tant que magasins non alignés qui chevauchent la région alignée (à condition que la longueur soit au moins égale à celle d'un vecteur), ou en utilisant quelque chose comme movmaskdqu. Quelqu'un est juste paresseux. Cependant, si l'intervieweur veut savoir si vous êtes à l'aise avec stdint.h, les opérateurs au niveau des bits et les fondamentaux de la mémoire, la question de l'interview est probablement raisonnable, afin que l'exemple artificiel puisse être pardonné.

5
Ian Ollmann

Lors de la lecture de cette question, la première chose qui me vint à l’esprit fut de définir une structure alignée, de l’instancier, puis de la pointer vers elle.

Y a-t-il une raison fondamentale qui me manque puisque personne d'autre ne l'a suggéré?

Sidenote, comme j'ai utilisé un tableau de caractères (en supposant que le caractère du système est de 8 bits (c.-à-d. Un octet)), je ne vois pas nécessairement la nécessité de la __attribute__((packed)) nécessairement (corrigez-moi si je me trompe). , mais je le mets quand même.

Cela fonctionne sur deux systèmes sur lesquels j'ai essayé, mais il est possible qu'il y ait une optimisation du compilateur dont je ne suis pas au courant pour me donner des faux positifs face à l'efficacité du code. J'ai utilisé gcc 4.9.2 sur OSX et gcc 5.2.1 sur Ubuntu.

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

int main ()
{

   void *mem;

   void *ptr;

   // answer a) here
   struct __attribute__((packed)) s_CozyMem {
       char acSpace[16];
   };

   mem = malloc(sizeof(struct s_CozyMem));
   ptr = mem;

   // memset_16aligned(ptr, 0, 1024);

   // Check if it's aligned
   if(((unsigned long)ptr & 15) == 0) printf("Aligned to 16 bytes.\n");
   else printf("Rubbish.\n");

   // answer b) here
   free(mem);

   return 1;
}
3
J-a-n-u-s

l'utilisation de memalign, Aligned-Memory-Blocks pourrait être une bonne solution au problème.

3
neuron

MacOS X spécifique:

  1. Tous les pointeurs attribués avec malloc sont alignés sur 16 octets.
  2. C11 est supporté, vous pouvez donc simplement appeler aligné_malloc (16, taille).

  3. MacOS X sélectionne un code optimisé pour chaque processeur au moment du démarrage pour memset, memcpy et memmove, et ce code utilise des astuces dont vous n'avez jamais entendu parler pour accélérer les choses. 99% de chances que memset s'exécute plus rapidement que n'importe quel memset16 écrit à la main, ce qui rend toute la question inutile.

Si vous voulez une solution 100% portable, avant C11, il n'y en a pas. Parce qu'il n'y a pas de moyen portable de tester l'alignement d'un pointeur. S'il n'est pas nécessaire qu'il soit 100% portable, vous pouvez utiliser

char* p = malloc (size + 15);
p += (- (unsigned int) p) % 16;

Cela suppose que l'alignement d'un pointeur est stocké dans les bits les plus bas lors de la conversion d'un pointeur en unsigned int. La conversion en informations non signées perd des informations et sa définition est définie par l'implémentation, mais cela n'a pas d'importance, car nous ne convertissons pas le résultat en pointeur.

La partie horrible est bien sûr que le pointeur original doit être sauvegardé quelque part pour appeler free () avec. Donc dans l'ensemble je doute vraiment de la sagesse de cette conception.

1
Chris

Juste utiliser memalign? http://linux.die.net/man/3/memalign

0
PhonicUK

Pour la solution, j'ai utilisé un concept de remplissage qui aligne la mémoire et ne gaspille pas la mémoire d'un seul octet.

S'il y a des contraintes, vous ne pouvez pas perdre un seul octet. Tous les pointeurs attribués avec malloc sont alignés sur 16 octets.

C11 est pris en charge, vous pouvez donc simplement appeler aligned_alloc (16, size).

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

Vous pouvez également ajouter environ 16 octets, puis pousser le ptr original sur 16 bits en ajoutant le (16 mod) comme ci-dessous le pointeur:

main(){
void *mem1 = malloc(1024+16);
void *mem = ((char*)mem1)+1; // force misalign ( my computer always aligns)
printf ( " ptr = %p \n ", mem );
void *ptr = ((long)mem+16) & ~ 0x0F;
printf ( " aligned ptr = %p \n ", ptr );

printf (" ptr after adding diff mod %p (same as above ) ", (long)mem1 + (16 -((long)mem1%16)) );


free(mem1);
}
0
resultsway

S'il existe des contraintes qui ne permettent pas de perdre un seul octet, cette solution fonctionne: Remarque: dans certains cas, il peut être exécuté à l'infini: D

   void *mem;  
   void *ptr;
try:
   mem =  malloc(1024);  
   if (mem % 16 != 0) {  
       free(mem);  
       goto try;
   }  
   ptr = mem;  
   memset_16aligned(ptr, 0, 1024);
0
Deepthought