我刚刚完成了工作面试的一部分测试,有一个问题难住了我,甚至用谷歌作为参考。我想看看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
}
MacOS X专用:
所有用malloc分配的指针都是16字节对齐的。
C11是支持的,所以你可以调用aligned_malloc (16, size)。
MacOS X在启动时为memset、memcpy和memmove的各个处理器选择了优化的代码,这些代码使用了你从未听说过的技巧来提高速度。99%的概率memset比任何手写的memset16运行得更快,这使得整个问题毫无意义。
如果你想要一个100%可移植的解决方案,在C11之前没有。因为没有可移植的方法来测试指针的对齐方式。如果它不需要100%便携,你可以使用
char* p = malloc (size + 15);
p += (- (unsigned int) p) % 16;
这假设将指针转换为unsigned int时,指针的对齐方式存储在最低位。转换为unsigned int会丢失信息,并且是实现定义的,但这并不重要,因为我们没有将结果转换回指针。
最可怕的部分当然是原始指针必须保存在某个地方,以便用它调用free()。所以总的来说,我真的怀疑这个设计是否明智。
读到这个问题时,我脑子里冒出的第一件事是定义一个对齐的结构,实例化它,然后指向它。
有没有什么根本的原因,因为没有人建议我这么做?
作为旁注,由于我使用了一个char数组(假设系统的char是8位(即1字节)),我认为不一定需要__attribute__((包装))(如果我错了请纠正我),但我还是把它放了进去。
这在我尝试的两个系统上都有效,但有可能是我不知道的编译器优化给了我关于代码有效性的误报。我在OSX上使用gcc 4.9.2,在Ubuntu上使用gcc 5.2.1。
#include <stdio.h>
#include <stdlib.h>
int main ()
{
void *mem;
void *ptr;
// answer a) here
struct __attribute__((packed)) s_CozyMem {
char acSpace[16];
};
mem = malloc(sizeof(struct s_CozyMem));
ptr = mem;
// memset_16aligned(ptr, 0, 1024);
// Check if it's aligned
if(((unsigned long)ptr & 15) == 0) printf("Aligned to 16 bytes.\n");
else printf("Rubbish.\n");
// answer b) here
free(mem);
return 1;
}
不幸的是,在C99中,似乎很难保证在任何符合C99的C实现之间都是可移植的。为什么?因为指针不能保证是平面内存模型中想象的“字节地址”。uintptr_t的表示也不是那么有保证,它本身是一个可选类型。
我们可能知道一些实现使用了表示void *(根据定义,也有char *),这是一个简单的字节地址,但在C99中,它对我们程序员来说是不透明的。一个实现可以用集合{segment, offset}表示一个指针,其中offset在“实际”中可能有谁知道的对齐方式。为什么,指针甚至可以是某种形式的哈希表查找值,甚至是链表查找值。它可以编码边界信息。
在最近的C标准C1X草案中,我们看到了_Alignas关键字。这可能会有所帮助。
C99给我们的唯一保证是内存分配函数将返回一个适合赋值给指向任何对象类型的指针的指针。因为我们不能指定对象的对齐方式,所以我们不能以定义良好的、可移植的方式实现我们自己的分配函数来负责对齐。
如果这种说法是错误的,那就好了。