原始答案
{
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
和对象的指针和指向函数当你在做更专业的事情(例如玩图形系统)时,他们可能需要比系统其他部分更严格的对齐方式-因此是这样的问题和答案。)
下一步是将void指针转换为char指针;尽管有GCC,您也不应该对空指针进行指针算术(并且GCC有警告选项可以告诉您何时滥用它)。然后将16添加到开始指针。假设malloc()
返回了一个不可能对齐的指针:0x800001。将16加到0x800011。现在我想向下舍入到16字节边界-所以我想将最后4位重置为0。0x0F将最后4位设置为1;将0x0F设置为1。因此,~0x0F
除了最后四个以外,所有位都设置为1。将0x800011与Anding得出0x800010。您可以遍历其他偏移量并看到相同的算法。
最后一个步骤free()
很容易:您总是并且唯一地返回free()
一个值malloc()
,calloc()
或者realloc()
返回给您-其他任何事情都是灾难。您正确提供mem
了该值,谢谢。免费发布它。
最后,如果您了解系统malloc
软件包的内部结构,您可能会猜测它很可能返回16字节对齐的数据(或者可能是8字节对齐的)。如果它是16字节对齐的,则无需使用这些值。但是,这是狡猾且不可移植的-其他malloc
软件包具有不同的最小对齐方式,因此,假设一件事情做一些不同的事情会导致核心转储。在广泛的范围内,该解决方案是便携式的。
提到posix_memalign()
其他人是获取对齐内存的另一种方法。并非到处都有,但通常可以以此为基础来实现。请注意,对齐方式是2的幂是很方便的。其他路线则比较混乱。
还有一个注释-此代码不检查分配是否成功。
修正案
Windows Programmer指出您不能对指针执行位掩码操作,并且确实,GCC(已测试3.4.6和4.3.1)确实有这样的抱怨。因此,随后是基本代码的修改版本-转换为主程序。正如我已经指出的,我也自由选择只添加15而不是16。我使用uintptr_t
C99的时间已经足够长,可以在大多数平台上访问了。如果不是PRIXPTR
在printf()
语句中使用,则只需#include <stdint.h>
使用即可#include <inttypes.h>
。[此代码包括CR指出的修复程序,该声明重申了Bill K数年前首次提出的观点,直到现在我一直忽略了这一点。]
#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)评论说:也许我今天早上在阅读理解问题,但是如果面试问题特别指出:“您将如何分配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
应为的2的幂sizeof(void *)
。
成功完成后,所指向的值memptr
应为的倍数alignment
。
如果请求的空间大小为0,则行为是实现定义的;返回的值memptr
应为空指针或唯一指针。
该free()
功能应取消分配先前由分配的内存posix_memalign()
。
返回值
成功完成后,posix_memalign()
应返回零;否则,返回零。否则,将返回一个错误编号以指示错误。
现在,可以使用这两种方法中的一种或两种方法来回答问题,但是当最初回答问题时,仅POSIX功能是一个选项。
在幕后,新的对齐内存功能与问题中概述的功能大致相同,不同之处在于它们能够更轻松地强制对齐,并在内部跟踪对齐内存的开始,从而使代码不会必须专门处理-它只是释放使用的分配函数返回的内存。