วิธีการจัดสรรหน่วยความจำที่จัดตำแหน่งโดยใช้ไลบรารีมาตรฐานเท่านั้น


421

ฉันเพิ่งเสร็จสิ้นการทดสอบซึ่งเป็นส่วนหนึ่งของการสัมภาษณ์งานและมีคำถามหนึ่งที่ทำให้ฉันนิ่งงันแม้กระทั่งการใช้ Google เพื่อการอ้างอิง ฉันต้องการดูว่าทีม StackOverflow สามารถทำอะไรได้บ้าง:

memset_16alignedฟังก์ชั่นต้องมี 16 ตัวชี้ไบต์ชิดผ่านไปหรือมันจะผิดพลาด

a) คุณจะจัดสรรหน่วยความจำ 1024 ไบต์ได้อย่างไรและจัดแนวให้เป็นเขต 16 ไบต์
b) เพิ่มหน่วยความจำหลังจากที่memset_16alignedได้ดำเนินการ

{    
   void *mem;
   void *ptr;

   // answer a) here

   memset_16aligned(ptr, 0, 1024);

   // answer b) here    
}

89
hmmm ... สำหรับความเป็นไปได้ของโค้ดในระยะยาวแล้ว "Fire ใครก็ตามที่เขียน memset_16 ปรับตำแหน่งแล้วแก้ไขหรือแทนที่มันเพื่อที่ว่ามันจะไม่ได้มีเงื่อนไขขอบเขตที่แปลกประหลาด"
สตีเฟนเอ. โลว์

29
คำถามที่ถูกต้องแน่นอนที่จะถาม - "ทำไมการจัดตำแหน่งหน่วยความจำที่แปลกประหลาด" แต่อาจมีเหตุผลที่ดีสำหรับมัน - ในกรณีนี้อาจเป็นได้ว่า memset_16aligned () สามารถใช้จำนวนเต็ม 128- บิตและสิ่งนี้จะง่ายกว่าหากหน่วยความจำถูกจัดตำแหน่ง อื่น ๆ
Jonathan Leffler

5
ใครก็ตามที่เขียน memset สามารถใช้การจัดตำแหน่ง 16- ไบต์ภายในสำหรับการล้างลูปด้านในและโปรล็อกข้อมูลขนาดเล็ก / epilog เพื่อล้างจุดสิ้นสุดที่ไม่ได้จัดแนว นั่นจะง่ายกว่าการทำให้โคเดอเรเตอร์จัดการกับตัวชี้หน่วยความจำเพิ่มเติม
Adisak

8
เหตุใดบางคนจึงต้องการให้ข้อมูลสอดคล้องกับขอบเขต 16 ไบต์ อาจโหลดลงในการลงทะเบียน SSE แบบ 128 บิต ฉันเชื่อว่า movs (ใหม่กว่า) ที่ไม่ได้จัดแนว (เช่น movupd, lddqu) ช้าลงหรือบางทีพวกเขากำลังกำหนดเป้าหมายโปรเซสเซอร์โดยไม่มี SSE2 / 3

11
การจัดตำแหน่งที่อยู่จะนำไปสู่การใช้แคชอย่างเหมาะสมรวมถึงแบนด์วิดท์ที่สูงขึ้นระหว่างระดับแคชและ RAM ที่แตกต่างกัน (สำหรับเวิร์กโหลดทั่วไป) ดูที่นี่stackoverflow.com/questions/381244/purpose-of-memory-alignment
Deep butt

คำตอบ:


585

คำตอบเดิม

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

แก้ไขคำตอบ

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

คำอธิบายตามที่ร้องขอ

ขั้นตอนแรกคือการจัดสรรพื้นที่ว่างให้เพียงพอในกรณี เนื่องจากหน่วยความจำจะต้องอยู่ในแนว 16- ไบต์ (หมายถึงว่าที่อยู่ไบต์นำจะต้องมีหลาย 16) เพิ่ม 16 ไบต์พิเศษรับประกันว่าเรามีพื้นที่เพียงพอ ที่ใดที่หนึ่งใน 16 ไบต์แรกจะมีตัวชี้การจัดตำแหน่ง 16 ไบต์ (หมายเหตุว่าmalloc()ควรจะกลับชี้ที่เพียงพอสอดคล้องกันสำหรับใด ๆ . วัตถุประสงค์อย่างไรก็ตามความหมายของ '' ใด ๆ เป็นหลักสำหรับสิ่งที่ต้องการขั้นพื้นฐานประเภท - long, double, long double, long long. และตัวชี้ไปยังวัตถุและตัวชี้ไปยังฟังก์ชั่นเมื่อคุณอยู่ ทำสิ่งพิเศษเช่นเล่นกับระบบกราฟิกพวกเขาสามารถต้องการการจัดตำแหน่งที่เข้มงวดกว่าส่วนที่เหลือของระบบ - ดังนั้นคำถามและคำตอบเช่นนี้)

ขั้นตอนต่อไปคือการแปลงตัวชี้โมฆะเป็นตัวชี้ถ่าน; GCC อย่างไรก็ตามคุณไม่ควรทำการคำนวณตัวชี้บนตัวชี้โมฆะ (และ GCC มีตัวเลือกคำเตือนเพื่อบอกคุณเมื่อคุณใช้งานในทางที่ผิด) จากนั้นเพิ่ม 16 เข้ากับตัวชี้เริ่มต้น สมมติว่าmalloc()คุณส่งคืนตัวชี้ที่จัดตำแหน่งอย่างไม่ถูกต้อง: 0x800001 การเพิ่ม 16 ทำให้ 0x800011 ตอนนี้ฉันต้องการปัดเศษเป็นขอบเขต 16 ไบต์ - ดังนั้นฉันต้องการรีเซ็ต 4 บิตสุดท้ายเป็น 0 0x0F ได้ตั้งค่า 4 บิตสุดท้ายเป็นหนึ่ง ดังนั้นจึง~0x0Fมีบิตทั้งหมดตั้งค่าเป็นหนึ่งยกเว้นสี่ล่าสุด และด้วยสิ่งนั้นด้วย 0x800011 ให้ 0x800010 คุณสามารถทำซ้ำมากกว่าออฟเซ็ตอื่นและดูว่าการคำนวณทางคณิตศาสตร์แบบเดียวกันนั้นใช้งานได้

ขั้นตอนสุดท้ายfree()เป็นเรื่องง่าย: คุณเสมอและมีเพียงกลับไปfree()เป็นค่าที่หนึ่งmalloc(), calloc()หรือrealloc()ส่งกลับให้คุณ - สิ่งอื่นใดคือภัยพิบัติ คุณให้memไว้อย่างถูกต้องเพื่อเก็บค่านั้น - ขอบคุณ ฟรีปล่อยมัน

ท้ายที่สุดถ้าคุณรู้เรื่องภายในของmallocแพ็คเกจระบบของคุณคุณอาจเดาได้ว่ามันอาจส่งคืนข้อมูลที่จัดเรียงแบบ 16 ไบต์ (หรืออาจจัดตำแหน่งแบบ 8 ไบต์) หากมีการจัดตำแหน่งแบบ 16 ไบต์คุณไม่จำเป็นต้องคิดด้วยค่า อย่างไรก็ตามนี่คือการหลบและไม่พกพา - mallocแพคเกจอื่น ๆมีการจัดตำแหน่งขั้นต่ำที่แตกต่างกันและดังนั้นสมมติว่าสิ่งหนึ่งเมื่อทำสิ่งที่แตกต่างกันจะนำไปสู่การทิ้งหลัก ภายในขอบเขตที่กว้างโซลูชั่นนี้สามารถพกพาได้

บางคนพูดถึงposix_memalign()วิธีอื่นในการรับหน่วยความจำที่จัดตำแหน่ง ที่ไม่สามารถใช้ได้ทุกที่ แต่มักจะสามารถนำมาใช้โดยใช้สิ่งนี้เป็นพื้นฐาน โปรดทราบว่าสะดวกในการจัดตำแหน่งคือกำลัง 2 การจัดตำแหน่งอื่นเป็น messier

อีกหนึ่งความคิดเห็น - รหัสนี้ไม่ได้ตรวจสอบว่าการจัดสรรสำเร็จ

การแก้ไข

โปรแกรมเมอร์ของ Windowsชี้ให้เห็นว่าคุณไม่สามารถทำการซ่อนบิตบนพอยน์เตอร์ได้และแน่นอนว่า GCC (3.4.6 และ 4.3.1 ทดสอบ) จะบ่นเช่นนั้น ดังนั้นเวอร์ชันพื้นฐานของโค้ดพื้นฐานที่ถูกแก้ไข - เปลี่ยนเป็นโปรแกรมหลักดังนี้ ฉันยังใช้เสรีภาพในการเพิ่มเพียง 15 แทน 16 ตามที่ได้อธิบายไว้ ฉันใช้uintptr_tตั้งแต่ C99 นั้นนานพอที่จะเข้าถึงได้บนแพลตฟอร์มส่วนใหญ่ ถ้ามันไม่ได้สำหรับการใช้PRIXPTRในprintf()งบก็จะเพียงพอที่จะแทนการใช้#include <stdint.h> [รหัสนี้รวมถึงการแก้ไขที่ชี้ให้เห็นโดยCRซึ่งเป็นการยืนยันจุดแรกโดยBill Kเมื่อหลายปีก่อนซึ่งฉันพยายามมองข้ามจนถึงตอนนี้]#include <inttypes.h>

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

และนี่คือเวอร์ชันทั่วไปที่กว้างขึ้นซึ่งจะใช้งานได้กับขนาดที่เป็นกำลัง 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);
}

ในการแปลงtest_mask()เป็นฟังก์ชันการจัดสรรวัตถุประสงค์ทั่วไปค่าส่งคืนเดียวจากตัวจัดสรรจะต้องเข้ารหัสที่อยู่ปล่อยเนื่องจากมีหลายคนระบุไว้ในคำตอบ

มีปัญหากับผู้สัมภาษณ์

Uriแสดงความคิดเห็น: บางทีฉันมีปัญหาการอ่านเพื่อความเข้าใจ [a] เมื่อเช้านี้ แต่ถ้าคำถามสัมภาษณ์บอกว่า: "คุณจะจัดสรรหน่วยความจำ 1024 ไบต์ได้อย่างไร" และคุณจัดสรรอย่างชัดเจนมากกว่านั้น นั่นจะเป็นความล้มเหลวโดยอัตโนมัติจากผู้สัมภาษณ์ใช่ไหม

การตอบสนองของฉันไม่เหมาะสมกับความคิดเห็น 300 ตัวอักษร ...

ฉันคิดว่ามันขึ้นอยู่กับ ฉันคิดว่าคนส่วนใหญ่ (รวมถึงฉัน) ได้ตั้งคำถามเพื่อหมายถึง "คุณจะจัดสรรพื้นที่ในการจัดเก็บข้อมูล 1024 ไบต์ได้อย่างไรและที่อยู่ฐานเป็นเท่าใด 16 ไบต์" หากผู้สัมภาษณ์ตั้งใจจริงๆว่าคุณจะจัดสรร 1024 ไบต์ได้อย่างไร (เท่านั้น) และจัดแนว 16- ไบต์แล้วตัวเลือกจะถูก จำกัด มากขึ้น

  • เห็นได้ชัดว่ามีความเป็นไปได้อย่างหนึ่งคือการจัดสรร 1024 ไบต์และจากนั้นให้ที่อยู่ 'การรักษาการจัดตำแหน่ง'; ปัญหาของวิธีการนั้นคือพื้นที่จริงที่มีอยู่นั้นไม่ได้ถูกกำหนดอย่างเหมาะสม (พื้นที่ที่สามารถใช้งานได้อยู่ระหว่าง 1008 ถึง 1024 ไบต์ แต่ไม่มีกลไกที่สามารถระบุขนาดได้) ซึ่งทำให้มันมีประโยชน์น้อยกว่า
  • ความเป็นไปได้อีกอย่างหนึ่งคือคุณคาดว่าจะเขียนตัวจัดสรรหน่วยความจำเต็มและตรวจสอบให้แน่ใจว่าบล็อก 1024- ไบต์ที่คุณส่งคืนนั้นได้รับการจัดตำแหน่งอย่างเหมาะสม หากเป็นกรณีนี้คุณอาจสิ้นสุดการดำเนินการที่ค่อนข้างคล้ายกับวิธีการแก้ปัญหาที่เสนอ แต่คุณซ่อนไว้ภายในตัวจัดสรร

อย่างไรก็ตามหากผู้สัมภาษณ์คาดหวังคำตอบใด ๆ เหล่านั้นฉันคาดหวังว่าพวกเขาจะรับรู้ว่าวิธีนี้ตอบคำถามที่เกี่ยวข้องอย่างใกล้ชิดและจากนั้นให้ตั้งคำถามใหม่เพื่อชี้การสนทนาในทิศทางที่ถูกต้อง (นอกจากนี้หากผู้สัมภาษณ์มีปัญหามากฉันจะไม่ต้องการงานถ้าคำตอบของความต้องการที่แม่นยำไม่เพียงพอถูกยิงลงไปในเปลวไฟโดยไม่มีการแก้ไขผู้สัมภาษณ์ไม่ใช่คนที่ปลอดภัยในการทำงาน)

โลกเคลื่อนไป

ชื่อของคำถามมีการเปลี่ยนแปลงเมื่อเร็ว ๆ นี้ มันก็แก้ปัญหาการจัดตำแหน่งของหน่วยความจำใน C คำถามสัมภาษณ์ว่าฉันนิ่งงัน หัวเรื่องที่ถูกแก้ไข ( วิธีการจัดสรรหน่วยความจำที่จัดเรียงโดยใช้ไลบรารีมาตรฐานเท่านั้น ) ต้องการคำตอบที่ได้รับการแก้ไขเล็กน้อย - ภาคผนวกนี้จัดเตรียมไว้ให้

C11 (ISO / IEC 9899: 2011) เพิ่มฟังก์ชั่นaligned_alloc():

7.22.3.1 aligned_allocฟังก์ชั่น

สรุป

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

คำอธิบายพื้นที่ฟังก์ชั่นสำหรับการจัดสรรวัตถุที่มีการจัดตำแหน่งที่ระบุไว้โดยที่มีขนาดตามที่ระบุไว้และมีค่าไม่แน่นอน ค่าของจะต้องได้รับการจัดตำแหน่งที่ถูกต้องได้รับการสนับสนุนโดยการดำเนินงานและความคุ้มค่าของการจะเป็นที่หนึ่งของหลาย ๆ
aligned_allocalignmentsizealignmentsizealignment

ส่งกลับกลับมาทำงานอย่างใดอย่างหนึ่งชี้โมฆะหรือตัวชี้ไปยังพื้นที่ที่จัดสรร
aligned_alloc

และ POSIX กำหนดposix_memalign():

#include <stdlib.h>

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

รายละเอียด

posix_memalign()ฟังก์ชั่นต้องจัดสรรsizeไบต์จัดตำแหน่งบนขอบที่ระบุโดยและจะกลับตัวชี้ไปจัดสรรหน่วยความจำในalignment memptrค่าของให้เป็นอำนาจของสองหลายของalignmentsizeof(void *)

เมื่อสำเร็จค่าชี้ไปจะเป็นหลายmemptralignment

หากขนาดของพื้นที่ที่ร้องขอเป็น 0 พฤติกรรมนั้นจะถูกนำไปใช้งาน ค่าที่ส่งคืนmemptrจะต้องเป็นตัวชี้โมฆะหรือตัวชี้ที่ไม่ซ้ำกัน

free()ฟังก์ชั่นจะ deallocate posix_memalign()หน่วยความจำที่ได้รับการจัดสรรโดยก่อนหน้านี้

คืนค่า

เมื่อสำเร็จposix_memalign()จะกลับมาเป็นศูนย์ มิฉะนั้นจะส่งคืนหมายเลขข้อผิดพลาดเพื่อระบุข้อผิดพลาด

สามารถใช้วิธีใดวิธีหนึ่งหรือทั้งสองอย่างเพื่อตอบคำถามในตอนนี้ แต่เฉพาะฟังก์ชัน POSIX เท่านั้นที่เป็นตัวเลือกเมื่อตอบคำถามครั้งแรก

เบื้องหลังของฟังก์ชั่นหน่วยความจำที่จัดเรียงใหม่นั้นทำหน้าที่เดียวกับที่ระบุไว้ในคำถามยกเว้นพวกเขามีความสามารถในการบังคับให้จัดตำแหน่งได้ง่ายขึ้นและติดตามจุดเริ่มต้นของหน่วยความจำที่จัดตำแหน่งไว้ภายในเพื่อไม่ให้รหัส ต้องจัดการกับพิเศษ - มันเพียงปลดปล่อยหน่วยความจำที่ส่งคืนโดยฟังก์ชั่นการจัดสรรที่ใช้


13
และฉันมีสนิมด้วย C ++ แต่ฉันไม่เชื่อจริง ๆ ว่า ~ 0x0F จะขยายขนาดตัวชี้อย่างถูกต้อง หากไม่เป็นเช่นนั้นนรกทั้งหมดก็จะหลุดออกเพราะคุณจะปกปิดส่วนที่สำคัญที่สุดของตัวชี้ของคุณเช่นกัน ฉันอาจจะผิดเกี่ยวกับที่แม้ว่า
Bill K

66
BTW '15 'ทำงานได้ดีเช่นเดียวกับ '16' ... ไม่มีผลกระทบในทางปฏิบัติในสถานการณ์นี้
Menkboy

15
ความคิดเห็น '+ 15' จาก Menkboy และ Greg นั้นถูกต้อง แต่ malloc () เกือบจะแน่นอนรอบที่มากถึง 16 ต่อไป การใช้ +16 นั้นง่ายต่อการอธิบาย วิธีการแก้ปัญหาทั่วไปคือเที่ยวยุ่งยิ่ง แต่ทำได้
Jonathan Leffler

6
@Aerovistae: มันเป็นคำถามที่หลอกลวงเล็กน้อยและส่วนใหญ่บานพับบนความเข้าใจของคุณเกี่ยวกับวิธีการสร้างหมายเลขโดยพลการ (ที่อยู่ที่ถูกส่งคืนโดยตัวจัดสรรหน่วยความจำ) ตรงกับความต้องการบางอย่าง (หลาย 16) ถ้าคุณถูกบอกให้ปัดเศษ 53 เป็นพหุคูณที่ใกล้เคียงที่สุดของ 16 คุณจะทำอย่างไร กระบวนการไม่แตกต่างกันมากสำหรับที่อยู่ เป็นเพียงตัวเลขที่คุณติดต่อด้วยโดยทั่วไปนั้นใหญ่กว่า อย่าลืมคำถามสัมภาษณ์จะถูกถามเพื่อค้นหาว่าคุณคิดอย่างไรไม่ทราบว่าคุณรู้คำตอบหรือไม่
Jonathan Leffler

3
@akristmann: รหัสเดิมถูกต้องถ้าคุณมี<inttypes.h>จาก C99 พร้อมใช้งาน (อย่างน้อยสำหรับสตริงรูปแบบ - เนื้อหาค่าที่ควรจะผ่านไปหล่อ: (uintptr_t)mem, (uintptr_t)ptr) สตริงรูปแบบอาศัยการต่อสตริงและแมโคร PRIXPTR คือprintf()ความยาวและตัวระบุชนิดที่ถูกต้องสำหรับเอาต์พุต hex สำหรับuintptr_tค่า ทางเลือกคือการใช้%pแต่เอาท์พุทจากที่แตกต่างกันไปตามแพลตฟอร์ม (บางคนเพิ่มนำ0xส่วนใหญ่ไม่ได้) และมักจะเขียนด้วยเลขฐานสิบหกกรณีที่ต่ำกว่าซึ่งฉันไม่ชอบ; สิ่งที่ฉันเขียนนั้นเหมือนกันในทุกแพลตฟอร์ม
Jonathan Leffler

58

สามคำตอบที่แตกต่างกันเล็กน้อยขึ้นอยู่กับว่าคุณมองคำถามนี้อย่างไร:

1) ดีพอสำหรับคำถามที่ถูกถามก็คือทางออกของ Jonathan Leffler ยกเว้นว่าจะปัดเศษได้มากถึง 16 แถวคุณต้องเพิ่ม 15 ไบต์เท่านั้นไม่ใช่ 16

A:

/* 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) สำหรับฟังก์ชันการจัดสรรหน่วยความจำทั่วไปที่มากกว่าผู้เรียกไม่ต้องการติดตามพอยน์เตอร์สองตัว (หนึ่งเพื่อใช้และหนึ่งเพื่อว่าง) ดังนั้นคุณเก็บตัวชี้ไปที่บัฟเฟอร์ 'ของจริง' ใต้บัฟเฟอร์ที่จัดตำแหน่ง

A:

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

โปรดทราบว่าแตกต่างจาก (1) ที่เพิ่มเพียง 15 ไบต์ใน mem รหัสนี้อาจลดการจัดตำแหน่งหากการใช้งานของคุณเกิดขึ้นเพื่อรับประกันการจัดตำแหน่ง 32- ไบต์จาก malloc (ไม่น่าเป็นไปได้ แต่ในทางทฤษฎีแล้วการใช้งาน C อาจมี 32 ไบต์ จัดประเภท) นั่นไม่สำคัญว่าคุณจะโทร memset_16 ถูกต้อง แต่ถ้าคุณใช้หน่วยความจำสำหรับโครงสร้าง

ฉันไม่แน่ใจว่าการแก้ไขที่ดีสำหรับสิ่งนี้คืออะไร (นอกเหนือจากการเตือนผู้ใช้ว่าบัฟเฟอร์ที่ส่งคืนนั้นไม่เหมาะสำหรับโครงสร้างโดยพลการ) เนื่องจากไม่มีวิธีการตรวจสอบโดยทางโปรแกรมว่าการรับประกันการจัดตำแหน่งเฉพาะการใช้งานคืออะไร ฉันเดาว่าเมื่อเริ่มต้นคุณสามารถจัดสรรบัฟเฟอร์ 1 ไบต์ตั้งแต่สองตัวขึ้นไปและสมมติว่าการจัดตำแหน่งที่แย่ที่สุดที่คุณเห็นคือการจัดตำแหน่งแบบรับประกัน หากคุณผิดคุณจะสูญเสียความทรงจำ ใครก็ตามที่มีความคิดที่ดีกว่าโปรดพูดอย่างนั้น ...

[ เพิ่ม : เคล็ดลับ 'มาตรฐาน' คือการสร้างสหภาพที่มีแนวโน้มว่าจะจัดตำแหน่งประเภทสูงสุดเพื่อกำหนดการจัดตำแหน่งที่จำเป็น ประเภทที่มีการจัดแนวสูงสุดมีแนวโน้มที่จะเป็น (ใน C99) ' long long', ' long double', ' void *' หรือ ' void (*)(void)'; หากคุณรวม<stdint.h>ไว้คุณสามารถใช้ ' intmax_t' แทนlong long(และบนเครื่อง Power 6 (AIX)) intmax_tจะให้ชนิดจำนวนเต็ม 128 บิตแก่คุณ ข้อกำหนดการจัดตำแหน่งสำหรับสหภาพนั้นสามารถกำหนดได้โดยการฝังลงในโครงสร้างด้วยอักขระเดี่ยวตามด้วยสหภาพ:

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;

จากนั้นคุณจะใช้การจัดตำแหน่งที่ร้องขอมากขึ้น (ในตัวอย่าง 16) และalignค่าที่คำนวณข้างต้น

บน Solaris 10 (64- บิต) ปรากฏว่าการจัดแนวพื้นฐานสำหรับผลลัพธ์จากmalloc()นั้นเป็นหลาย 32 ไบต์
]

ในทางปฏิบัติผู้จัดสรรที่ได้รับการจัดตำแหน่งมักใช้พารามิเตอร์สำหรับการจัดตำแหน่งแทนที่จะเป็นการเดินสาย ดังนั้นผู้ใช้จะผ่านขนาดของโครงสร้างที่พวกเขาสนใจ (หรือพลังงานน้อยที่สุด 2 หรือมากกว่านั้น) และทั้งหมดจะดี

3) ใช้สิ่งที่แพลตฟอร์มของคุณมี: posix_memalignสำหรับ POSIX _aligned_mallocบน Windows

4) ถ้าคุณใช้ C11 ตัวเลือกที่สะอาดที่สุด - แบบพกพาและรัดกุมคือการใช้ฟังก์ชั่นไลบรารีมาตรฐานaligned_allocที่เปิดตัวในข้อกำหนดภาษารุ่นนี้


1
ฉันเห็นด้วย - ฉันคิดว่าเจตนาของคำถามคือรหัสที่เพิ่มบล็อกหน่วยความจำจะสามารถเข้าถึงตัวชี้การจัดตำแหน่งแบบ "สุก" 16 ไบต์เท่านั้น
Michael Burr

1
สำหรับการแก้ปัญหาทั่วไป - คุณพูดถูก อย่างไรก็ตามเทมเพลตรหัสในคำถามนั้นแสดงให้เห็นอย่างชัดเจน
Jonathan Leffler

1
แน่นอนและในการสัมภาษณ์ที่ดีสิ่งที่เกิดขึ้นคือคุณให้คำตอบถ้าผู้สัมภาษณ์ต้องการเห็นคำตอบของฉันพวกเขาจะเปลี่ยนคำถาม
Steve Jessop

1
ฉันคัดค้านการใช้ASSERT(mem);เพื่อตรวจสอบผลการจัดสรร assertใช้สำหรับตรวจจับข้อผิดพลาดในการเขียนโปรแกรมและไม่มีทรัพยากรรันไทม์
hlovdal

4
การใช้ไบนารี & กับ a char *และ a size_tจะทำให้เกิดข้อผิดพลาด uintptr_tคุณจะต้องใช้สิ่งที่ต้องการ
Marko

37

คุณสามารถลองposix_memalign()(บนแพลตฟอร์ม POSIX แน่นอน)


13
และ _aligned_malloc บน Windows
Steve Jessop

12
เมื่อไม่กี่ปีที่ผ่านมาฟังก์ชั่น "aligned_alloc" ได้กลายเป็นส่วนหนึ่งของข้อกำหนด C11: open-std.org/jtc1/sc22/wg14/www/docs/n1516.pdf (หน้า 346)
skagedal

20

นี่คือวิธีอื่นในการ 'ปัดเศษ' ส่วนหนึ่ง ไม่ใช่วิธีการเข้ารหัสที่ยอดเยี่ยมที่สุด แต่เป็นการทำให้งานเสร็จและไวยากรณ์ประเภทนี้จำได้ง่ายกว่าเล็กน้อย (บวกกับใช้งานได้กับค่าการจัดตำแหน่งที่ไม่ใช่พลังของ 2) uintptr_tหล่อเป็นสิ่งจำเป็นที่จะเอาใจคอมไพเลอร์; เลขคณิตของตัวชี้ไม่ได้เป็นที่ชื่นชอบการแบ่งหรือการคูณ

void *mem = malloc(1024 + 15);
void *ptr = (void*) ((uintptr_t) mem + 15) / 16 * 16;
memset_16aligned(ptr, 0, 1024);
free(mem);

2
โดยทั่วไปที่คุณ 'ยาวไม่ได้ลงนาม' คุณยังมี uintptr_t ซึ่งกำหนดไว้อย่างชัดเจนว่าใหญ่พอที่จะเก็บตัวชี้ข้อมูล (void *) แต่วิธีการแก้ปัญหาของคุณมีข้อดีถ้าด้วยเหตุผลบางอย่างคุณจำเป็นต้องมีการจัดตำแหน่งที่ไม่ใช่พลังของ 2 ไม่น่าจะเป็นไปได้ แต่เป็นไปได้
Jonathan Leffler

@Andrew: upvoted สำหรับประเภทของรูปแบบนี้เป็นบิตง่ายต่อการจดจำ (บวกจะทำงานให้ค่าการจัดตำแหน่งที่ไม่ได้เป็นอำนาจของ 2)
legends2k

19

น่าเสียดายที่ใน C99 นั้นค่อนข้างยากที่จะรับประกันการจัดเรียงของสิ่งใด ๆ ก็ตามซึ่งจะพกพาได้ในทุก ๆ การใช้งาน C ที่สอดคล้องกับ C99 ทำไม? เนื่องจากตัวชี้ไม่รับประกันว่าจะเป็น "ที่อยู่ไบต์" อย่างใดอย่างหนึ่งอาจจินตนาการกับรุ่นหน่วยความจำแบบแบน ไม่เป็นตัวแทนของuintptr_tดังนั้นรับประกันซึ่งตัวมันเองเป็นประเภทเสริมอยู่แล้ว

เราอาจรู้ว่าการใช้งานบางอย่างที่ใช้การแทนค่าเป็นโมฆะ * (และตามคำนิยาม, ยังchar * ) ซึ่งเป็นที่อยู่ไบต์ง่ายๆ แต่โดย C99 มันทึบแสงสำหรับเราโปรแกรมเมอร์ การนำไปใช้อาจเป็นตัวแทนของตัวชี้โดยชุด { เซ็กเมนต์ , ชดเชย } โดยที่อ็อฟเซ็ตอาจมีการจัดตำแหน่งแบบใครรู้ "ในความเป็นจริง" ทำไมตัวชี้อาจเป็นรูปแบบของค่าการค้นหาตารางแฮชหรือแม้แต่ค่าการค้นหาลิสต์ที่เชื่อมโยง มันสามารถเข้ารหัสข้อมูลขอบเขต

ในแบบร่าง C1X ล่าสุดสำหรับมาตรฐาน C เราจะเห็นคำหลัก_Alignas นั่นอาจช่วยสักหน่อย

การรับประกันเพียงอย่างเดียว C99 ให้กับเราคือฟังก์ชั่นการจัดสรรหน่วยความจำจะคืนพอยน์เตอร์ที่เหมาะสำหรับการกำหนดไปยังพอยน์เตอร์ที่ชี้ไปที่วัตถุชนิดใดก็ได้ เนื่องจากเราไม่สามารถระบุการจัดตำแหน่งของวัตถุเราจึงไม่สามารถใช้ฟังก์ชั่นการจัดสรรของเราเองด้วยความรับผิดชอบในการจัดตำแหน่งในแบบพกพาที่กำหนดไว้อย่างดี

มันจะเป็นการดีที่จะผิดเกี่ยวกับการเรียกร้องนี้


C11 aligned_alloc()มี (C ++ 11/14 / 1z ยังไม่มี) _Alignas()และ C ++ alignas()ไม่ได้ทำอะไรเลยสำหรับการจัดสรรแบบไดนามิกเฉพาะสำหรับการจัดเก็บอัตโนมัติและคงที่ (หรือเค้าโครงเค้าโครง)
Peter Cordes

15

บนช่องว่างด้านหน้า 16 vs 15 ไบต์จำนวนจริงที่คุณต้องเพิ่มเพื่อรับการจัดตำแหน่งของ N คือสูงสุด (0, NM)โดยที่ M คือการจัดตำแหน่งตามธรรมชาติของตัวจัดสรรหน่วยความจำ (และทั้งคู่เป็นกำลัง 2)

เนื่องจากการจัดตำแหน่งหน่วยความจำน้อยที่สุดของตัวจัดสรรใด ๆ คือ 1 ไบต์ 15 = สูงสุด (0,16-1) เป็นคำตอบที่ระมัดระวัง อย่างไรก็ตามหากคุณรู้ว่าตัวจัดสรรหน่วยความจำของคุณกำลังจะให้ที่อยู่ที่จัดชิดแบบ 32 บิต (ซึ่งค่อนข้างเป็นเรื่องธรรมดา) คุณอาจใช้ 12 เป็นแผ่นข้อมูล

นี่ไม่ใช่สิ่งสำคัญสำหรับตัวอย่างนี้ แต่อาจมีความสำคัญกับระบบสมองกลฝังตัวที่มี RAM ขนาด 12K ซึ่งจะนับจำนวน int ที่บันทึกไว้ทั้งหมด

วิธีที่ดีที่สุดในการปรับใช้ถ้าคุณกำลังพยายามบันทึกทุกไบต์ที่เป็นไปได้จะเป็นมาโครเพื่อให้คุณสามารถป้อนการจัดตำแหน่งหน่วยความจำดั้งเดิมของคุณ นี่อาจเป็นประโยชน์สำหรับระบบฝังตัวที่คุณต้องบันทึกทุกไบต์เท่านั้น

ในตัวอย่างด้านล่างสำหรับระบบส่วนใหญ่ค่า 1 นั้นใช้ได้MEMORY_ALLOCATOR_NATIVE_ALIGNMENTสำหรับระบบฝังตัวเชิงทฤษฎีของเราที่มีการจัดสรรแบบ 32 บิตการจัดสรรต่อไปนี้สามารถประหยัดหน่วยความจำอันมีค่าเล็กน้อย:

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

8

บางทีพวกเขาอาจจะได้รับความพึงพอใจที่มีความรู้ของmemalign ? และโจนาธานเลฟเลอร์ชี้ให้เห็นว่ามีฟังก์ชั่นใหม่ที่น่าสนใจกว่าสองรายการ

โอ๊ะฟลอรินตีฉันให้ได้ อย่างไรก็ตามหากคุณอ่าน man page ที่ฉันเชื่อมโยงไปคุณน่าจะเข้าใจตัวอย่างจากโปสเตอร์ก่อนหน้า


1
โปรดทราบว่าปัจจุบัน (กุมภาพันธ์ 2016) รุ่นของหน้าอ้างอิงกล่าวว่า "การmemalignฟังก์ชั่นที่ล้าสมัยและaligned_allocหรือposix_memalignควรจะนำมาใช้แทน" ฉันไม่รู้ว่ามันพูดอะไรในเดือนตุลาคม 2008 - แต่มันอาจจะไม่ได้พูดถึงaligned_alloc()เพราะมันถูกเพิ่มเข้าไปใน C11
Jonathan Leffler

5

เราทำสิ่งนี้ตลอดเวลาสำหรับ Accelerate.framework ซึ่งเป็นไลบรารี OS X / iOS แบบ vectorized อย่างมากซึ่งเราต้องให้ความสนใจกับการจัดตำแหน่งตลอดเวลา มีตัวเลือกค่อนข้างน้อยหนึ่งหรือสองอย่างที่ฉันไม่ได้เห็นดังกล่าวข้างต้น

วิธีที่เร็วที่สุดสำหรับอาเรย์ขนาดเล็กเช่นนี้ก็แค่ติดมันลงบนสแต็ก ด้วย GCC / เสียงดังกราว:

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

ไม่ต้องใช้ฟรี () โดยทั่วไปจะเป็นสองคำแนะนำ: ลบ 1024 จากตัวชี้สแต็กแล้วและตัวชี้สแต็กที่มี - การจัดตำแหน่ง สันนิษฐานว่าผู้ร้องขอต้องการข้อมูลบนฮีปเนื่องจากอายุการใช้งานของอาเรย์นั้นเกินกว่าที่สแต็กหรือการเรียกซ้ำอยู่ในที่ทำงานหรือพื้นที่สแต็กเป็นพรีเมี่ยมที่ร้ายแรง

บน OS X / iOS การโทรทั้งหมดไปที่ malloc / calloc / etc มีการจัดแนว 16 ไบต์เสมอ หากคุณต้องการการจัดแนว 32 ไบต์สำหรับ AVX คุณสามารถใช้ posix_memalign:

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

บางคนพูดถึงอินเตอร์เฟส C ++ ที่ใช้งานได้เหมือนกัน

ไม่ควรลืมว่าหน้าถูกจัดตำแหน่งให้มีกำลังมากถึงสองหน้าดังนั้นบัฟเฟอร์ที่จัดหน้าจะถูกจัดตำแหน่งด้วย 16 ไบต์ ดังนั้น mmap () และ valloc () และอินเทอร์เฟซที่คล้ายคลึงกันอื่น ๆ ก็เป็นตัวเลือกเช่นกัน mmap () มีข้อดีที่สามารถจัดสรรบัฟเฟอร์ได้ล่วงหน้าด้วยบางสิ่งที่ไม่เป็นศูนย์หากคุณต้องการ เนื่องจากสิ่งเหล่านี้มีขนาดหน้าชิดคุณจะไม่ได้รับการจัดสรรขั้นต่ำจากสิ่งเหล่านี้และอาจเป็นไปได้ที่ข้อผิดพลาดของ VM ในครั้งแรกที่คุณสัมผัส

Cheesy: เปิดยาม malloc หรือคล้ายกัน บัฟเฟอร์ที่มีขนาด n * 16 ไบต์เช่นนี้จะถูกจัดตำแหน่ง n * 16 ไบต์เนื่องจาก VM ถูกใช้เพื่อดักจับการบุกรุกและขอบเขตของมันอยู่ที่ขอบเขตของหน้า

ฟังก์ชัน Accelerate.framework บางอย่างใช้ในบัฟเฟอร์ชั่วคราวที่ผู้ใช้จัดหาเพื่อใช้เป็นพื้นที่เริ่มต้น ที่นี่เราต้องสมมติว่าบัฟเฟอร์ที่ส่งถึงเรานั้นไม่ตรงแนวและผู้ใช้พยายามอย่างหนักที่จะทำให้ชีวิตของเรายากลำบาก (กรณีทดสอบของเราติดหน้าการ์ดป้องกันไว้ด้านหน้าและด้านหลังบัฟเฟอร์ชั่วคราวเพื่อขีดเส้นใต้ทั้ง ๆ ที่นี่) เราคืนขนาดต่ำสุดที่เราต้องการเพื่อให้แน่ใจว่าส่วนของการจัดตำแหน่งแบบ 16 ไบต์นั้นอยู่ในตำแหน่งนั้น ขนาดนี้เป็นที่ต้องการ_size + alignment - 1 ดังนั้นในกรณีนี้คือ 1024 + 16 - 1 = 1,039 bytes จากนั้นจัดแนวตาม:

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

การเพิ่มการจัดตำแหน่ง -1 จะเลื่อนตัวชี้ไปยังที่อยู่แรกที่ผ่านการจัดตำแหน่งจากนั้น ANDing ด้วย -alignment (เช่น 0xfff ... ff0 สำหรับการจัดตำแหน่ง = 16) จะนำมันกลับไปยังที่อยู่ที่จัดตำแหน่ง

ตามที่อธิบายโดยโพสต์อื่น ๆ ในระบบปฏิบัติการอื่นที่ไม่มีการจัดตำแหน่งแบบ 16 ไบต์คุณสามารถเรียก malloc ด้วยขนาดที่ใหญ่ขึ้นตั้งค่าตัวชี้ฟรี () ในภายหลังจากนั้นจัดตำแหน่งตามที่อธิบายไว้ข้างต้นทันทีและใช้ตัวชี้ที่จัดตำแหน่ง อธิบายไว้สำหรับกรณีบัฟเฟอร์ temp ของเรา

สำหรับ aligned_memset มันค่อนข้างงี่เง่า คุณต้องวนลูปสูงสุด 15 ไบต์เพื่อเข้าถึงที่อยู่ที่จัดตำแหน่งแล้วดำเนินการตามร้านค้าที่จัดตำแหน่งหลังจากนั้นด้วยรหัสการล้างข้อมูลที่เป็นไปได้บางส่วนในตอนท้าย คุณสามารถทำบิตการทำความสะอาดในโค้ดเว็กเตอร์ไม่ว่าจะเป็นร้านค้าที่ไม่มีการจัดแนวที่ซ้อนทับพื้นที่ที่จัดชิด (หากความยาวอย่างน้อยความยาวของเวกเตอร์) หรือการใช้งานบางอย่างเช่น movmaskdqu บางคนขี้เกียจ อย่างไรก็ตามอาจเป็นคำถามสัมภาษณ์ที่สมเหตุสมผลหากผู้สัมภาษณ์ต้องการทราบว่าคุณพอใจกับ stdint.h ผู้ประกอบการระดับบิตและพื้นฐานหน่วยความจำหรือไม่ดังนั้นตัวอย่างที่วางแผนไว้สามารถได้รับการอภัย


5

ฉันประหลาดใจที่ไม่มีใครโหวตคำตอบของShaoว่าในขณะที่ฉันเข้าใจมันเป็นไปไม่ได้ที่จะทำสิ่งที่ถามในมาตรฐาน C99 เนื่องจากการแปลงตัวชี้เป็นประเภทหนึ่งอย่างเป็นทางการนั้นเป็นพฤติกรรมที่ไม่ได้กำหนด (นอกเหนือจากมาตรฐานที่อนุญาตให้แปลง<-> แต่มาตรฐานดูเหมือนจะไม่อนุญาตให้ทำการปรับเปลี่ยนค่าใด ๆแล้วแปลงกลับมา)uintptr_tvoid*uintptr_t


ไม่มีข้อกำหนดว่ามีประเภท uintptr_t อยู่หรือว่าบิตมีความสัมพันธ์กับบิตในตัวชี้พื้นฐาน หากมีการจัดเก็บข้อมูลมากกว่าจัดสรรเก็บชี้เป็นunsigned char* myptr; และจากนั้นคำนวณ `mptr + = (16- (uintptr_t) my_ptr) & 0x0F พฤติกรรมจะถูกกำหนดในการใช้งานทั้งหมดที่กำหนด my_ptr แต่ไม่ว่าตัวชี้ผลลัพธ์จะจัดตำแหน่งจะขึ้นอยู่กับการแมประหว่างบิต uintptr_t และที่อยู่
supercat

3

การใช้ memalign, Aligned-Memory-Blocksอาจเป็นทางออกที่ดีสำหรับปัญหา


โปรดทราบว่าปัจจุบัน (กุมภาพันธ์ 2016) รุ่นของหน้าอ้างอิงกล่าวว่า "การmemalignฟังก์ชั่นที่ล้าสมัยและaligned_allocหรือposix_memalignควรจะนำมาใช้แทน" ฉันไม่รู้ว่าจะพูดอะไรในเดือนตุลาคม 2010
Jonathan Leffler

3

สิ่งแรกที่โผล่เข้ามาในหัวของฉันเมื่ออ่านคำถามนี้คือการกำหนดโครงสร้างที่สอดคล้องกันยกตัวอย่างแล้วชี้ไปที่มัน

มีเหตุผลพื้นฐานที่ฉันหายไปหรือเปล่าเพราะไม่มีใครแนะนำสิ่งนี้?

ในฐานะ sidenote เนื่องจากฉันใช้อาร์เรย์ของถ่าน (สมมติว่าถ่านของระบบคือ 8 บิต (เช่น 1 ไบต์)) ฉันไม่เห็นความจำเป็นในการ__attribute__((packed))จำเป็น (แก้ไขฉันถ้าฉันผิด) แต่ฉันวางไว้ ในทางใดทางหนึ่ง.

สิ่งนี้ใช้ได้กับทั้งสองระบบที่ฉันลองใช้ แต่เป็นไปได้ว่ามีการเพิ่มประสิทธิภาพคอมไพเลอร์ที่ฉันไม่ทราบว่าจะให้ผลบวกที่ผิดพลาดกับประสิทธิภาพของรหัส ฉันใช้gcc 4.9.2บน OSX และgcc 5.2.1บน 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;
}

1

MacOS X เฉพาะ:

  1. พอยน์เตอร์ทั้งหมดที่จัดสรรด้วย malloc อยู่ในแนวเดียวกัน 16 ไบต์
  2. รองรับ C11 ดังนั้นคุณสามารถโทรไปที่ aligned_malloc (ขนาด 16)

  3. MacOS X เลือกรหัสที่ปรับให้เหมาะสมสำหรับโปรเซสเซอร์แต่ละตัวในเวลาบูตสำหรับ memset, memcpy และ memmove และรหัสนั้นใช้เทคนิคที่คุณไม่เคยได้ยินมาก่อนเพื่อให้เร็ว โอกาส 99% ที่ memset รันเร็วกว่า memset ที่เขียนด้วยมือ 16 ซึ่งทำให้คำถามทั้งหมดไม่มีจุดหมาย

หากคุณต้องการโซลูชันแบบพกพา 100% ก่อน C11 จะไม่มีเลย เนื่องจากไม่มีวิธีพกพาในการทดสอบการจัดตำแหน่งของตัวชี้ หากไม่จำเป็นต้องพกพาได้ 100% คุณสามารถใช้

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

นี่ถือว่าการจัดตำแหน่งของตัวชี้ถูกเก็บไว้ในบิตต่ำสุดเมื่อแปลงตัวชี้ไปยัง int ที่ไม่ได้ลงนาม การแปลงเป็น int ที่ไม่ได้ลงชื่อจะสูญเสียข้อมูลและมีการกำหนดการใช้งาน แต่นั่นไม่สำคัญเพราะเราไม่แปลงผลลัพธ์กลับเป็นตัวชี้

ส่วนที่น่ากลัวนั้นแน่นอนว่าต้องมีการบันทึกตัวชี้ดั้งเดิมไว้ที่ใดที่หนึ่งเพื่อโทรฟรี () ด้วย ดังนั้นโดยรวมฉันจะสงสัยในความฉลาดของการออกแบบนี้


1
คุณจะหาที่ไหนaligned_mallocใน OS X ฉันใช้ Xcode 6.1 และไม่ได้กำหนดไว้ที่ใด ๆ ใน iOS SDK และไม่ได้ประกาศไว้ที่ใด/usr/include/*
ทอดด์เลห์แมน

เหมือนกันสำหรับ XCode 7.2 บน El Capitan (Mac OS X 10.11.3) ฟังก์ชั่น C11 ไม่ว่าในกรณีใด ๆaligned_alloc()แต่ก็ไม่ได้ประกาศเช่นกัน จาก GCC 5.3.0 ฉันได้รับข้อความที่น่าสนใจและalig.c:7:15: error: incompatible implicit declaration of built-in function ‘aligned_alloc’ [-Werror] alig.c:7:15: note: include ‘<stdlib.h>’ or provide a declaration of ‘aligned_alloc’รหัสไม่แน่นอนรวมถึง<stdlib.h>แต่ไม่-std=c11ว่ามิได้-std=gnu11เปลี่ยนข้อความผิดพลาด
Jonathan Leffler

0

คุณสามารถเพิ่ม 16 ไบต์บางส่วนจากนั้นกด ptr ดั้งเดิมให้เป็น 16 บิตที่จัดตำแหน่งโดยการเพิ่ม (16-mod) ด้านล่างตัวชี้:

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

หากมีข้อ จำกัด คุณจะไม่สามารถเสียไบต์เดียวโซลูชันนี้จึงใช้งานได้: หมายเหตุ: มีบางกรณีที่อาจถูกเรียกใช้งานอย่างไม่สิ้นสุด: D

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

มีโอกาสดีมากที่ถ้าคุณจัดสรรและเพิ่มบล็อก N ไบต์แล้วให้บล็อกอีกบล็อกหนึ่งของ N ไบต์บล็อกเดิมจะถูกส่งกลับอีกครั้ง ดังนั้นการวนซ้ำไม่สิ้นสุดมีโอกาสมากถ้าการจัดสรรครั้งแรกไม่ตรงตามข้อกำหนดการจัดตำแหน่ง แน่นอนว่าเพื่อหลีกเลี่ยงการเสียไบต์เดียวในราคาที่สิ้นเปลือง CPU มาก ๆ
Jonathan Leffler

คุณแน่ใจหรือว่า%ผู้ปฏิบัติงานได้รับการกำหนดvoid*อย่างมีความหมาย?
Ajay Brahmakshatriya

0

สำหรับวิธีการแก้ปัญหาฉันใช้แนวคิดของการขยายซึ่งจัดตำแหน่งหน่วยความจำและไม่เสียหน่วยความจำของไบต์เดียว

หากมีข้อ จำกัด คุณจะไม่สามารถเสียไบต์เดียว พอยน์เตอร์ทั้งหมดที่จัดสรรด้วย malloc อยู่ในแนวเดียวกัน 16 ไบต์

รองรับ C11 ดังนั้นคุณสามารถโทรaligned_alloc (16, size)ได้

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

1
ในระบบ 64 บิตหลาย ๆ ตัวชี้ที่ส่งคืนมาmalloc()นั้นจะถูกจัดตำแหน่งบนขอบเขต 16- ไบต์ แต่ไม่มีสิ่งใดในมาตรฐานที่รับประกันว่า - มันจะได้รับการจัดตำแหน่งให้เพียงพอสำหรับการใช้งานใด ๆ และในระบบ 32- บิตจำนวนมาก เขตแดน 8 ไบต์เพียงพอและสำหรับบางเขตแดนขนาด 4 ไบต์ก็เพียงพอแล้ว
Jonathan Leffler

0
size =1024;
alignment = 16;
aligned_size = size +(alignment -(size %  alignment));
mem = malloc(aligned_size);
memset_16aligned(mem, 0, 1024);
free(mem);

หวังว่าอันนี้เป็นการใช้งานที่ง่ายที่สุดขอให้ฉันรู้ความคิดเห็นของคุณ


-3
long add;   
mem = (void*)malloc(1024 +15);
add = (long)mem;
add = add - (add % 16);//align to 16 byte boundary
ptr = (whatever*)(add);

ฉันคิดว่ามีปัญหาเกิดขึ้นกับสิ่งนี้เนื่องจากการเพิ่มของคุณจะชี้ไปยังตำแหน่งที่ไม่ใช่ malloc'd - ไม่แน่ใจว่าสิ่งนี้ทำงานกับคุณได้อย่างไร
resultsway

@ Sam add += 16 - (add % 16)มันควรจะเป็น (2 - (2 % 16)) == 0.
SS Anne
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.