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

当前回答

不幸的是,在C99中,似乎很难保证在任何符合C99的C实现之间都是可移植的。为什么?因为指针不能保证是平面内存模型中想象的“字节地址”。uintptr_t的表示也不是那么有保证,它本身是一个可选类型。

我们可能知道一些实现使用了表示void *(根据定义,也有char *),这是一个简单的字节地址,但在C99中,它对我们程序员来说是不透明的。一个实现可以用集合{segment, offset}表示一个指针,其中offset在“实际”中可能有谁知道的对齐方式。为什么,指针甚至可以是某种形式的哈希表查找值,甚至是链表查找值。它可以编码边界信息。

在最近的C标准C1X草案中,我们看到了_Alignas关键字。这可能会有所帮助。

C99给我们的唯一保证是内存分配函数将返回一个适合赋值给指向任何对象类型的指针的指针。因为我们不能指定对象的对齐方式,所以我们不能以定义良好的、可移植的方式实现我们自己的分配函数来负责对齐。

如果这种说法是错误的,那就好了。

其他回答

不幸的是,在C99中,似乎很难保证在任何符合C99的C实现之间都是可移植的。为什么?因为指针不能保证是平面内存模型中想象的“字节地址”。uintptr_t的表示也不是那么有保证,它本身是一个可选类型。

我们可能知道一些实现使用了表示void *(根据定义,也有char *),这是一个简单的字节地址,但在C99中,它对我们程序员来说是不透明的。一个实现可以用集合{segment, offset}表示一个指针,其中offset在“实际”中可能有谁知道的对齐方式。为什么,指针甚至可以是某种形式的哈希表查找值,甚至是链表查找值。它可以编码边界信息。

在最近的C标准C1X草案中,我们看到了_Alignas关键字。这可能会有所帮助。

C99给我们的唯一保证是内存分配函数将返回一个适合赋值给指向任何对象类型的指针的指针。因为我们不能指定对象的对齐方式,所以我们不能以定义良好的、可移植的方式实现我们自己的分配函数来负责对齐。

如果这种说法是错误的,那就好了。

如果有限制,你不能浪费一个字节,那么这个解决方案是有效的: 注意:有一种情况可以无限地执行:D

   void *mem;  
   void *ptr;
try:
   mem =  malloc(1024);  
   if (mem % 16 != 0) {  
       free(mem);  
       goto try;
   }  
   ptr = mem;  
   memset_16aligned(ptr, 0, 1024);

读到这个问题时,我脑子里冒出的第一件事是定义一个对齐的结构,实例化它,然后指向它。

有没有什么根本的原因,因为没有人建议我这么做?

作为旁注,由于我使用了一个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;
}

使用memalign, Aligned-Memory-Blocks可能是解决这个问题的好方法。

我们一直在为accelerator .framework做这样的事情,这是一个高度向量化的OS X / iOS库,在那里我们必须一直注意对齐。有很多选择,其中一两个我在上面没有提到。

对于这样的小数组,最快的方法就是把它放在堆栈上。GCC / clang:

 void my_func( void )
 {
     uint8_t array[1024] __attribute__ ((aligned(16)));
     ...
 }

不需要free()。这通常是两条指令:从堆栈指针减去1024,然后用-align对堆栈指针进行AND运算。假设请求者需要堆上的数据,因为数组的生命周期超过了堆栈,或者递归在工作,或者堆栈空间非常宝贵。

在OS X / iOS上,所有调用malloc/calloc/etc。总是16字节对齐。例如,如果你需要为AVX对齐32字节,那么你可以使用posix_memalign:

void *buf = NULL;
int err = posix_memalign( &buf, 32 /*alignment*/, 1024 /*size*/);
if( err )
   RunInCirclesWaivingArmsWildly();
...
free(buf);

有些人提到c++接口的工作原理与此类似。

不要忘记页是按2的大幂进行对齐的,因此页对齐的缓冲区也是16字节对齐的。因此,mmap()和valloc()以及其他类似的接口也是选项。Mmap()的优点是,如果您愿意,可以在缓冲区中预先初始化一些非零的东西。由于它们具有页面对齐的大小,因此您将无法从中获得最小分配,并且在第一次接触它时可能会出现VM故障。

Cheesy:打开守卫malloc或类似的。像这样大小为n*16字节的缓冲区将对齐为n*16字节,因为VM用于捕获溢出,并且其边界位于页面边界。

Some Accelerate.framework functions take in a user supplied temp buffer to use as scratch space. Here we have to assume that the buffer passed to us is wildly misaligned and the user is actively trying to make our life hard out of spite. (Our test cases stick a guard page right before and after the temp buffer to underline the spite.) Here, we return the minimum size we need to guarantee a 16-byte aligned segment somewhere in it, and then manually align the buffer afterward. This size is desired_size + alignment - 1. So, In this case that is 1024 + 16 - 1 = 1039 bytes. Then align as so:

#include <stdint.h>
void My_func( uint8_t *tempBuf, ... )
{
    uint8_t *alignedBuf = (uint8_t*) 
                          (((uintptr_t) tempBuf + ((uintptr_t)alignment-1)) 
                                        & -((uintptr_t) alignment));
    ...
}

添加align -1会将指针移动到第一个对齐地址之前,然后使用-align进行and(例如0xfff…)Ff0 for alignment=16)将它带回对齐的地址。

正如其他文章所描述的,在其他没有16字节对齐保证的操作系统上,您可以调用更大的malloc,稍后将指针预留给free(),然后按照上面所述进行对齐并使用对齐的指针,这与我们的临时缓冲区的情况非常相似。

As for aligned_memset, this is rather silly. You only have to loop in up to 15 bytes to reach an aligned address, and then proceed with aligned stores after that with some possible cleanup code at the end. You can even do the cleanup bits in vector code, either as unaligned stores that overlap the aligned region (providing the length is at least the length of a vector) or using something like movmaskdqu. Someone is just being lazy. However, it is probably a reasonable interview question if the interviewer wants to know whether you are comfortable with stdint.h, bitwise operators and memory fundamentals, so the contrived example can be forgiven.