Bagaimana cara mengalokasikan memori selaras hanya menggunakan perpustakaan standar?

422

Saya baru saja menyelesaikan tes sebagai bagian dari wawancara kerja, dan satu pertanyaan mengejutkan saya, bahkan menggunakan Google untuk referensi. Saya ingin melihat apa yang dapat dilakukan oleh kru StackOverflow dengannya:

The memset_16alignedFungsi membutuhkan 16-byte selaras pointer berlalu untuk itu, atau akan crash.

a) Bagaimana Anda mengalokasikan 1024 byte memori, dan menyelaraskannya ke batas 16 byte?
b) Bebaskan memori setelah memset_16alignedeksekusi.

{    
   void *mem;
   void *ptr;

   // answer a) here

   memset_16aligned(ptr, 0, 1024);

   // answer b) here    
}
JimDaniel
sumber
89
hmmm ... untuk kelayakan kode jangka panjang, bagaimana dengan "Tembak siapa pun yang menulis memset_16 menyelaraskan dan menggantinya atau menggantinya sehingga tidak memiliki kondisi batas yang aneh"
Steven A. Lowe
29
Tentu saja pertanyaan yang valid untuk ditanyakan - "mengapa penyelarasan memori aneh". Tetapi bisa ada alasan bagus untuk itu - dalam hal ini, bisa jadi memset_16aligned () dapat menggunakan integer 128-bit dan ini lebih mudah jika memori diketahui selaras. Dll
Jonathan Leffler
5
Siapa pun yang menulis memset dapat menggunakan penyelarasan 16 byte internal untuk membersihkan loop dalam dan prolog / epilog data kecil untuk membersihkan ujung yang tidak selaras. Itu akan jauh lebih mudah daripada membuat coders menangani pointer memori tambahan.
Adisak
8
Mengapa seseorang ingin data selaras dengan batas 16 byte? Mungkin untuk memuatnya ke register 128E SSE. Saya percaya movs baru (yang tidak selaras) (misalnya, movupd, lddqu) lebih lambat, atau mungkin mereka menargetkan prosesor tanpa SSE2 / 3
11
Menyelaraskan alamat mengarah pada penggunaan cache yang dioptimalkan serta bandwidth yang lebih tinggi antara berbagai tingkat cache dan RAM (untuk sebagian besar beban kerja umum). Lihat di sini stackoverflow.com/questions/381244/purpose-of-memory-alignment
Deepthought

Jawaban:

587

Jawaban asli

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

Jawaban tetap

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

Penjelasan seperti yang diminta

Langkah pertama adalah mengalokasikan ruang cadangan yang cukup, untuk berjaga-jaga. Karena memori harus selaras 16-byte (artinya alamat byte utama harus kelipatan 16), menambahkan 16 byte tambahan menjamin bahwa kami memiliki cukup ruang. Di suatu tempat di 16 byte pertama, ada pointer 16 byte yang disejajarkan. (Perhatikan bahwa malloc()seharusnya mengembalikan pointer yang cukup baik selaras untuk setiap . Tujuan Namun, arti dari 'setiap' terutama untuk hal-hal seperti dasar jenis - long, double, long double, long long., Dan pointer ke objek dan pointer ke fungsi Bila Anda melakukan hal-hal yang lebih khusus, seperti bermain dengan sistem grafis, mereka dapat membutuhkan lebih banyak keselarasan yang ketat daripada sistem lainnya - karenanya pertanyaan dan jawaban seperti ini.)

Langkah selanjutnya adalah mengonversi void pointer ke char pointer; Meskipun demikian, GCC, Anda tidak seharusnya melakukan aritmatika penunjuk pada pointer kosong (dan GCC memiliki opsi peringatan untuk memberi tahu Anda ketika Anda menyalahgunakannya). Kemudian tambahkan 16 ke pointer mulai. Misalkan malloc()mengembalikan Anda pointer yang sangat tidak selaras: 0x800001. Menambahkan 16 memberi 0x800011. Sekarang saya ingin membulatkan batas 16-byte - jadi saya ingin mengatur ulang 4 bit terakhir ke 0. 0x0F memiliki 4 bit terakhir yang disetel menjadi satu; oleh karena itu, ~0x0Ftetapkan semua bit ke satu kecuali empat terakhir. Anding itu dengan 0x800011 memberi 0x800010. Anda dapat beralih dari offset lain dan melihat bahwa aritmatika yang sama berfungsi.

Langkah terakhir,, free()mudah: Anda selalu, dan hanya, kembali ke free()nilai yang salah satunya malloc(), calloc()atau realloc()kembali kepada Anda - hal lain adalah bencana. Anda menyediakan dengan benar memuntuk memegang nilai itu - terima kasih. Gratis merilisnya.

Akhirnya, jika Anda tahu tentang internal mallocpaket sistem Anda, Anda bisa menebak bahwa itu mungkin mengembalikan data sejajar 16-byte (atau mungkin sejajar 8-byte). Jika sejajar 16 byte, maka Anda tidak perlu bingung dengan nilai-nilai tersebut. Namun, ini cerdik dan non-portabel - mallocpaket lain memiliki keberpihakan minimum yang berbeda, dan oleh karena itu mengasumsikan satu hal ketika melakukan sesuatu yang berbeda akan menyebabkan dump inti. Dalam batas luas, solusi ini portabel.

Orang lain disebut posix_memalign()sebagai cara lain untuk mendapatkan memori yang selaras; yang tidak tersedia di mana-mana, tetapi seringkali dapat diimplementasikan menggunakan ini sebagai dasar. Perhatikan bahwa itu nyaman bahwa perataan adalah kekuatan 2; keberpihakan lainnya berantakan.

Satu komentar lagi - kode ini tidak memeriksa apakah alokasi berhasil.

Amandemen

Windows Programmer menunjukkan bahwa Anda tidak dapat melakukan operasi topeng bit pada pointer, dan, memang, GCC (3.4.6 dan 4.3.1 diuji) tidak mengeluh seperti itu. Jadi, versi kode dasar yang diubah - diubah menjadi program utama, mengikuti. Saya juga mengambil kebebasan untuk menambahkan hanya 15 bukan 16, seperti yang telah ditunjukkan. Saya menggunakan uintptr_tkarena C99 sudah ada cukup lama untuk dapat diakses di sebagian besar platform. Jika bukan karena penggunaan PRIXPTRdalam printf()pernyataan, itu sudah cukup untuk #include <stdint.h>bukan menggunakan #include <inttypes.h>. [Kode ini termasuk perbaikan yang ditunjukkan oleh CR , yang mengulangi poin yang pertama kali dibuat oleh Bill K beberapa tahun yang lalu, yang berhasil saya abaikan sampai sekarang.]

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

Dan ini adalah versi yang sedikit lebih umum, yang akan bekerja untuk ukuran yang merupakan kekuatan 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);
}

Untuk mengkonversi test_mask()ke fungsi alokasi tujuan umum, nilai pengembalian tunggal dari pengalokasi harus meng-encode alamat rilis, seperti yang ditunjukkan beberapa orang dalam jawaban mereka.

Masalah dengan pewawancara

Uri berkomentar: Mungkin saya mengalami masalah pemahaman membaca pagi ini, tetapi jika pertanyaan wawancara secara khusus mengatakan: "Bagaimana Anda mengalokasikan 1024 byte memori" dan Anda jelas mengalokasikan lebih dari itu. Bukankah itu merupakan kegagalan otomatis dari pewawancara?

Respons saya tidak akan cocok dengan komentar 300 karakter ...

Tergantung, kurasa. Saya pikir kebanyakan orang (termasuk saya) mengambil pertanyaan yang berarti "Bagaimana Anda mengalokasikan ruang di mana 1024 byte data dapat disimpan, dan di mana alamat basis adalah kelipatan 16 byte". Jika pewawancara benar-benar bermaksud bagaimana Anda dapat mengalokasikan 1024 byte (hanya) dan membuatnya sejajar 16-byte, maka opsi lebih terbatas.

  • Jelas, satu kemungkinan adalah mengalokasikan 1024 byte dan kemudian memberikan alamat itu 'perlakuan penyelarasan'; masalah dengan pendekatan itu adalah bahwa ruang yang tersedia sebenarnya tidak ditentukan dengan benar (ruang yang dapat digunakan adalah antara 1008 dan 1024 byte, tetapi tidak ada mekanisme yang tersedia untuk menentukan ukuran mana), yang menjadikannya kurang bermanfaat.
  • Kemungkinan lain adalah bahwa Anda diharapkan untuk menulis alokasi memori penuh dan memastikan bahwa blok 1024-byte yang Anda kembalikan selaras. Jika itu masalahnya, Anda mungkin akhirnya melakukan operasi yang cukup mirip dengan apa yang dilakukan solusi yang diusulkan, tetapi Anda menyembunyikannya di dalam pengalokasi.

Namun, jika pewawancara mengharapkan salah satu dari tanggapan tersebut, saya berharap mereka mengenali bahwa solusi ini menjawab pertanyaan yang terkait erat, dan kemudian membingkai ulang pertanyaan mereka untuk mengarahkan percakapan ke arah yang benar. (Lebih jauh, jika pewawancara benar-benar stroppy, maka saya tidak akan menginginkan pekerjaan itu; jika jawaban terhadap persyaratan yang tidak tepat akurat ditembak jatuh dalam api tanpa koreksi, maka pewawancara bukanlah seseorang yang aman untuk bekerja.)

Dunia bergerak

Judul pertanyaan telah berubah baru-baru ini. Itu Memecahkan keselarasan memori di C pertanyaan wawancara yang bingung saya . Judul yang direvisi ( Bagaimana cara mengalokasikan memori selaras hanya menggunakan perpustakaan standar? ) Menuntut jawaban yang sedikit direvisi - adendum ini menyediakannya.

C11 (ISO / IEC 9899: 2011) menambahkan fungsi aligned_alloc():

7.22.3.1 aligned_allocFungsi

Ringkasan

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

Deskripsi
The aligned_allocmengalokasikan fungsi ruang untuk obyek yang keselarasan ditentukan oleh alignment, yang ukurannya ditentukan oleh size, dan yang nilainya tak tentu. Nilai alignmentharus merupakan keselarasan yang valid yang didukung oleh implementasi dan nilai sizeharus merupakan kelipatan integral dari alignment.

Pengembalian
tersebut aligned_allockembali fungsi baik pointer nol atau pointer ke ruang yang dialokasikan.

Dan POSIX mendefinisikan posix_memalign():

#include <stdlib.h>

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

DESKRIPSI

The posix_memalign()Fungsi akan mengalokasikan sizebyte selaras pada batas yang ditentukan oleh alignment, dan akan kembali pointer ke memori yang dialokasikan di memptr. Nilai alignmentharus menjadi kekuatan dua kelipatan sizeof(void *).

Setelah berhasil diselesaikan, nilai yang ditunjukkan oleh memptrharus kelipatan alignment.

Jika ukuran ruang yang diminta adalah 0, perilaku ditentukan oleh implementasi; nilai yang dikembalikan memptrharus berupa pointer nol atau pointer unik.

The free()Fungsi akan deallocate memori yang sebelumnya telah dialokasikan oleh posix_memalign().

NILAI KEMBALI

Setelah berhasil diselesaikan, posix_memalign()akan mengembalikan nol; jika tidak, nomor kesalahan harus dikembalikan untuk menunjukkan kesalahan.

Salah satu atau keduanya dapat digunakan untuk menjawab pertanyaan sekarang, tetapi hanya fungsi POSIX yang menjadi pilihan ketika pertanyaan itu awalnya dijawab.

Di belakang layar, fungsi memori selaras baru melakukan pekerjaan yang sama seperti yang dijelaskan dalam pertanyaan, kecuali mereka memiliki kemampuan untuk memaksa penyelarasan lebih mudah, dan melacak dimulainya memori selaras secara internal sehingga kode tidak harus berurusan dengan khusus - itu hanya membebaskan memori yang dikembalikan oleh fungsi alokasi yang digunakan.

Jonathan Leffler
sumber
13
Dan saya berkarat dengan C ++, tapi saya tidak benar-benar percaya bahwa ~ 0x0F akan berkembang dengan baik ke ukuran pointer. Jika tidak, semua neraka akan lepas karena Anda akan menutupi bit paling signifikan dari pointer Anda juga. Saya bisa salah tentang itu.
Bill K
66
BTW '+15' berfungsi dan '+16' ... tidak ada dampak praktis dalam situasi ini.
Menkboy
15
Komentar '+ 15' dari Menkboy dan Greg benar, tetapi malloc () hampir pasti akan menambahnya hingga 16. Menggunakan +16 sedikit lebih mudah untuk dijelaskan. Solusi umum adalah fiddly, tetapi bisa dilakukan.
Jonathan Leffler
6
@Aerovistae: Ini sedikit pertanyaan jebakan, dan sebagian besar bergantung pada pemahaman Anda tentang cara membuat nomor arbitrer (sebenarnya alamat yang dikembalikan oleh pengalokasi memori) cocok dengan persyaratan tertentu (kelipatan 16). Jika Anda diminta untuk mengumpulkan 53 hingga kelipatan 16 terdekat, bagaimana Anda melakukannya? Prosesnya tidak jauh berbeda untuk alamat; hanya saja angka yang biasanya Anda hadapi lebih besar. Jangan lupa, pertanyaan wawancara diminta untuk mengetahui bagaimana Anda berpikir, bukan untuk mengetahui apakah Anda tahu jawabannya.
Jonathan Leffler
3
@akristmann: Kode asli benar jika Anda memiliki <inttypes.h>dari C99 tersedia (setidaknya untuk string format - bisa dibilang, nilai-nilai harus dilewatkan dengan pemain:) (uintptr_t)mem, (uintptr_t)ptr. String format bergantung pada penggabungan string dan makro PRIXPTR adalah printf()specifier panjang dan tipe yang benar untuk output hex untuk suatu uintptr_tnilai. Alternatifnya adalah menggunakan %ptetapi output dari itu bervariasi berdasarkan platform (beberapa menambahkan yang terkemuka 0x, sebagian besar tidak) dan biasanya ditulis dengan angka hex kecil, yang tidak saya sukai; apa yang saya tulis adalah seragam di seluruh platform.
Jonathan Leffler
58

Tiga jawaban yang sedikit berbeda tergantung bagaimana Anda melihat pertanyaan:

1) Cukup bagus untuk pertanyaan persis yang diajukan adalah solusi Jonathan Leffler, kecuali untuk mengumpulkan hingga 16-aligned, Anda hanya perlu 15 byte tambahan, bukan 16.

SEBUAH:

/* 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) Untuk fungsi alokasi memori yang lebih umum, penelepon tidak ingin harus melacak dua pointer (satu untuk digunakan dan satu lagi untuk membebaskan). Jadi, Anda menyimpan pointer ke buffer 'asli' di bawah buffer yang diselaraskan.

SEBUAH:

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

Perhatikan bahwa tidak seperti (1), di mana hanya 15 byte ditambahkan ke mem, kode ini benar-benar dapat mengurangi penyelarasan jika implementasi Anda terjadi untuk menjamin penyelarasan 32 byte dari malloc (tidak mungkin, tetapi secara teori implementasi C dapat memiliki 32 byte jenis selaras). Itu tidak masalah jika semua yang Anda lakukan adalah memanggil memset_16aligned, tetapi jika Anda menggunakan memori untuk struct maka itu bisa jadi masalah.

Saya tidak yakin apa perbaikan yang baik untuk ini (selain untuk memperingatkan pengguna bahwa buffer yang dikembalikan belum tentu cocok untuk struct sewenang-wenang) karena tidak ada cara untuk menentukan secara programatik apa jaminan penyelarasan implementasi khusus. Saya kira pada saat startup Anda dapat mengalokasikan dua atau lebih buffer 1-byte, dan menganggap bahwa alignment terburuk yang Anda lihat adalah alignment yang dijamin. Jika Anda salah, Anda membuang-buang memori. Siapa pun yang memiliki ide yang lebih baik, katakan saja ...

[ Ditambahkan : Trik 'standar' adalah untuk membuat penyatuan 'jenis yang kemungkinan akan disejajarkan secara maksimal' untuk menentukan perataan yang diperlukan. Tipe yang disejajarkan secara maksimal kemungkinan besar adalah (dalam C99) ' long long', ' long double', ' void *', atau ' void (*)(void)'; jika Anda menyertakan <stdint.h>, Anda mungkin bisa menggunakan ' intmax_t' sebagai pengganti long long(dan, pada mesin Power 6 (AIX), intmax_takan memberi Anda tipe integer 128-bit). Persyaratan penyelarasan untuk serikat itu dapat ditentukan dengan menanamkannya ke dalam sebuah struct dengan satu char yang diikuti oleh serikat pekerja:

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;

Anda kemudian akan menggunakan yang lebih besar dari perataan yang diminta (dalam contoh, 16) dan alignnilai yang dihitung di atas.

Pada (64-bit) Solaris 10, tampak bahwa penyelarasan dasar untuk hasil dari malloc()adalah kelipatan 32 byte.
]

Dalam praktiknya, pengalokasi yang selaras sering mengambil parameter untuk penyelarasan alih-alih bawaan. Jadi pengguna akan lulus dalam ukuran struct yang mereka pedulikan (atau kekuatan paling sedikit 2 lebih besar dari atau sama dengan itu) dan semua akan baik-baik saja.

3) Gunakan apa yang disediakan platform Anda: posix_memalignuntuk POSIX, _aligned_mallocdi Windows.

4) Jika Anda menggunakan C11, maka opsi terbersih - portabel dan ringkas - adalah menggunakan fungsi pustaka standar aligned_allocyang diperkenalkan dalam versi spesifikasi bahasa ini.

Steve Jessop
sumber
1
Saya setuju - saya pikir maksud pertanyaannya adalah bahwa kode yang membebaskan blok memori akan memiliki akses hanya ke pointer sejajar 16-byte 'dimasak'.
Michael Burr
1
Untuk solusi umum - Anda benar. Namun, templat kode dalam pertanyaan dengan jelas menunjukkan keduanya.
Jonathan Leffler
1
Tentu, dan dalam wawancara yang baik apa yang terjadi adalah Anda memberikan jawaban Anda, maka jika pewawancara ingin melihat jawaban saya, mereka mengubah pertanyaan itu.
Steve Jessop
1
Saya keberatan menggunakan ASSERT(mem);untuk memeriksa hasil alokasi; assertadalah untuk menangkap kesalahan pemrograman dan tidak kekurangan sumber daya run-time.
hlovdal
4
Menggunakan biner & dengan a char *dan size_takan menghasilkan kesalahan. Anda harus menggunakan sesuatu seperti uintptr_t.
Marko
20

Inilah pendekatan alternatif untuk bagian 'pembulatan'. Bukan solusi yang paling dikodekan tetapi menyelesaikan pekerjaan, dan jenis sintaksis ini sedikit lebih mudah diingat (ditambah akan bekerja untuk nilai penyelarasan yang bukan kekuatan 2). Para uintptr_tpemain diperlukan untuk menenangkan kompiler; pointer aritmatika tidak terlalu menyukai pembagian atau perkalian.

void *mem = malloc(1024 + 15);
void *ptr = (void*) ((uintptr_t) mem + 15) / 16 * 16;
memset_16aligned(ptr, 0, 1024);
free(mem);
An̲̳̳drew
sumber
2
Secara umum, di mana Anda memiliki 'unsigned long long', Anda juga memiliki uintptr_t yang secara eksplisit didefinisikan cukup besar untuk menahan penunjuk data (batal *). Tetapi solusi Anda memang memiliki kelebihan jika, karena alasan tertentu, Anda membutuhkan keberpihakan yang bukan kekuatan 2. Tidak mungkin, tetapi mungkin.
Jonathan Leffler
@Andrew: Upvoted untuk jenis sintaksis ini sedikit lebih mudah diingat (plus akan bekerja untuk nilai penyelarasan yang bukan kekuatan 2) .
legends2k
19

Sayangnya, di C99 tampaknya cukup sulit untuk menjamin penyelarasan dalam bentuk apa pun dengan cara yang portabel di setiap implementasi C yang sesuai dengan C99. Mengapa? Karena pointer tidak dijamin menjadi "alamat byte" yang mungkin dibayangkan dengan model memori datar. Representasi dari uintptr_t juga tidak dijamin, yang merupakan tipe opsional.

Kita mungkin tahu beberapa implementasi yang menggunakan representasi untuk void * (dan menurut definisi, juga char * ) yang merupakan alamat byte sederhana, tetapi oleh C99 itu tidak jelas bagi kita, para programmer. Sebuah implementasi mungkin mewakili sebuah pointer oleh set { segment , offset } di mana offset bisa memiliki keselarasan siapa-tahu-apa "dalam kenyataan." Mengapa, sebuah pointer bahkan bisa berupa nilai pencarian tabel hash, atau bahkan nilai pencarian daftar-tertaut. Itu bisa menyandikan informasi batas.

Dalam konsep C1X terbaru untuk Standar C, kita melihat kata kunci _Alignas . Itu mungkin sedikit membantu.

Satu-satunya jaminan yang diberikan C99 kepada kami adalah bahwa fungsi alokasi memori akan mengembalikan pointer yang cocok untuk penugasan ke pointer yang menunjuk pada jenis objek apa pun. Karena kami tidak dapat menentukan perataan objek, kami tidak dapat mengimplementasikan fungsi alokasi kami sendiri dengan tanggung jawab untuk penyelarasan dengan cara portabel yang terdefinisi dengan baik.

Akan baik salah tentang klaim ini.

Shao
sumber
C11 punya aligned_alloc(). (C ++ 11/14 / 1z masih belum memilikinya). _Alignas()dan C ++ alignas()tidak melakukan apa pun untuk alokasi dinamis, hanya untuk penyimpanan otomatis dan statis (atau tata letak struct).
Peter Cordes
15

Di depan padding count-16 16 vs 15, angka aktual yang perlu Anda tambahkan untuk mendapatkan keselarasan N adalah maks (0, NM) di mana M adalah penyelarasan alami dari pengalokasi memori (dan keduanya adalah kekuatan 2).

Karena penyelarasan memori minimal dari setiap pengalokasi adalah 1 byte, 15 = maks (0,16-1) adalah jawaban yang konservatif. Namun, jika Anda tahu pengalokasi memori Anda akan memberi Anda alamat sejajar 32-bit (yang cukup umum), Anda bisa menggunakan 12 sebagai pad.

Ini tidak penting untuk contoh ini tetapi mungkin penting pada sistem tertanam dengan 12K RAM di mana setiap int disimpan.

Cara terbaik untuk mengimplementasikannya jika Anda benar-benar akan mencoba untuk menyimpan setiap byte yang mungkin adalah sebagai makro sehingga Anda dapat memberinya makan perataan memori asli Anda. Sekali lagi, ini mungkin hanya berguna untuk sistem embedded di mana Anda perlu menyimpan setiap byte.

Dalam contoh di bawah ini, pada sebagian besar sistem, nilai 1 baik-baik saja MEMORY_ALLOCATOR_NATIVE_ALIGNMENT, namun untuk sistem tertanam teoritis kami dengan alokasi rata-rata 32-bit, berikut ini dapat menghemat sedikit memori berharga:

#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)
Adisak
sumber
8

Mungkin mereka akan puas dengan pengetahuan tentang memalign ? Dan seperti yang ditunjukkan Jonathan Leffler, ada dua fungsi baru yang lebih disukai untuk diketahui.

Ups, florin mengalahkan saya untuk itu. Namun, jika Anda membaca halaman manual yang saya tautkan, kemungkinan besar Anda akan memahami contoh yang diberikan oleh poster sebelumnya.

Don Wakefield
sumber
1
Perhatikan bahwa versi halaman referensi saat ini (Februari 2016) mengatakan " memalignFungsi ini sudah usang dan aligned_allocatau posix_memalignharus digunakan sebagai gantinya". Saya tidak tahu apa yang dikatakannya pada Oktober 2008 - tetapi mungkin tidak disebutkan aligned_alloc()karena ditambahkan ke C11.
Jonathan Leffler
5

Kami melakukan hal semacam ini sepanjang waktu untuk Accelerate.framework, perpustakaan OS X / iOS yang sangat vektor, di mana kami harus memperhatikan penyelarasan sepanjang waktu. Ada beberapa opsi, satu atau dua di antaranya tidak saya lihat di atas.

Metode tercepat untuk array kecil seperti ini hanya menempelkannya di stack. Dengan GCC / dentang:

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

Tidak diperlukan gratis (). Ini biasanya dua instruksi: kurangi 1024 dari stack pointer, lalu AND stack pointer dengan -alignment. Mungkin pemohon membutuhkan data pada heap karena umur array melebihi stack atau rekursi sedang bekerja atau ruang stack berada pada premium yang serius.

Pada OS X / iOS semua panggilan ke malloc / calloc / dll. selalu selaras 16 byte. Jika Anda membutuhkan 32 byte yang diluruskan untuk AVX, misalnya, maka Anda dapat menggunakan posix_memalign:

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

Beberapa orang telah menyebutkan antarmuka C ++ yang bekerja sama.

Seharusnya tidak dilupakan bahwa halaman disejajarkan dengan kekuatan besar dua, jadi buffer halaman-disejajarkan juga 16 byte selaras. Dengan demikian, mmap () dan valloc () dan antarmuka serupa lainnya juga merupakan opsi. mmap () memiliki keuntungan bahwa buffer dapat dialokasikan diinisialisasi dengan sesuatu yang tidak nol di dalamnya, jika Anda mau. Karena ini memiliki ukuran selaras halaman, Anda tidak akan mendapatkan alokasi minimum dari ini, dan kemungkinan akan dikenakan kesalahan VM saat pertama kali Anda menyentuhnya.

Cheesy: Aktifkan guard malloc atau sejenisnya. Buffer yang berukuran n * 16 byte seperti ini akan disejajarkan n * 16 byte, karena VM digunakan untuk menangkap overruns dan batas-batasnya berada pada batas halaman.

Beberapa fungsi Accelerate.framework menggunakan buffer temp yang disediakan pengguna untuk digunakan sebagai ruang awal. Di sini kita harus mengasumsikan bahwa buffer yang diberikan kepada kita tidak selaras dan pengguna secara aktif berusaha membuat hidup kita sulit karena dendam. (Test case kami menempel halaman penjaga tepat sebelum dan sesudah buffer temp untuk menggarisbawahi dendam.) Di sini, kami mengembalikan ukuran minimum yang kami butuhkan untuk menjamin segmen selaras 16 byte di suatu tempat di dalamnya, dan kemudian secara manual menyelaraskan buffer sesudahnya. Ukuran ini diinginkan_ukuran + perataan - 1. Jadi, Dalam hal ini yaitu 1024 + 16 - 1 = 1039 byte. Kemudian sejajarkan seperti itu:

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

Menambahkan alignment-1 akan memindahkan pointer melewati alamat yang pertama dan kemudian ANDing dengan -alignment (mis. 0xfff ... ff0 untuk alignment = 16) membawanya kembali ke alamat yang disejajarkan.

Seperti dijelaskan oleh posting lain, pada sistem operasi lain tanpa jaminan penyelarasan 16-byte, Anda dapat memanggil malloc dengan ukuran yang lebih besar, menyisihkan pointer secara gratis () kemudian, menyelaraskan seperti dijelaskan di atas dan menggunakan pointer yang disejajarkan, sebanyak dijelaskan untuk kasus buffer temp kami.

Adapun aligned_memset, ini agak konyol. Anda hanya perlu mengulang hingga 15 byte untuk mencapai alamat yang selaras, dan kemudian melanjutkan dengan toko selaras setelah itu dengan beberapa kemungkinan kode pembersihan di akhir. Anda bahkan dapat melakukan bit pembersihan dalam kode vektor, baik sebagai toko yang tidak selaras yang tumpang tindih dengan wilayah yang disejajarkan (memberikan panjangnya setidaknya panjang vektor) atau menggunakan sesuatu seperti movmaskdqu. Seseorang sedang malas. Namun, itu mungkin pertanyaan wawancara yang masuk akal jika pewawancara ingin tahu apakah Anda merasa nyaman dengan stdint.h, operator bitwise dan dasar-dasar memori, sehingga contoh yang dibuat-buat dapat dimaafkan.

Ian Ollmann
sumber
5

Aku heran tidak ada ini sebagai up Shao 's jawaban itu, seperti yang saya mengerti, tidak mungkin untuk melakukan apa yang diminta dalam standar C99, karena mengubah pointer ke tipe integral secara resmi adalah perilaku undefined. (Terlepas dari standar yang memungkinkan konversi uintptr_t<-> void*, tetapi standar tersebut tampaknya tidak memungkinkan melakukan manipulasi uintptr_tnilai dan mengubahnya kembali.)

Lutorm
sumber
Tidak ada persyaratan bahwa jenis uintptr_t ada, atau bit-bitnya memiliki hubungan dengan bit pada pointer yang mendasarinya. Jika seseorang terlalu mengalokasikan penyimpanan, simpan pointer sebagai unsigned char* myptr; dan kemudian menghitung `mptr + = (16- (uintptr_t) my_ptr) & 0x0F, perilaku akan didefinisikan pada semua implementasi yang mendefinisikan my_ptr, tetapi apakah pointer yang dihasilkan akan disejajarkan akan tergantung pada pemetaan antara bit dan alamat uintptr_t.
supercat
3

penggunaan memalign, Aligned-Memory-Blocks mungkin menjadi solusi yang baik untuk masalah tersebut.

neuron
sumber
Perhatikan bahwa versi halaman referensi saat ini (Februari 2016) mengatakan " memalignFungsi ini sudah usang dan aligned_allocatau posix_memalignharus digunakan sebagai gantinya". Saya tidak tahu apa yang dikatakannya pada Oktober 2010.
Jonathan Leffler
3

Hal pertama yang muncul di kepala saya ketika membaca pertanyaan ini adalah untuk mendefinisikan struct yang selaras, instantiate, dan kemudian arahkan ke itu.

Apakah ada alasan mendasar saya kehilangan karena tidak ada orang lain yang menyarankan ini?

Sebagai sidenote, karena saya menggunakan array char (dengan asumsi char sistem adalah 8 bit (yaitu 1 byte)), saya tidak melihat perlunya __attribute__((packed))(mengoreksi saya jika saya salah), tapi saya katakan dengan cara apapun.

Ini bekerja pada dua sistem yang saya coba, tetapi ada kemungkinan bahwa ada optimisasi kompiler yang saya tidak sadari memberi saya positif palsu berhadapan dengan kemanjuran kode. Saya menggunakan gcc 4.9.2OSX dan gcc 5.2.1Ubuntu.

#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;
}
Janus
sumber
1

Khusus MacOS X:

  1. Semua pointer yang dialokasikan dengan malloc selaras 16 byte.
  2. C11 didukung, jadi Anda bisa memanggil aligned_malloc (16, size).

  3. MacOS X mengambil kode yang dioptimalkan untuk masing-masing prosesor pada saat boot untuk memset, memcpy dan memmove dan kode itu menggunakan trik yang belum pernah Anda dengar untuk membuatnya cepat. 99% kemungkinan memset berjalan lebih cepat daripada memset tulisan tangan16 yang membuat seluruh pertanyaan tidak ada gunanya.

Jika Anda menginginkan solusi portabel 100%, sebelum C11 tidak ada. Karena tidak ada cara portabel untuk menguji keselarasan pointer. Jika tidak harus 100% portabel, Anda dapat menggunakannya

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

Ini mengasumsikan bahwa penyelarasan pointer disimpan dalam bit terendah ketika mengkonversi pointer ke unsigned int. Konversi ke int yang tidak ditandatangani kehilangan informasi dan implementasi didefinisikan, tetapi itu tidak masalah karena kami tidak mengubah hasilnya kembali menjadi sebuah pointer.

Bagian yang mengerikan tentu saja bahwa pointer asli harus disimpan di suatu tempat untuk memanggil bebas () dengannya. Jadi semuanya saya benar-benar meragukan kearifan desain ini.

Chris
sumber
1
Di mana Anda menemukan aligned_mallocdi OS X? Saya menggunakan Xcode 6.1 dan tidak didefinisikan di mana pun di iOS SDK, juga tidak dinyatakan di mana pun di /usr/include/*.
Todd Lehman
Ditto untuk XCode 7.2 pada El Capitan (Mac OS X 10.11.3). Fungsi C11, dalam hal apa pun aligned_alloc(),, tapi itu juga tidak dideklarasikan. Dari GCC 5.3.0, saya mendapatkan pesan yang menarik alig.c:7:15: error: incompatible implicit declaration of built-in function ‘aligned_alloc’ [-Werror]dan alig.c:7:15: note: include ‘<stdlib.h>’ or provide a declaration of ‘aligned_alloc’. Kode melakukan memang termasuk <stdlib.h>, tetapi tidak -std=c11juga -std=gnu11mengubah pesan kesalahan.
Jonathan Leffler
0

Anda juga dapat menambahkan 16 byte dan kemudian mendorong ptr asli ke 16bit sejajar dengan menambahkan (16-mod) seperti di bawah penunjuk:

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);
}
akibatnya
sumber
0

Jika ada kendala, Anda tidak dapat membuang satu byte, maka solusi ini berfungsi: Catatan: Ada kasus di mana ini dapat dieksekusi tanpa batas: D

   void *mem;  
   void *ptr;
try:
   mem =  malloc(1024);  
   if (mem % 16 != 0) {  
       free(mem);  
       goto try;
   }  
   ptr = mem;  
   memset_16aligned(ptr, 0, 1024);
Berpikir mendalam
sumber
Ada kemungkinan yang sangat baik bahwa jika Anda mengalokasikan dan kemudian membebaskan blok N byte dan kemudian meminta blok N byte lainnya, blok asli akan dikembalikan lagi. Jadi loop tak terbatas sangat mungkin jika alokasi pertama tidak memenuhi persyaratan perataan. Tentu saja, itu menghindari pemborosan satu byte dengan biaya membuang banyak siklus CPU.
Jonathan Leffler
Apakah Anda yakin %operator didefinisikan void*dengan cara yang bermakna?
Ajay Brahmakshatriya
0

Untuk solusinya saya menggunakan konsep padding yang menyelaraskan memori dan jangan buang memori satu byte.

Jika ada kendala itu, Anda tidak bisa membuang satu byte. Semua pointer yang dialokasikan dengan malloc selaras 16 byte.

C11 didukung, jadi Anda bisa langsung menelepon aligned_alloc (16, size).

void *mem = malloc(1024+16);
void *ptr = ((char *)mem+16) & ~ 0x0F;
memset_16aligned(ptr, 0, 1024);
free(mem);
pengguna3415603
sumber
1
Pada banyak sistem 64-bit, penunjuk yang dikembalikan oleh malloc()memang selaras pada batas 16-byte, tetapi tidak ada dalam standar apa pun yang menjamin - itu hanya akan cukup selaras dengan baik untuk penggunaan apa pun, dan pada banyak sistem 32-bit menyelaraskan pada Batas 8-byte sudah cukup, dan bagi sebagian orang, batas 4-byte sudah cukup.
Jonathan Leffler
0
size =1024;
alignment = 16;
aligned_size = size +(alignment -(size %  alignment));
mem = malloc(aligned_size);
memset_16aligned(mem, 0, 1024);
free(mem);

Semoga yang ini adalah implementasi paling sederhana, beri tahu saya komentar Anda.

stackguy
sumber
-3
long add;   
mem = (void*)malloc(1024 +15);
add = (long)mem;
add = add - (add % 16);//align to 16 byte boundary
ptr = (whatever*)(add);
Ramana
sumber
Saya pikir ada masalah dengan ini karena add Anda akan menunjuk ke lokasi yang tidak malloc'd - Tidak yakin bagaimana ini bekerja pada Anda.
resultsway
@ Sam Seharusnya begitu add += 16 - (add % 16). (2 - (2 % 16)) == 0.
SS Anne