คำตอบเดิม
{
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_alloc
alignment
size
alignment
size
alignment
ส่งกลับกลับมาทำงานอย่างใดอย่างหนึ่งชี้โมฆะหรือตัวชี้ไปยังพื้นที่ที่จัดสรร
aligned_alloc
และ POSIX กำหนดposix_memalign()
:
#include <stdlib.h>
int posix_memalign(void **memptr, size_t alignment, size_t size);
รายละเอียด
posix_memalign()
ฟังก์ชั่นต้องจัดสรรsize
ไบต์จัดตำแหน่งบนขอบที่ระบุโดยและจะกลับตัวชี้ไปจัดสรรหน่วยความจำในalignment
memptr
ค่าของให้เป็นอำนาจของสองหลายของalignment
sizeof(void *)
เมื่อสำเร็จค่าชี้ไปจะเป็นหลายmemptr
ๆalignment
หากขนาดของพื้นที่ที่ร้องขอเป็น 0 พฤติกรรมนั้นจะถูกนำไปใช้งาน ค่าที่ส่งคืนmemptr
จะต้องเป็นตัวชี้โมฆะหรือตัวชี้ที่ไม่ซ้ำกัน
free()
ฟังก์ชั่นจะ deallocate posix_memalign()
หน่วยความจำที่ได้รับการจัดสรรโดยก่อนหน้านี้
คืนค่า
เมื่อสำเร็จposix_memalign()
จะกลับมาเป็นศูนย์ มิฉะนั้นจะส่งคืนหมายเลขข้อผิดพลาดเพื่อระบุข้อผิดพลาด
สามารถใช้วิธีใดวิธีหนึ่งหรือทั้งสองอย่างเพื่อตอบคำถามในตอนนี้ แต่เฉพาะฟังก์ชัน POSIX เท่านั้นที่เป็นตัวเลือกเมื่อตอบคำถามครั้งแรก
เบื้องหลังของฟังก์ชั่นหน่วยความจำที่จัดเรียงใหม่นั้นทำหน้าที่เดียวกับที่ระบุไว้ในคำถามยกเว้นพวกเขามีความสามารถในการบังคับให้จัดตำแหน่งได้ง่ายขึ้นและติดตามจุดเริ่มต้นของหน่วยความจำที่จัดตำแหน่งไว้ภายในเพื่อไม่ให้รหัส ต้องจัดการกับพิเศษ - มันเพียงปลดปล่อยหน่วยความจำที่ส่งคืนโดยฟังก์ชั่นการจัดสรรที่ใช้