Câu trả lời gốc
{
void *mem = malloc(1024+16);
void *ptr = ((char *)mem+16) & ~ 0x0F;
memset_16aligned(ptr, 0, 1024);
free(mem);
}
Trả lời cố định
{
void *mem = malloc(1024+15);
void *ptr = ((uintptr_t)mem+15) & ~ (uintptr_t)0x0F;
memset_16aligned(ptr, 0, 1024);
free(mem);
}
Giải thích theo yêu cầu
Bước đầu tiên là phân bổ đủ không gian dự phòng, chỉ trong trường hợp. Vì bộ nhớ phải được căn chỉnh 16 byte (có nghĩa là địa chỉ byte hàng đầu cần phải là bội số của 16), nên thêm 16 byte đảm bảo rằng chúng ta có đủ không gian. Ở đâu đó trong 16 byte đầu tiên, có một con trỏ được căn chỉnh 16 byte. (Lưu ý rằng malloc()
có nghĩa vụ phải trả về một con trỏ được đầy đủ cũng liên kết với bất kỳ . Mục đích Tuy nhiên, ý nghĩa của 'bất kỳ' là chủ yếu cho những thứ như cơ bản loại - long
, double
, long double
, long long
., Và con trỏ đến đối tượng và con trỏ đến chức năng Khi bạn đang ở làm những việc chuyên biệt hơn, như chơi với các hệ thống đồ họa, chúng có thể cần sự liên kết chặt chẽ hơn so với phần còn lại của hệ thống - do đó các câu hỏi và câu trả lời như thế này.)
Bước tiếp theo là chuyển đổi con trỏ void thành con trỏ char; Mặc dù vậy, GCC không được phép thực hiện số học con trỏ trên các con trỏ void (và GCC có các tùy chọn cảnh báo để cho bạn biết khi bạn lạm dụng nó). Sau đó thêm 16 vào con trỏ bắt đầu. Giả sử malloc()
trả về cho bạn một con trỏ được căn chỉnh cực kỳ tệ: 0x800001. Thêm 16 cho 0x800011. Bây giờ tôi muốn làm tròn xuống ranh giới 16 byte - vì vậy tôi muốn đặt lại 4 bit cuối cùng thành 0. 0x0F có 4 bit cuối cùng được đặt thành một; do đó, ~0x0F
có tất cả các bit được đặt thành một ngoại trừ bốn bit cuối cùng. Anding rằng với 0x800011 cho 0x800010. Bạn có thể lặp lại các lần bù khác và thấy rằng cùng một số học hoạt động.
Bước cuối cùng, free()
là dễ dàng: bạn luôn, và chỉ trở về free()
một giá trị mà một trong malloc()
, calloc()
hoặc realloc()
trả lại cho bạn - bất cứ điều gì khác là một thảm họa. Bạn cung cấp chính xác mem
để giữ giá trị đó - cảm ơn bạn. Miễn phí phát hành nó.
Cuối cùng, nếu bạn biết về các phần bên trong của malloc
gói hệ thống của mình , bạn có thể đoán rằng nó cũng có thể trả về dữ liệu được căn chỉnh 16 byte (hoặc có thể được căn chỉnh 8 byte). Nếu nó được căn chỉnh 16 byte, thì bạn không cần phải làm mờ các giá trị. Tuy nhiên, đây là tinh ranh và không di động - các malloc
gói khác có sự sắp xếp tối thiểu khác nhau, và do đó, giả sử một điều khi nó làm một cái gì đó khác nhau sẽ dẫn đến các bãi rác cốt lõi. Trong giới hạn rộng, giải pháp này là di động.
Một số người khác được đề cập posix_memalign()
như một cách khác để có được bộ nhớ phù hợp; không có sẵn ở mọi nơi, nhưng thường có thể được thực hiện bằng cách sử dụng điều này làm cơ sở. Lưu ý rằng thật thuận tiện khi căn chỉnh là lũy thừa 2; sắp xếp khác là lộn xộn hơn.
Thêm một nhận xét - mã này không kiểm tra việc phân bổ đã thành công.
Sửa đổi
Lập trình viên Windows chỉ ra rằng bạn không thể thực hiện các thao tác mặt nạ bit trên con trỏ và thực tế, GCC (đã kiểm tra 3.4.6 và 4.3.1) đã phàn nàn như vậy. Vì vậy, một phiên bản sửa đổi của mã cơ bản - được chuyển đổi thành một chương trình chính, theo sau. Tôi cũng có quyền tự do chỉ thêm 15 thay vì 16, như đã được chỉ ra. Tôi đang sử dụng uintptr_t
vì C99 đã có khoảng thời gian đủ dài để có thể truy cập trên hầu hết các nền tảng. Nếu nó không được sử dụng PRIXPTR
trong các printf()
báo cáo, nó sẽ đủ để #include <stdint.h>
thay vì sử dụng #include <inttypes.h>
. [Mã này bao gồm bản sửa lỗi được chỉ ra bởi CR , được nhắc lại một điểm đầu tiên được thực hiện bởi Bill K một số năm trước, mà tôi đã bỏ qua cho đến bây giờ.]
#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);
}
Và đây là một phiên bản tổng quát hơn một chút, sẽ hoạt động với các kích thước có sức mạnh bằng 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);
}
Để chuyển đổi test_mask()
thành hàm phân bổ mục đích chung, giá trị trả về duy nhất từ bộ cấp phát sẽ phải mã hóa địa chỉ phát hành, như một số người đã chỉ ra trong câu trả lời của họ.
Vấn đề với người phỏng vấn
Uri nhận xét: Có thể tôi đang gặp [a] vấn đề đọc hiểu sáng nay, nhưng nếu câu hỏi phỏng vấn nói cụ thể: "Làm thế nào bạn sẽ phân bổ 1024 byte bộ nhớ" và bạn phân bổ rõ ràng hơn thế. Đó sẽ không phải là một thất bại tự động từ người phỏng vấn?
Phản hồi của tôi sẽ không phù hợp với nhận xét 300 ký tự ...
Nó phụ thuộc, tôi cho rằng. Tôi nghĩ rằng hầu hết mọi người (bao gồm cả tôi) đã đặt câu hỏi có nghĩa là "Làm thế nào bạn có thể phân bổ một không gian trong đó 1024 byte dữ liệu có thể được lưu trữ và trong đó địa chỉ cơ sở là bội số của 16 byte". Nếu người phỏng vấn thực sự có nghĩa là làm thế nào bạn có thể phân bổ 1024 byte (chỉ) và có 16 byte được căn chỉnh, thì các tùy chọn bị hạn chế hơn.
- Rõ ràng, một khả năng là phân bổ 1024 byte và sau đó cung cấp cho địa chỉ đó 'điều trị căn chỉnh'; vấn đề với cách tiếp cận đó là không gian có sẵn thực tế không được xác định đúng (không gian có thể sử dụng nằm trong khoảng từ 1008 đến 1024 byte, nhưng không có cơ chế có sẵn để chỉ định kích thước nào), khiến nó không hữu dụng.
- Một khả năng khác là bạn dự kiến sẽ viết một bộ cấp phát bộ nhớ đầy đủ và đảm bảo rằng khối 1024 byte mà bạn trả về được căn chỉnh phù hợp. Nếu đó là trường hợp, có lẽ bạn sẽ thực hiện một thao tác khá giống với những gì giải pháp đề xuất đã làm, nhưng bạn ẩn nó bên trong bộ cấp phát.
Tuy nhiên, nếu người phỏng vấn mong đợi một trong những câu trả lời đó, tôi mong họ nhận ra rằng giải pháp này trả lời một câu hỏi liên quan chặt chẽ, và sau đó điều chỉnh lại câu hỏi của họ để chỉ cuộc trò chuyện theo đúng hướng. (Hơn nữa, nếu người phỏng vấn thực sự khó tính, thì tôi sẽ không muốn công việc; nếu câu trả lời cho một yêu cầu không chính xác bị bắn hạ trong ngọn lửa mà không sửa, thì người phỏng vấn không phải là người an toàn để làm việc.)
Thế giới chuyển sang
Tiêu đề của câu hỏi đã thay đổi gần đây. Đó là giải quyết sự liên kết bộ nhớ trong câu hỏi phỏng vấn C làm tôi bối rối . Tiêu đề sửa đổi ( Cách phân bổ bộ nhớ được căn chỉnh chỉ bằng thư viện chuẩn? ) Yêu cầu một câu trả lời được sửa đổi một chút - phụ lục này cung cấp nó.
Chức năng được thêm vào C11 (ISO / IEC 9899: 2011) aligned_alloc()
:
7.22.3.1 aligned_alloc
Hàm
Tóm tắc
#include <stdlib.h>
void *aligned_alloc(size_t alignment, size_t size);
Mô tả
Các aligned_alloc
không gian chức năng giao đất cho một đối tượng có liên kết được xác định bởi alignment
, có kích thước được xác định bởi size
, và có giá trị là không xác định. Giá trị của alignment
sẽ là một căn chỉnh hợp lệ được hỗ trợ bởi việc thực hiện và giá trị của size
sẽ là một bội số của alignment
.
Trả
Các aligned_alloc
chức năng lợi nhuận hoặc một con trỏ null hoặc một con trỏ để không gian được phân bổ.
Và POSIX định nghĩa posix_memalign()
:
#include <stdlib.h>
int posix_memalign(void **memptr, size_t alignment, size_t size);
SỰ MIÊU TẢ
Các posix_memalign()
chức năng có trách nhiệm phân bổ size
byte xếp trên một ranh giới được xác định bởi alignment
, và sẽ trả về một con trỏ tới bộ nhớ phân bổ memptr
. Giá trị của alignment
sẽ là một lũy thừa của hai bội số sizeof(void *)
.
Sau khi hoàn thành thành công, giá trị được trỏ đến memptr
sẽ là bội số của alignment
.
Nếu kích thước của không gian được yêu cầu là 0, thì hành vi được xác định theo thực hiện; giá trị được trả về memptr
sẽ là con trỏ null hoặc con trỏ duy nhất.
Các free()
chức năng có trách nhiệm deallocate nhớ rằng trước đây đã được giao posix_memalign()
.
GIÁ TRỊ TRẢ LẠI
Sau khi hoàn thành, posix_memalign()
sẽ trả về 0; mặt khác, một số lỗi sẽ được trả về để chỉ ra lỗi.
Bây giờ hoặc cả hai đều có thể được sử dụng để trả lời câu hỏi, nhưng chỉ có chức năng POSIX là một tùy chọn khi câu hỏi ban đầu được trả lời.
Đằng sau hậu trường, chức năng bộ nhớ căn chỉnh mới thực hiện nhiều công việc tương tự như được nêu trong câu hỏi, ngoại trừ chúng có khả năng buộc căn chỉnh dễ dàng hơn và theo dõi sự khởi đầu của bộ nhớ được căn chỉnh bên trong để mã không phải xử lý đặc biệt - nó chỉ giải phóng bộ nhớ được trả về bởi hàm phân bổ đã được sử dụng.