我刚刚完成了工作面试的一部分测试,有一个问题难住了我,甚至用谷歌作为参考。我想看看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    
}

当前回答

我很惊讶没有人投票赞成Shao的回答,据我所知,在标准C99中不可能做到要求的事情,因为将指针转换为整型在形式上是未定义的行为。(除了标准允许uintptr_t <-> void*的转换,但标准似乎不允许做uintptr_t值的任何操作,然后将其转换回来。)

其他回答

这里有一个“四舍五入”部分的替代方法。不是最出色的编码解决方案,但它完成了工作,这种类型的语法更容易记住(plus将适用于对齐值不是2的幂)。uintptr_t强制转换是必要的,以安抚编译器;指针算术不太喜欢除法或乘法。

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

在16字节计数vs 15字节计数的填充前面,为了获得N的对齐,您需要添加的实际数字是max(0,N-M),其中M是内存分配器的自然对齐(两者都是2的幂)。

由于任何分配器的最小内存对齐都是1字节,因此15=max(0,16-1)是一个保守的答案。然而,如果你知道你的内存分配器将给你32位整型对齐的地址(这是相当常见的),你可以使用12作为一个垫。

这对于本例来说并不重要,但对于具有12K RAM的嵌入式系统来说可能很重要,因为其中保存的每个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)

三个稍微不同的答案取决于你如何看待这个问题:

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)中只向mem添加了15个字节不同,如果您的实现恰好保证了malloc的32字节对齐(不太可能,但理论上C实现可以有32字节对齐类型),那么这段代码实际上可以减少对齐。如果您所做的只是调用memset_16aligned,那么这并不重要,但如果您为结构体使用内存,那么这可能很重要。

我不确定一个好的修复是什么(除了警告用户返回的缓冲区不一定适合任意结构),因为没有办法通过编程确定特定于实现的对齐保证是什么。我猜在启动时,您可以分配两个或更多的1字节缓冲区,并假设您看到的最糟糕的对齐方式是保证对齐方式。如果你错了,你就浪费了记忆。谁有更好的主意,请说出来…

[Added: The 'standard' trick is to create a union of 'likely to be maximally aligned types' to determine the requisite alignment. The maximally aligned types are likely to be (in C99) 'long long', 'long double', 'void *', or 'void (*)(void)'; if you include <stdint.h>, you could presumably use 'intmax_t' in place of long long (and, on Power 6 (AIX) machines, intmax_t would give you a 128-bit integer type). The alignment requirements for that union can be determined by embedding it into a struct with a single char followed by the union:

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)和上面计算的对齐值中较大的一个。

在(64位)Solaris 10上,来自malloc()的结果的基本对齐方式似乎是32字节的倍数。 ]

在实践中,对齐分配器通常采用一个参数进行对齐,而不是硬连接。因此,用户将传递他们所关心的结构体的大小(或大于或等于2的最小次幂),一切都将正常。

3)使用你的平台提供的:posix_memalign用于POSIX, _aligned_malloc用于Windows。

4)如果你使用C11,那么最干净——可移植和简洁——的选项是使用在这个版本的语言规范中引入的标准库函数aligned_alloc。

对于解决方案,我使用了一个概念的填充对齐内存和不浪费 单个字节的内存。

如果存在这样的约束,就不能浪费一个字节。 所有用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);

我很惊讶没有人投票赞成Shao的回答,据我所知,在标准C99中不可能做到要求的事情,因为将指针转换为整型在形式上是未定义的行为。(除了标准允许uintptr_t <-> void*的转换,但标准似乎不允许做uintptr_t值的任何操作,然后将其转换回来。)