抱歉,我无法执行您的请求。我只能用英语回答问题。
{
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 之外,您不应该在 void 指针上执行指针算术运算(GCC 有警告选项告诉您是否滥用了它)。 然后将起始指针增加16。 假设 malloc()
返回无法对齐的指针:0x800001。 添加16会得到 0x800011。现在我要将其向下舍入到16字节边界-因此,我要将最后4位重置为 0。 0x0F 的最后4位设置为1;因此,~0x0F
具有除最后四个以外的所有位都设置为1。对其与0x800011进行 AND 操作可以得到0x800010。您可以遍历其他偏移量并查看是否同样适用该算法。
free()
的最后一步很容易:您始终只返回 malloc()
、calloc()
或 realloc()
返回给您的值,否则就会出问题。 您正确地提供了 mem
来保存该值-谢谢。 free() 释放它。
最后,如果您了解系统的 malloc
包的内部工作方式,那么您可能会猜测它可能会返回 16 字节对齐的数据(或者是 8 字节对齐)。 如果它是 16 字节对齐的,则不需要调整值。但这是不稳定和不可移植的,因为其他 malloc
包具有不同的最小对齐方式,因此假设一件事情并做另一件事情可能会导致核心转储。 在广泛的限制范围内,此解决方案是可移植的。
其他评论-此代码不检查分配是否成功。
修正
Windows Programmer 指出指针不能进行位掩码操作,实际上,GCC(已测试3.4.6和4.3.1)确实会发出警告。因此,下面是一个基本代码的修正版本——转换为主程序。我还修改了15而不是16,正如之前指出的那样。由于C99已经存在了足够长的时间以便在大多数平台上使用,因此我正在使用 uintptr_t
。如果没有在printf()
语句中使用PRIXPTR
,只需#include <stdint.h>
即可,而无需使用#include <inttypes.h>
。[这段代码包括C.R.指出的问题修复,该问题一开始由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);
}
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);
}
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
函数
Synopsis
#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
中返回的值应为null指针或唯一指针。
free()
函数将释放先前由posix_memalign()
分配的内存。
返回值
成功完成后,posix_memalign()
将返回零; 否则,将返回错误号以指示出错。
现在可以使用其中任何一个或两个来回答问题,但最初回答问题时,只有POSIX函数可用。
在幕后,新的对齐内存函数执行与问题中概述的相同工作,除了它们能够更轻松地强制对齐,并在内部跟踪对齐内存的开始,以便代码不必特别处理 - 它只释放使用的分配函数返回的内存。