如何只使用标准库分配对齐内存?

465

我刚完成了一次工作面试的测试,其中有一个问题让我束手无策,即使使用谷歌也没能找到答案。现在请看看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    
}

94
为了确保代码的长期可行性,考虑解雇写memset_16aligned的人并修复或替换它,以使其不再具有奇特的边界条件。 - Steven A. Lowe
33
问“为什么要进行特殊的内存对齐”是一个合理的问题。但是这样做可能有好的原因——在这种情况下,memset_16aligned()可以使用128位整数,如果已知内存对齐,那么这样做更容易。等等。 - Jonathan Leffler
5
谁写的memset可以使用内部16字节对齐来清除内部循环,并使用一个小的数据前/后缀来清理非对齐的结尾。这比让编码处理额外的内存指针要容易得多。 - Adisak
9
为什么有些人希望数据按16字节边界对齐?可能是为了将其加载到128位SSE寄存器中。我认为(更新的)不对齐mov指令(例如movupd,lddqu)速度较慢,或者它们是针对没有SSE2/3处理器的情况。 - user21037
17
地址对齐可优化缓存的使用,并提高不同级别缓存和 RAM 之间的带宽利用率(对于大多数常见工作负载而言)。请参阅此处:https://dev59.com/G3RC5IYBdhLWcg3wMeBS - Deepthought
显示剩余3条评论
17个回答

3
阅读这个问题时,我想到的第一件事是定义一个对齐的结构体,实例化它,然后指向它。其他人为什么没有建议这么做呢?是否有我所不知道的基本原因?
顺便说一句,由于我使用了一个char数组(假设系统的char是8位(即1字节)),我认为不一定需要__attribute__((packed)) (如果我错了,请纠正我),但我还是加上了。
我在两个系统上尝试了这个方法,它们都可以工作。但是可能存在一些编译器优化,我不知道这些优化会不会导致代码效果不佳。我在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;
}

1

针对MacOS X:

  1. 使用malloc分配的所有指针都是16字节对齐的。
  2. 支持C11,因此可以直接调用aligned_malloc(16, size)。

  3. 在启动时,MacOS X会为memset、memcpy和memmove挑选针对个别处理器进行优化的代码,并且该代码使用了您从未听说过的技巧,使其速度更快。99%的机会是memset运行得比任何手写的memset16都要快,这使得整个问题毫无意义。

如果您想要一个100%可移植的解决方案,在C11之前是没有的,因为没有一种可移植的方法来测试指针的对齐方式。如果不需要100%可移植性,可以使用

char* p = malloc (size + 15);
p += (- (unsigned int) p) % 16;

这假设将指针转换为无符号整数时,指针的对齐方式存储在最低位中。将其转换为无符号整数会丢失信息,并且是实现定义的,但这并不重要,因为我们不会将结果转换回指针。
可怕的部分当然是原始指针必须保存在某个地方以调用 free()。总的来说,我真的怀疑这种设计的智慧。

1
你在 OS X 中哪里找到了 aligned_malloc?我正在使用 Xcode 6.1,它在 iOS SDK 中没有定义,也没有在 /usr/include/* 中声明。 - Todd Lehman
同样适用于El Capitan(Mac OS X 10.11.3)上的XCode 7.2。无论如何,C11函数是aligned_alloc(),但也没有声明。从GCC 5.3.0开始,我得到了有趣的消息alig.c:7:15: error: incompatible implicit declaration of built-in function ‘aligned_alloc’ [-Werror]alig.c:7:15: note: include ‘<stdlib.h>’ or provide a declaration of ‘aligned_alloc’。代码确实包括<stdlib.h>,但是-std=c11-std=gnu11都没有改变错误消息。 - Jonathan Leffler

0

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

如果有不能浪费单个字节的限制,那么使用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);

1
在许多64位系统上,malloc()返回的指针确实对齐在16字节边界上,但是没有任何标准保证这一点——它只会被充分地对齐以供任何使用,在许多32位系统上,对齐到8字节边界就足够了,而对于某些系统,4字节边界就足够了。 - Jonathan Leffler

0
如果有限制,不能浪费一字节,那么这个解决方案就可以使用: 注意:有一种情况可能会无限执行 :D
   void *mem;  
   void *ptr;
try:
   mem =  malloc(1024);  
   if (mem % 16 != 0) {  
       free(mem);  
       goto try;
   }  
   ptr = mem;  
   memset_16aligned(ptr, 0, 1024);

1
如果您分配并释放了 N 字节大小的一个内存块,然后请求另一个 N 字节大小的内存块,那么有很大的可能性会再次返回原始块。因此,如果第一个分配不符合对齐要求,则非常可能陷入无限循环。当然,这避免了浪费单个字节的情况,但代价是浪费了大量的 CPU 周期。 - Jonathan Leffler
1
你确定%运算符在void*类型中有意义的定义吗? - Ajay Brahmakshatriya

0

您还可以添加16个字节,然后通过添加(16-mod)将原始指针推送到16位对齐的位置,如下所示:

main(){
void *mem1 = malloc(1024+16);
void *mem = ((char*)mem1)+1; // force misalign ( my computer always aligns)
printf ( " ptr = %p \n ", mem );
void *ptr = ((long)mem+16) & ~ 0x0F;
printf ( " aligned ptr = %p \n ", ptr );

printf (" ptr after adding diff mod %p (same as above ) ", (long)mem1 + (16 -((long)mem1%16)) );


free(mem1);
}

-1
size =1024;
alignment = 16;
aligned_size = size +(alignment -(size %  alignment));
mem = malloc(aligned_size);
memset_16aligned(mem, 0, 1024);
free(mem);

希望这个是最简单的实现,期待您的评论。

-3
long add;   
mem = (void*)malloc(1024 +15);
add = (long)mem;
add = add - (add % 16);//align to 16 byte boundary
ptr = (whatever*)(add);

我认为这个有问题,因为你的添加将指向一个未分配的位置 - 不确定这在你的情况下是如何工作的。 - resultsway
@Sam 应该是 add += 16 - (add % 16)(2 - (2 % 16)) == 0 - S.S. Anne

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接