Orijinal cevap
{
void *mem = malloc(1024+16);
void *ptr = ((char *)mem+16) & ~ 0x0F;
memset_16aligned(ptr, 0, 1024);
free(mem);
}
Sabit cevap
{
void *mem = malloc(1024+15);
void *ptr = ((uintptr_t)mem+15) & ~ (uintptr_t)0x0F;
memset_16aligned(ptr, 0, 1024);
free(mem);
}
İstendiği gibi açıklama
İlk adım, her ihtimale karşı yeterli boş alan tahsis etmektir. Bellek 16 bayt hizalı olması gerektiğinden (önde gelen bayt adresinin 16'nın katı olması gerekir), 16 ekstra bayt eklemek yeterli alanımız olduğunu garanti eder. İlk 16 baytın bir yerinde 16 baytlık hizalanmış bir işaretçi vardır. (Not malloc()
yeterince iyi için hizalanmış bir işaretçi dönmek gerekiyordu herhangi . Amaç Bununla birlikte 'Herhangi bir' anlamı temel tipi gibi şeyler için öncelikle - long
, double
, long double
, long long
., Ve nesnelere işaretçiler ve işaretçileri işlevlerine sen olduğunda grafik sistemleriyle oynamak gibi daha özel şeyler yapmak, sistemin geri kalanından daha sıkı bir hizalamaya ihtiyaç duyabilirler - bu nedenle böyle sorular ve cevaplar.)
Bir sonraki adım void işaretçisini bir char işaretçisine dönüştürmektir; GCC'ye rağmen, boş işaretçilerde işaretçi aritmetiği yapmanız gerekmez (ve GCC'nin kötüye kullandığınızı size bildirmek için uyarı seçenekleri vardır). Ardından başlangıç işaretçisine 16 ekleyin. Diyelim ki malloc()
size inanılmaz derecede hizalanmış bir işaretçi döndürdünüz: 0x800001. 16 eklenmesi 0x800011 verir. Şimdi 16 bayt sınırına yuvarlamak istiyorum - bu yüzden son 4 biti 0'a sıfırlamak istiyorum. 0x0F son 4 biti bire ayarladı; bu nedenle, ~0x0F
son dört hariç tüm bitler bire ayarlanmıştır. Ve bunu 0x800011 ile 0x800010 verir. Diğer ofsetleri tekrarlayabilir ve aynı aritmetik çalıştığını görebilirsiniz.
Son adım, free()
her zaman ve sadece, dönüş: kolaydır free()
bir değere o biri malloc()
, calloc()
ya da realloc()
size iade - başka bir şey tam bir felaket. Bu mem
değeri tutmayı doğru bir şekilde sağladınız - teşekkürler. Ücretsiz serbest bırakır.
Son olarak, sisteminizin malloc
paketinin içindekileri biliyorsanız , 16 baytlık hizalanmış veriyi geri gönderebileceğini (veya 8 baytlık hizalanmış olabilir) tahmin edebilirsiniz. 16 baytlık hizalanmışsa, değerlerle bağlantı kurmanız gerekmez. Bununla birlikte, bu tehlikeli ve taşınabilir değildir - diğer malloc
paketler farklı minimum hizalamalara sahiptir ve bu nedenle farklı bir şey yaptığında bir şeyin varsayılması çekirdek dökümlere yol açacaktır. Geniş sınırlar içinde bu çözüm taşınabilirdir.
Başka biri posix_memalign()
hizalanmış belleği almanın başka bir yolu olarak bahsetti ; bu her yerde mevcut değildir, ancak genellikle bunu temel alarak uygulanabilir. Hizalamanın 2'lik bir güç olması uygun olduğunu unutmayın; diğer hizalamalar daha karışıktır.
Bir yorum daha - bu kod, ayırmanın başarılı olup olmadığını kontrol etmez.
düzeltme
Windows Programmer , işaretçiler üzerinde bit maskesi işlemleri yapamayacağınıza dikkat çekti ve gerçekten de GCC (3.4.6 ve 4.3.1 test edildi) böyle şikayet ediyor. Böylece, ana kodun değiştirilmiş bir sürümü - ana programa dönüştürülür. Ayrıca, belirtildiği gibi, 16 yerine sadece 15 ekleme özgürlüğünü aldım. Ben kullanıyorum uintptr_t
C99 yeterince uzun çoğu platformlarda erişilebilir olmasını yana yaklaşık edilmiş. Bunun kullanımı için değilse PRIXPTR
de printf()
ifadeleri, bu yeterli olacaktır #include <stdint.h>
kullanmak yerine #include <inttypes.h>
. [Bu kod, birkaç yıl önce Bill K tarafından ilk kez yapılan ve şimdiye kadar göz ardı edebildiğim bir noktayı yineleyen CR'nin işaret ettiği düzeltmeyi içeriyor .]
#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);
}
Ve işte marjinal olarak daha genelleştirilmiş bir versiyon, 2 gücü olan boyutlar için çalışacak:
#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);
}
test_mask()
Genel amaçlı bir tahsis fonksiyonuna dönüştürmek için , birkaç kişi cevaplarında belirttiği gibi, ayırıcıdan gelen tek dönüş değerinin, çıkış adresini kodlaması gerekecektir.
Görüşmecilerle ilgili sorunlar
Uri şöyle dedi: Belki bu sabah [a] okuduğunu anlama problemi yaşıyorum, ancak görüşme sorusu özellikle şöyle diyorsa: "1024 bayt belleği nasıl tahsis edersiniz" ve açıkça bundan daha fazlasını tahsis edersiniz. Bu, görüşmeciden gelen otomatik bir başarısızlık olmaz mı?
Yanıtım 300 karakterlik bir yoruma sığmayacak ...
Sanırım buna bağlı. Sanırım çoğu insan (ben dahil) "1024 baytlık verilerin depolanabileceği ve temel adresin 16 baytın katı olduğu bir alanı nasıl tahsis edersiniz?" Görüşmeci gerçekten 1024 baytı (yalnızca) nasıl ayırabileceğinizi ve 16 baytlık hizalayabileceğinizi kastediyorsa, seçenekler daha sınırlıdır.
- Açıkçası, bir olasılık 1024 bayt tahsis etmek ve daha sonra bu adrese 'hizalama tedavisi' vermek; bu yaklaşımla ilgili sorun, gerçek kullanılabilir alanın düzgün bir şekilde belirlenmemesidir (kullanılabilir alan 1008 ila 1024 bayt arasındadır, ancak hangi boyutu belirtmek için kullanılabilir bir mekanizma yoktu), bu da onu kullanışlı kılar.
- Başka bir olasılık, tam bellek ayırıcı yazmanız ve geri döndüğünüz 1024 baytlık bloğun uygun şekilde hizalandığından emin olmanızdır. Bu durumda, muhtemelen önerilen çözümün yaptıklarına oldukça benzer bir işlem yapıyorsunuz, ancak bunu ayırıcı içinde saklıyorsunuz.
Ancak, görüşmeci bu yanıtlardan herhangi birini beklerse, bu çözümün yakından ilgili bir soruyu cevapladığını fark etmelerini ve daha sonra sohbeti doğru yöne yönlendirmek için sorularını yeniden düzenlemelerini beklerdim. (Ayrıca, görüşmeci gerçekten okşadıysa, işi istemezdim; eğer yeterince kesin bir gereksinimin cevabı düzeltilmeden alevlerde vurulursa, görüşmeci çalışmanın güvenli olduğu biri değildir.)
Dünya devam ediyor
Sorunun başlığı son zamanlarda değişti. Beni röportaj C görüşme soru bellek hizalama çözmek oldu . Revize edilmiş başlık ( sadece standart kütüphaneyi kullanarak hizalanmış bellek nasıl tahsis edilir? ) Biraz revize edilmiş bir cevap gerektirir - bu ek sağlar.
C11 (ISO / IEC 9899: 2011) eklenen fonksiyon aligned_alloc()
:
7.22.3.1 aligned_alloc
İşlev
özet
#include <stdlib.h>
void *aligned_alloc(size_t alignment, size_t size);
Açıklama hizalama ile belirtilen bir nesne için işlev ayırır alan boyutu ile belirlenir, ve değeri belirsizdir. Değeri , uygulama tarafından desteklenen geçerli bir hizalama ve değeri, ayrılmaz bir katı olacaktır .
aligned_alloc
alignment
size
alignment
size
alignment
İade işlev döner bir boş gösterici veya ayrılmış alana bir işaretçi ya.
aligned_alloc
POSIX posix_memalign()
şunları tanımlar :
#include <stdlib.h>
int posix_memalign(void **memptr, size_t alignment, size_t size);
AÇIKLAMA
posix_memalign()
Fonksiyonu tahsis edecektir size
tarafından belirlenen bir sınır üzerindeki hizalanmış bayt alignment
ve ayrılan bellek için bir işaretçi iade eder memptr
. Değeri alignment
iki katının gücüdür sizeof(void *)
.
Başarılı bir şekilde tamamlanmasının ardından, işaret ettiği değerin memptr
katları olacaktır alignment
.
İstenen alanın boyutu 0 ise, davranış uygulama tanımlıdır; döndürülen değer memptr
bir boş gösterici veya benzersiz bir işaretçi olmalıdır.
free()
Fonksiyon daha önce tahsis edilmiş bellek ayırması eder posix_memalign()
.
GERİ DÖNÜŞ DEĞERİ
Başarılı bir şekilde tamamlanınca posix_memalign()
sıfır döner; aksi halde, hatayı belirtmek için bir hata numarası döndürülecektir.
Bunların biri veya her ikisi de şimdi soruyu cevaplamak için kullanılabilir, ancak soru orijinal olarak cevaplandığında sadece POSIX işlevi bir seçimdi.
Sahnelerin arkasında, yeni hizalanmış bellek işlevi, hizalamayı daha kolay zorlama ve hizalanmış belleğin başlangıcını dahili olarak takip edebilmeleri için, soruda özetlenenle aynı işi yapar. özel olarak uğraşmak zorunda - sadece kullanılan ayırma işlevi tarafından döndürülen belleği serbest bırakır.