it-swarm.com.de

Wie reserviere ich den ausgerichteten Speicher nur mit der Standardbibliothek?

Ich habe gerade einen Test als Teil eines Vorstellungsgesprächs beendet, und eine Frage hat mich überrascht, auch wenn ich Google als Referenz verwendet habe. Ich würde gerne sehen, was die StackOverflow-Crew damit anfangen kann:

Das memset_16aligned-Funktion erfordert einen 16-Byte-ausgerichteten Zeiger, der an sie übergeben wird, oder sie stürzt ab.

a) Wie würden Sie 1024 Byte Speicher zuweisen und an einer 16-Byte-Grenze ausrichten?
b) Geben Sie den Speicher frei, nachdem memset_16aligned wurde ausgeführt.

{    
   void *mem;
   void *ptr;

   // answer a) here

   memset_16aligned(ptr, 0, 1024);

   // answer b) here    
}
400
JimDaniel

Ursprüngliche Antwort

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

Feste Antwort

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

Erklärung wie gewünscht

Der erste Schritt besteht darin, für alle Fälle genügend freien Speicherplatz bereitzustellen. Da der Speicher auf 16 Byte ausgerichtet sein muss (was bedeutet, dass die führende Byteadresse ein Vielfaches von 16 sein muss), stellt das Hinzufügen von 16 zusätzlichen Bytes sicher, dass genügend Speicherplatz vorhanden ist. Irgendwo in den ersten 16 Bytes befindet sich ein mit 16 Bytes ausgerichteter Zeiger. (Beachten Sie, dass malloc() einen Zeiger zurückgeben soll, der für den any Zweck ausreichend gut ausgerichtet ist. Die Bedeutung von 'any' bezieht sich jedoch hauptsächlich auf grundlegende Typen - long, double, long double, long long Und Zeiger auf Objekte und Zeiger zu Funktionen. Wenn Sie spezialisiertere Dinge tun, wie das Spielen mit Grafiksystemen, müssen diese möglicherweise strenger ausgerichtet werden als der Rest des Systems - daher Fragen und Antworten wie diese.)

Der nächste Schritt besteht darin, den Leerzeiger in einen Zeichenzeiger umzuwandeln. Ungeachtet dessen sollten Sie keine Zeigerarithmetik für leere Zeiger ausführen (und GCC verfügt über Warnoptionen, die Sie benachrichtigen, wenn Sie sie missbrauchen). Fügen Sie dann 16 zum Startzeiger hinzu. Angenommen, malloc() hat einen unmöglich falsch ausgerichteten Zeiger zurückgegeben: 0x800001. Das Hinzufügen der 16 ergibt 0x800011. Jetzt möchte ich auf die 16-Byte-Grenze abrunden - also möchte ich die letzten 4 Bits auf 0 zurücksetzen. 0x0F hat die letzten 4 Bits auf Eins gesetzt; Daher sind bei ~0x0F alle Bits mit Ausnahme der letzten vier auf eins gesetzt. Und das mit 0x800011 ergibt 0x800010. Sie können die anderen Offsets durchlaufen und feststellen, dass die gleiche Arithmetik funktioniert.

Der letzte Schritt, free(), ist einfach: Sie kehren immer und nur zu free() zurück, einem Wert, der einem der Werte malloc(), calloc() entspricht. oder realloc() ist an Sie zurückgekehrt - alles andere ist eine Katastrophe. Sie haben mem korrekt angegeben, um diesen Wert beizubehalten - danke. Das kostenlose veröffentlicht es.

Wenn Sie schließlich die Interna des Pakets malloc Ihres Systems kennen, können Sie davon ausgehen, dass es möglicherweise 16-Byte-ausgerichtete Daten zurückgibt (oder 8-Byte-ausgerichtete Daten). Wenn es auf 16 Byte ausgerichtet wäre, müssten Sie nicht mit den Werten dinken. Dies ist jedoch zweifelhaft und nicht portierbar - andere malloc -Pakete weisen unterschiedliche Mindestausrichtungen auf und die Annahme, dass eine andere Vorgehensweise zu Core-Dumps führen würde. Diese Lösung ist in weiten Grenzen portabel.

Jemand anders erwähnte posix_memalign() als einen anderen Weg, um den ausgerichteten Speicher zu erhalten; Das ist nicht überall verfügbar, könnte aber oft auf dieser Basis implementiert werden. Beachten Sie, dass es praktisch war, dass die Ausrichtung eine Potenz von 2 war; andere Ausrichtungen sind unordentlicher.

Noch ein Kommentar - dieser Code überprüft nicht, ob die Zuordnung erfolgreich war.

Änderung

Windows Programmer wies darauf hin, dass Sie keine Bitmaskenoperationen mit Zeigern ausführen können, und tatsächlich beklagt sich GCC (3.4.6 und 4.3.1 getestet) so. Es folgt eine geänderte Version des Basiscodes, der in ein Hauptprogramm konvertiert wurde. Ich habe mir auch erlaubt, nur 15 statt 16 hinzuzufügen, wie bereits erwähnt wurde. Ich verwende uintptr_t, Da C99 schon lange genug existiert, um auf den meisten Plattformen erreichbar zu sein. Ohne die Verwendung von PRIXPTR in den printf() -Anweisungen wäre es ausreichend, #include <stdint.h> Anstelle von #include <inttypes.h> Zu verwenden. [Dieser Code enthält den Fix, auf den CR hingewiesen hat und der einen Punkt wiederholt, den Bill K vor einigen Jahren zum ersten Mal gemacht hat und den ich übersehen habe bis jetzt.]

#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);
}

Und hier ist eine etwas allgemeinere Version, die für Größen mit einer Potenz von 2 funktioniert:

#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);
}

Um test_mask() in eine Allzweckzuweisungsfunktion zu konvertieren, müsste der einzelne Rückgabewert vom Zuweiser die Freigabeadresse codieren, wie mehrere Personen in ihren Antworten angegeben haben.

Probleme mit Interviewern

ri kommentiert: Vielleicht habe ich heute Morgen ein Leseverständnisproblem, aber wenn die Interviewfrage speziell lautet: "Wie würden Sie 1024 Bytes Speicher zuweisen?" Und Sie weisen eindeutig mehr zu. Wäre das nicht ein automatischer Fehler des Interviewers?

Meine Antwort passt nicht in einen Kommentar mit 300 Zeichen ...

Es kommt darauf an, nehme ich an. Ich denke, die meisten Leute (einschließlich mir) haben die Frage mit "Wie würden Sie einen Speicherplatz zuweisen, in dem 1024 Datenbytes gespeichert werden können und in dem die Basisadresse ein Vielfaches von 16 Bytes ist" beantwortet. Wenn der Interviewer wirklich gemeint hat, wie Sie (nur) 1024 Bytes zuordnen und 16 Bytes ausrichten können, sind die Optionen eingeschränkter.

  • Es ist klar, dass eine Möglichkeit darin besteht, 1024 Bytes zuzuweisen und dieser Adresse dann die "Ausrichtungsbehandlung" zu geben; Das Problem bei diesem Ansatz ist, dass der tatsächlich verfügbare Speicherplatz nicht richtig bestimmt wird (der nutzbare Speicherplatz liegt zwischen 1008 und 1024 Byte, es war jedoch kein Mechanismus verfügbar, um die Größe anzugeben), wodurch er weniger als nützlich wird.
  • Eine andere Möglichkeit besteht darin, dass Sie einen vollständigen Speicherzuweiser schreiben und sicherstellen müssen, dass der von Ihnen zurückgegebene 1024-Byte-Block ordnungsgemäß ausgerichtet ist. Wenn dies der Fall ist, führen Sie wahrscheinlich eine Operation aus, die der vorgeschlagenen Lösung ziemlich ähnlich ist, die Sie jedoch im Zuweiser verbergen.

Wenn der Interviewer jedoch eine dieser Antworten erwartet, sollte er erkennen, dass diese Lösung eine eng verwandte Frage beantwortet, und seine Frage neu formulieren, um die Konversation in die richtige Richtung zu lenken. (Wenn der Interviewer wirklich unruhig geworden wäre, dann würde ich den Job nicht wollen. Wenn die Antwort auf eine unzureichend genaue Anforderung ohne Korrektur in Flammen niedergeschossen wird, dann ist der Interviewer nicht jemand, für den es sicher ist, zu arbeiten.)

Die Welt bewegt sich weiter

Der Titel der Frage hat sich kürzlich geändert. Es war Lösen Sie die Speicherausrichtung in C Interview Frage, die mich ratlos. Der überarbeitete Titel ( Wie kann der ausgerichtete Speicher nur mit der Standardbibliothek zugewiesen werden?) erfordert eine leicht überarbeitete Antwort - dieser Nachtrag enthält diese.

C11 (ISO/IEC 9899: 2011) hat die Funktion aligned_alloc() hinzugefügt:

7.22.3.1 Die Funktion aligned_alloc

Inhaltsangabe

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

Beschreibung
Die Funktion aligned_alloc Reserviert Platz für ein Objekt, dessen Ausrichtung durch alignment angegeben wird, dessen Größe durch size angegeben wird und dessen Wert unbestimmt ist. Der Wert von alignment muss eine gültige Ausrichtung sein, die von der Implementierung unterstützt wird, und der Wert von size muss ein ganzzahliges Vielfaches von alignment sein.

Rückgabe
Die Funktion aligned_alloc Gibt entweder einen Nullzeiger oder einen Zeiger auf den zugewiesenen Speicherplatz zurück.

Und POSIX definiert posix_memalign() :

#include <stdlib.h>

int posix_memalign(void **memptr, size_t alignment, size_t size);

BESCHREIBUNG

Die Funktion posix_memalign() weist size Bytes zu, die an einer durch alignment angegebenen Grenze ausgerichtet sind, und gibt einen Zeiger auf den zugewiesenen Speicher in memptr zurück. Der Wert von alignment ist eine Potenz von zwei Vielfachen von sizeof(void *).

Nach erfolgreichem Abschluss muss der Wert, auf den memptr zeigt, ein Vielfaches von alignment sein.

Wenn die Größe des angeforderten Speicherplatzes 0 ist, ist das Verhalten implementierungsdefiniert. Der in memptr zurückgegebene Wert muss entweder ein Nullzeiger oder ein eindeutiger Zeiger sein.

Die Funktion free() soll den Speicher freigeben, der zuvor von posix_memalign() zugewiesen wurde.

RÜCKGABEWERT

Nach erfolgreichem Abschluss soll posix_memalign() Null zurückgeben; Andernfalls wird eine Fehlernummer zurückgegeben, um den Fehler anzuzeigen.

Eine oder beide davon könnten jetzt zur Beantwortung der Frage verwendet werden, aber nur die POSIX-Funktion war eine Option, als die Frage ursprünglich beantwortet wurde.

Hinter den Kulissen erledigt die neue Funktion für den ausgerichteten Speicher fast die gleiche Aufgabe wie in der Frage beschrieben, mit der Ausnahme, dass sie die Ausrichtung einfacher erzwingen und den Anfang des ausgerichteten Speichers intern verfolgen kann, sodass der Code dies nicht tut müssen speziell behandeln - es gibt nur den Speicher frei, der von der Zuordnungsfunktion zurückgegeben wurde, die verwendet wurde.

565

Drei leicht unterschiedliche Antworten, je nachdem, wie Sie die Frage betrachten:

1) Gut genug für die genaue gestellte Frage ist die Lösung von Jonathan Leffler, mit der Ausnahme, dass Sie zum Aufrunden auf 16 ausgerichtete Bytes nur 15 zusätzliche Bytes benötigen, nicht 16.

EIN:

/* 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) Für eine allgemeinere Speicherzuweisungsfunktion möchte der Anrufer nicht zwei Zeiger verfolgen müssen (einen zum Verwenden und einen zum Freigeben). Sie speichern also einen Zeiger auf den "echten" Puffer unterhalb des ausgerichteten Puffers.

EIN:

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]);

Beachten Sie, dass im Gegensatz zu (1), bei dem nur 15 Byte zu mem hinzugefügt wurden, dieser Code die Ausrichtung tatsächlich reduzieren könnte , wenn Ihre Implementierung 32 Byte garantiert Ausrichtung von malloc (unwahrscheinlich, aber theoretisch könnte eine C-Implementierung einen 32-Byte-ausgerichteten Typ haben). Das ist nicht wichtig, wenn Sie nur memset_16aligned aufrufen, aber wenn Sie den Speicher für eine Struktur verwenden, kann dies von Bedeutung sein.

Ich bin mir nicht sicher, was eine gute Lösung dafür ist (außer um den Benutzer zu warnen, dass der zurückgegebene Puffer nicht unbedingt für beliebige Strukturen geeignet ist), da es keine Möglichkeit gibt, programmgesteuert zu bestimmen, wie hoch die Garantie für die implementierungsspezifische Ausrichtung ist. Ich vermute, Sie könnten beim Start zwei oder mehr 1-Byte-Puffer zuweisen und davon ausgehen, dass die schlechteste Ausrichtung die garantierte Ausrichtung ist. Wenn Sie sich irren, verschwenden Sie Speicher. Wenn Sie eine bessere Idee haben, sagen Sie es bitte ...

[ Hinzugefügt : Der 'Standard'-Trick besteht darin, eine Vereinigung von' wahrscheinlich maximal ausgerichteten Typen 'zu erstellen, um die erforderliche Ausrichtung zu bestimmen. Die maximal ausgerichteten Typen sind wahrscheinlich (in C99) 'long long', 'long double', 'void *' Oder 'void (*)(void)'; Wenn Sie <stdint.h> einschließen, könnten Sie vermutlich 'intmax_t' anstelle von long long verwenden (und auf Power 6-Computern (AIX) würde intmax_t geben Sie einen 128-Bit-Integer-Typ). Die Ausrichtungsanforderungen für diese Vereinigung können bestimmt werden, indem sie in eine Struktur mit einem einzelnen Zeichen, gefolgt von der Vereinigung, eingebettet werden:

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;

Sie würden dann die größere der angeforderten Ausrichtung (im Beispiel 16) und den oben berechneten Wert align verwenden.

Unter Solaris 10 (64-Bit) scheint die Grundausrichtung für das Ergebnis von malloc() ein Vielfaches von 32 Byte zu sein.

In der Praxis verwenden ausgerichtete Allokatoren häufig einen Parameter für die Ausrichtung, anstatt fest verdrahtet zu sein. Der Benutzer gibt also die Größe der Struktur ein, die ihm wichtig ist (oder die geringste Potenz von 2, die größer oder gleich dieser ist), und alles wird gut.

3) Verwenden Sie, was Ihre Plattform bietet: posix_memalign Für POSIX, _aligned_malloc Unter Windows.

4) Wenn Sie C11 verwenden, besteht die sauberste - tragbare und übersichtliche - Option darin, die Standardbibliotheksfunktion aligned_alloc zu verwenden, die in dieser Version der Sprachspezifikation eingeführt wurde.

56
Steve Jessop

Sie können auch posix_memalign() versuchen (natürlich auf POSIX-Plattformen).

37
florin

Hier ist eine alternative Herangehensweise an den Aufrundungsteil. Nicht die am besten codierte Lösung, aber sie erledigt die Aufgabe, und diese Art der Syntax ist ein bisschen leichter zu merken (plus würde für Ausrichtungswerte funktionieren, die keine Potenz 2 sind). Das uintptr_t cast war notwendig, um den Compiler zu beschwichtigen; Zeigerarithmetik mag Division oder Multiplikation nicht besonders.

void *mem = malloc(1024 + 15);
void *ptr = (void*) ((uintptr_t) mem + 15) / 16 * 16;
memset_16aligned(ptr, 0, 1024);
free(mem);
19
An̲̳̳drew

Leider scheint es in C99 ziemlich schwierig zu sein, eine Ausrichtung jeglicher Art auf eine Weise zu gewährleisten, die auf jede C-Implementierung übertragbar wäre, die C99 entspricht. Warum? Da nicht garantiert ist, dass ein Zeiger die "Byteadresse" ist, könnte man sich dies bei einem flachen Speichermodell vorstellen. Weder ist die Darstellung von intptr_t so garantiert, das selbst sowieso ein optionaler Typ ist.

Wir kennen vielleicht einige Implementierungen, die eine Repräsentation für void * (und per Definition auch char *) verwenden, die eine einfache Byteadresse ist, aber nach C99 ist sie undurchsichtig für uns, die Programmierer. Eine Implementierung kann einen Zeiger durch ein gesetztes { Segment , Offset } darstellen. wo Offset könnte wer-weiß-was Ausrichtung "in der Realität" haben. Ein Zeiger kann sogar eine Form eines Hash-Tabellen-Suchwerts oder sogar ein Suchwert für verknüpfte Listen sein. Es könnte Informationen über Grenzen verschlüsseln.

In einem aktuellen C1X-Entwurf für einen C-Standard sehen wir das Schlüsselwort _ Alignas. Das könnte ein bisschen helfen.

Die einzige Garantie, die C99 gibt, besteht darin, dass die Speicherzuweisungsfunktionen einen Zeiger zurückgeben, der für die Zuordnung zu einem Zeiger geeignet ist, der auf einen beliebigen Objekttyp zeigt. Da wir die Ausrichtung von Objekten nicht spezifizieren können, können wir unsere eigenen Zuordnungsfunktionen mit der Verantwortung für die Ausrichtung nicht auf eine gut definierte, tragbare Weise implementieren.

Es wäre gut, sich über diese Behauptung zu irren.

18
Shao

Die tatsächliche Zahl, die Sie hinzufügen müssen, um eine Ausrichtung von N zu erhalten, ist max (0, NM) wobei M die natürliche Ausrichtung des Speicherzuordners ist (und beide sind Potenzen von 2).

Da die minimale Speicherausrichtung eines Allokators 1 Byte beträgt, ist 15 = max (0,16-1) eine konservative Antwort. Wenn Sie jedoch wissen, dass Ihr Arbeitsspeicher-Allokator 32-Bit-Int-Aligned-Adressen liefert (was ziemlich häufig vorkommt), hätten Sie 12 als Pad verwenden können.

Dies ist für dieses Beispiel nicht wichtig, kann aber bei einem eingebetteten System mit 12 KB RAM wichtig sein, bei dem jedes einzelne gespeicherte int zählt.

Die beste Methode, um es zu implementieren, wenn Sie tatsächlich versuchen, jedes mögliche Byte zu speichern, ist ein Makro, mit dem Sie die native Speicherausrichtung festlegen können. Auch dies ist wahrscheinlich nur für eingebettete Systeme nützlich, in denen Sie jedes Byte speichern müssen.

In dem folgenden Beispiel ist auf den meisten Systemen der Wert 1 in Ordnung für MEMORY_ALLOCATOR_NATIVE_ALIGNMENT Für unser theoretisches eingebettetes System mit 32-Bit-ausgerichteten Zuweisungen könnte jedoch Folgendes ein kleines bisschen wertvollen Speicher einsparen:

#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

Vielleicht wären sie mit einer Kenntnis von memalign zufrieden gewesen? Und wie Jonathan Leffler betont, gibt es zwei neuere vorzuziehende Funktionen, die es zu kennen gilt.

Ups, Florin hat mich geschlagen. Wenn Sie jedoch die von mir verlinkte Manpage lesen, werden Sie höchstwahrscheinlich das Beispiel eines früheren Posters verstehen.

8
Don Wakefield

Ich bin überrascht, dass niemand dafür gestimmt hat Shao 's Antwort dass es nach meinem Verständnis unmöglich ist, das zu tun, was in Standard C99 verlangt wird, da ein Zeiger in ein Integral konvertiert wurde Typ ist formal undefiniertes Verhalten. (Abgesehen von dem Standard, der die Konvertierung von uintptr_t <-> void* Erlaubt, scheint es der Standard jedoch nicht zu erlauben, den Wert uintptr_t Zu manipulieren und ihn dann zurück zu konvertieren. )

5
Lutorm

Bei Accelerate.framework, einer stark vektorisierten OS X/iOS-Bibliothek, müssen wir ständig auf die Ausrichtung achten. Es gibt einige Optionen, von denen ich eine oder zwei oben nicht gesehen habe.

Die schnellste Methode für ein kleines Array wie dieses ist, es einfach auf den Stapel zu kleben. Mit GCC/clang:

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

Kein free () erforderlich. Dies sind normalerweise zwei Anweisungen: Subtrahiere 1024 vom Stapelzeiger und dann UND den Stapelzeiger mit -Alignment. Vermutlich benötigte der Anforderer die Daten auf dem Heap, weil die Lebensdauer des Arrays den Stack überschritt oder die Rekursion in Betrieb ist oder der Stack-Platz sehr knapp ist.

Unter OS X/iOS werden alle Anrufe an malloc/calloc/etc. sind immer 16 Byte ausgerichtet. Wenn Sie beispielsweise 32 Byte für AVX benötigen, können Sie posix_memalign verwenden:

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

Einige Leute haben die C++ - Schnittstelle erwähnt, die ähnlich funktioniert.

Es sollte nicht vergessen werden, dass Seiten auf große Zweierpotenzen ausgerichtet sind, so dass seitenausgerichtete Puffer auch 16 Byte ausgerichtet sind. Daher sind mmap () und valloc () und andere ähnliche Schnittstellen ebenfalls Optionen. mmap () hat den Vorteil, dass der Puffer auf Wunsch vorinitialisiert mit einem Wert ungleich Null belegt werden kann. Da diese die Größe einer ausgerichteten Seite haben, erhalten Sie nicht die Mindestzuordnung von diesen und es wird wahrscheinlich ein VM Fehler auftreten, wenn Sie es zum ersten Mal berühren.

Cheesy: Schalten Sie Guard Malloc oder ähnliches ein. Puffer mit einer Größe von n * 16 Bytes wie diesem werden mit n * 16 Bytes ausgerichtet, da VM zum Abfangen von Überläufen verwendet wird und sich die Grenzen an den Seitengrenzen befinden.

Einige Accelerate.framework-Funktionen benötigen einen vom Benutzer angegebenen temporären Puffer, um ihn als Arbeitsspeicherplatz zu verwenden. Hier müssen wir davon ausgehen, dass der Puffer, der uns übergeben wurde, stark falsch ausgerichtet ist und der Benutzer aktiv versucht, unser Leben trotz allem zu erschweren. (Unsere Testfälle kleben eine Schutzseite direkt vor und nach dem temporären Puffer, um den Trotz zu unterstreichen.) Hier geben wir die Mindestgröße zurück, die wir benötigen, um ein 16-Byte-ausgerichtetes Segment irgendwo darin zu gewährleisten, und richten den Puffer anschließend manuell aus. Diese Größe ist Gewünschte_Größe + Ausrichtung - 1. In diesem Fall ist das also 1024 + 16 - 1 = 1039 Bytes. Dann wie folgt ausrichten:

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

Durch Hinzufügen von Ausrichtung-1 wird der Zeiger an der ersten ausgerichteten Adresse vorbeibewegt, und durch UND-Verknüpfung mit Ausrichtung (z. B. 0xfff ... ff0 für Ausrichtung = 16) wird er wieder an die ausgerichtete Adresse zurückgebracht.

Wie in anderen Beiträgen beschrieben, können Sie auf anderen Betriebssystemen ohne 16-Byte-Ausrichtungsgarantie malloc mit der größeren Größe aufrufen, den Zeiger später kostenlos beiseite stellen (), dann wie oben beschrieben ausrichten und den ausgerichteten Zeiger verwenden, ähnlich wie beschrieben für unseren temporären Pufferfall.

Was aligned_memset betrifft, ist dies ziemlich albern. Sie müssen nur bis zu 15 Bytes einschleifen, um eine ausgerichtete Adresse zu erreichen, und anschließend mit ausgerichteten Speichern fortfahren, die am Ende möglicherweise einen Bereinigungscode enthalten. Sie können die Bereinigungsbits sogar im Vektorcode ausführen, entweder als nicht ausgerichtete Speicher, die den ausgerichteten Bereich überlappen (vorausgesetzt, die Länge entspricht mindestens der Länge eines Vektors), oder mit etwas wie movmaskdqu. Jemand ist nur faul. Es ist jedoch wahrscheinlich eine vernünftige Interviewfrage, wenn der Interviewer wissen möchte, ob Sie mit stdint.h, bitweisen Operatoren und Speichergrundlagen vertraut sind, damit das erfundene Beispiel vergeben werden kann.

5
Ian Ollmann

Das erste, was mir beim Lesen dieser Frage in den Sinn kam, war, eine ausgerichtete Struktur zu definieren, sie zu instanziieren und dann darauf zu verweisen.

Gibt es einen fundamentalen Grund, warum ich vermisse, da dies sonst niemand vorgeschlagen hat?

Als Randbemerkung sehe ich, da ich ein Array von Zeichen verwendet habe (vorausgesetzt, das Zeichen des Systems ist 8 Bits (dh 1 Byte)), die Notwendigkeit für die Funktion __attribute__((packed)) nicht unbedingt (korrigiere mich, wenn ich es bin) falsch), aber ich steckte es trotzdem ein.

Dies funktioniert auf zwei Systemen, auf denen ich es ausprobiert habe, aber es ist möglich, dass es eine Compileroptimierung gibt, die mir nicht bewusst ist, dass ich im Hinblick auf die Wirksamkeit des Codes falsch positive Ergebnisse erhalte. Ich benutzte gcc 4.9.2 unter OSX und gcc 5.2.1 unter 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

verwendung von memalign, Aligned-Memory-Blocks könnte eine gute Lösung für das Problem sein.

3
neuron

MacOS X spezifisch:

  1. Alle mit malloc zugewiesenen Zeiger sind mit 16 Bytes ausgerichtet.
  2. C11 wird unterstützt, daher können Sie align_malloc (16, size) aufrufen.

  3. MacOS X wählt Code aus, der für einzelne Prozessoren beim Booten für memset, memcpy und memmove optimiert ist, und dieser Code verwendet Tricks, von denen Sie noch nie gehört haben, um ihn schnell zu machen. 99% ige Chance, dass Memset schneller ausgeführt wird als jedes handgeschriebene Memset16, wodurch die gesamte Frage sinnlos wird.

Wenn Sie eine 100% tragbare Lösung wünschen, gibt es vor C11 keine. Weil es keine tragbare Möglichkeit gibt, die Ausrichtung eines Zeigers zu testen. Wenn es nicht zu 100% portabel sein muss, können Sie es verwenden

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

Dies setzt voraus, dass die Ausrichtung eines Zeigers in den niedrigsten Bits gespeichert ist, wenn ein Zeiger in ein vorzeichenloses int konvertiert wird. Bei der Konvertierung in unsigned int gehen Informationen verloren und die Implementierung ist definiert. Dies spielt jedoch keine Rolle, da das Ergebnis nicht zurück in einen Zeiger konvertiert wird.

Das Schreckliche ist natürlich, dass der ursprüngliche Zeiger irgendwo gespeichert werden muss, um free () damit aufrufen zu können. Alles in allem würde ich die Weisheit dieses Entwurfs wirklich bezweifeln.

1
Chris

Verwenden Sie einfach memalign? http://linux.die.net/man/3/memalign

0
PhonicUK

Für die Lösung verwendete ich ein Konzept des Auffüllens, das den Speicher ausrichtet und nicht den Speicher eines einzelnen Bytes verschwendet.

Wenn es Einschränkungen gibt, können Sie kein einziges Byte verschwenden. Alle mit malloc zugewiesenen Zeiger sind mit 16 Bytes ausgerichtet.

C11 wird unterstützt, Sie können also einfach aligned_alloc (16, size) aufrufen.

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

Sie können auch 16 Bytes hinzufügen und dann den ursprünglichen ptr auf 16bit ausrichten, indem Sie den (16-mod) wie folgt unter den Zeiger setzen:

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

Wenn es Einschränkungen gibt, die Sie nicht ein einziges Byte verschwenden können, funktioniert diese Lösung: Hinweis: Es gibt einen Fall, in dem dies unbegrenzt ausgeführt werden kann: 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