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 zuzuweisen. Da der Speicher 16-Byte-ausgerichtet sein muss (was bedeutet, dass die Adresse des führenden Bytes ein Vielfaches von 16 sein muss), garantiert das Hinzufügen von 16 zusätzlichen Bytes, dass wir über genügend Speicherplatz verfügen. Irgendwo in den ersten 16 Bytes befindet sich ein auf 16 Bytes ausgerichteter Zeiger. (Beachten Sie, dass malloc()
angeblich einen Zeiger zurückzugeben , die gut genug für ausgerichtet ist jeder . Zweck jedoch die Bedeutung von ‚any‘ ist in erster Linie für Dinge wie Grundtypen - long
, double
, long double
, long long
., Und Zeiger auf Objekte und Zeiger auf Funktionen Wenn Sie sind Wenn sie speziellere Dinge tun, wie das Spielen mit Grafiksystemen, müssen sie möglicherweise strenger ausgerichtet werden als der Rest des Systems - daher Fragen und Antworten wie diese.)
Der nächste Schritt besteht darin, den void-Zeiger in einen char-Zeiger umzuwandeln. Ungeachtet des GCC sollten Sie keine Zeigerarithmetik für leere Zeiger durchführen (und GCC verfügt über Warnoptionen, die Sie darüber informieren, wenn Sie es missbrauchen). Fügen Sie dann 16 zum Startzeiger hinzu. Angenommen, malloc()
Sie haben einen unglaublich schlecht 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. Bei 0x0F sind die letzten 4 Bits auf eins gesetzt. Daher sind ~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 sehen, dass dieselbe Arithmetik funktioniert.
Der letzte Schritt, free()
ist einfach: Sie immer, und nur, Rückkehr zu free()
einem Wert , dass einer malloc()
, calloc()
oder realloc()
an Sie zurückgeschickt - alles andere ist eine Katastrophe. Sie haben richtig angegeben mem
, um diesen Wert zu halten - danke. Das kostenlose veröffentlicht es.
Wenn Sie die Interna des Systems kennen malloc
, können Sie davon ausgehen, dass es möglicherweise 16-Byte-ausgerichtete Daten zurückgibt (oder 8-Byte-ausgerichtet ist). Wenn es 16-Byte-ausgerichtet wäre, müssten Sie nicht mit den Werten dink. Dies ist jedoch zweifelhaft und nicht portabel - andere malloc
Pakete haben unterschiedliche Mindestausrichtungen, und daher würde die Annahme einer Sache, wenn sie etwas anderes tun, zu Core-Dumps führen. In weiten Grenzen ist diese Lösung portabel.
Jemand anderes erwähnte posix_memalign()
als einen anderen Weg, um das ausgerichtete Gedächtnis 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 für Zeiger ausführen können, und tatsächlich beschwert sich GCC (3.4.6 und 4.3.1 getestet) so. Es folgt also 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
C99 seit langem, um auf den meisten Plattformen verfügbar zu sein. Wenn es nicht für die Verwendung PRIXPTR
in den printf()
Anweisungen wäre, würde es ausreichen, #include <stdint.h>
anstatt zu verwenden #include <inttypes.h>
. [Dieser Code enthält die Korrektur, auf die CR hingewiesen hat und die einen Punkt wiederholte, den Bill K vor einigen Jahren zum ersten Mal gemacht hatte und den ich bisher übersehen habe.]
#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 geringfügig 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 umzuwandeln , müsste der einzelne Rückgabewert des Zuweisers die Freigabeadresse codieren, wie mehrere Personen in ihren Antworten angegeben haben.
Probleme mit Interviewern
Uri kommentierte: Vielleicht habe ich heute Morgen ein Problem mit dem Leseverständnis, aber wenn die Interviewfrage speziell sagt: "Wie würden Sie 1024 Bytes Speicher zuweisen?" Und Sie weisen eindeutig mehr als das 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 so verstanden: "Wie würden Sie einen Speicherplatz zuweisen, in dem 1024 Byte Daten gespeichert werden können und in dem die Basisadresse ein Vielfaches von 16 Byte ist?". Wenn der Interviewer wirklich gemeint hat, wie Sie 1024 Bytes (nur) zuweisen und 16 Bytes ausrichten können, sind die Optionen eingeschränkter.
- Eine Möglichkeit besteht eindeutig darin, 1024 Bytes zuzuweisen und dieser Adresse dann die "Ausrichtungsbehandlung" zu geben. Das Problem bei diesem Ansatz besteht darin, dass der tatsächlich verfügbare Speicherplatz nicht richtig bestimmt wird (der nutzbare Speicherplatz liegt zwischen 1008 und 1024 Byte, aber es war kein Mechanismus verfügbar, um die Größe anzugeben), was ihn weniger nützlich macht.
- Eine andere Möglichkeit besteht darin, dass von Ihnen erwartet wird, dass Sie einen vollständigen Speicherzuweiser schreiben und sicherstellen, dass der von Ihnen zurückgegebene 1024-Byte-Block entsprechend ausgerichtet ist. Wenn dies der Fall ist, führen Sie wahrscheinlich einen Vorgang aus, der dem der vorgeschlagenen Lösung ziemlich ähnlich ist, aber Sie verstecken ihn im Allokator.
Wenn der Interviewer jedoch eine dieser Antworten erwartet, würde ich erwarten, dass er erkennt, dass diese Lösung eine eng verwandte Frage beantwortet, und dann seine Frage neu formuliert, um das Gespräch in die richtige Richtung zu lenken. (Wenn der Interviewer wirklich schlampig geworden wäre, würde ich den Job nicht wollen. Wenn die Antwort auf eine unzureichend genaue Anforderung ohne Korrektur in Flammen niedergeschossen wird, 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 die Lösung der Gedächtnisausrichtung in der C-Interview-Frage, die mich verblüffte . Der überarbeitete Titel ( Wie ordne ich ausgerichteten Speicher nur mit der Standardbibliothek zu? ) Erfordert eine leicht überarbeitete Antwort - dieses Addendum enthält sie.
C11 (ISO / IEC 9899: 2011) hinzugefügte Funktion aligned_alloc()
:
7.22.3.1 Die aligned_alloc
Funktion
Zusammenfassung
#include <stdlib.h>
void *aligned_alloc(size_t alignment, size_t size);
Beschreibung
Die aligned_alloc
Funktion weist einem Objekt Platz zu, dessen Ausrichtung durch angegeben ist alignment
, dessen Größe durch angegeben size
ist 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 sein alignment
.
Gibt
Die aligned_alloc
Funktion gibt entweder einen Null - Zeiger oder einen Zeiger auf den zugewiesenen Platz.
Und POSIX definiert posix_memalign()
:
#include <stdlib.h>
int posix_memalign(void **memptr, size_t alignment, size_t size);
BESCHREIBUNG
Die posix_memalign()
Funktion weist size
Bytes zu, die an einer durch angegebenen Grenze ausgerichtet sind alignment
, und gibt einen Zeiger auf den zugewiesenen Speicher in zurück memptr
. Der Wert von alignment
ist eine Potenz von zwei Vielfachen von sizeof(void *)
.
Nach erfolgreichem Abschluss muss der von angegebene Wert memptr
ein Vielfaches von sein alignment
.
Wenn die Größe des angeforderten Speicherplatzes 0 ist, ist das Verhalten implementierungsdefiniert. Der zurückgegebene Wert memptr
muss entweder ein Nullzeiger oder ein eindeutiger Zeiger sein.
Die free()
Funktion muss den zuvor zugewiesenen Speicher freigeben posix_memalign()
.
RÜCKGABEWERT
Nach erfolgreichem Abschluss posix_memalign()
wird Null zurückgegeben; Andernfalls wird eine Fehlernummer zurückgegeben, um den Fehler anzuzeigen.
Eine oder beide könnten verwendet werden, um die Frage jetzt zu beantworten, aber nur die POSIX-Funktion war eine Option, als die Frage ursprünglich beantwortet wurde.
Hinter den Kulissen erledigt die neue Funktion für ausgerichteten Speicher fast die gleiche Aufgabe wie in der Frage beschrieben, außer dass sie die Ausrichtung einfacher erzwingen und den Start des ausgerichteten Speichers intern verfolgen kann, damit der Code dies nicht tut müssen sich speziell damit befassen - es gibt nur den Speicher frei, der von der verwendeten Zuordnungsfunktion zurückgegeben wird.