当前位置:  开发笔记 > IOS > 正文

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

如何解决《如何仅使用标准库分配对齐的内存?》经验,为你挑选了9个好方法。

我刚刚完成了一项测试,作为求职面试的一部分,一个问题让我感到难过 - 甚至使用谷歌作为参考.我想看看stackoverflow工作人员可以用它做什么:

"memset_16aligned"函数需要传递给它的16byte对齐指针,否则它将崩溃.

a)如何分配1024字节的内存,并将其与16字节边界对齐?
b)执行memset_16aligned后释放内存.

{    
   void *mem;
   void *ptr;

   // answer a) here

   memset_16aligned(ptr, 0, 1024);

   // answer b) here    
}

Jonathan Lef.. 570

原始答案

{
    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得到0x800010.您可以迭代其他偏移量并查看相同的算法是否有效.

最后一步,free()是很容易:你一定要只,返回free()的值之一malloc(),calloc()realloc()退还给你-还有什么别的是一场灾难.你正确地提供mem了保持这个价值 - 谢谢你.免费发布它.

最后,如果您了解系统malloc包的内部结构,您可能会猜测它可能会返回16字节对齐的数据(或者它可能是8字节对齐的).如果它是16字节对齐的,那么你不需要对这些值进行调整.然而,这是狡猾和不可移植 - 其他malloc包具有不同的最小对齐,因此假设有一件事情,当它做不同的事情将导致核心转储.在宽范围内,此解决方案是便携式的.

其他人提到posix_memalign()另一种获得对齐记忆的方法; 这在任何地方都不可用,但通常可以使用此作为基础来实现.注意,对齐方便是2的幂; 其他路线比较混乱.

还有一条评论 - 此代码不会检查分配是否成功.

修订

Windows程序员指出你不能对指针进行位掩码操作,事实上,GCC(经过测试的3.4.6和4.3.1)就是这样抱怨的.因此,基本代码的修改版本 - 转换为主程序,如下.我已经冒昧地增加了15而不是16,正如已经指出的那样.我正在使用,uintptr_t因为C99已经存在很长时间,可以在大多数平台上访问.如果不是PRIXPTRprintf()陈述中使用,那么#include 代替使用就足够了#include .[这段代码包括CR指出的修正案,它重申了Bill K多年前首次提出的一个观点,直到现在我都忽略了这一点.

#include 
#include 
#include 
#include 
#include 

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);  // Not a custom implementation of memset()
}

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 
#include 
#include 
#include 
#include 

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);  // Not a custom implementation of memset()
}

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功能

概要

#include 
void *aligned_alloc(size_t alignment, size_t size);

说明
aligned_alloc函数为对象指定的对象分配空间,对象的alignment大小由指定size,并且其值是不确定的.值alignment应为实现支持的有效对齐,其值size应为.的整数倍alignment.

返回
aligned_alloc函数返回空指针或指向已分配空间的指针.

而POSIX定义posix_memalign():

#include 

int posix_memalign(void **memptr, size_t alignment, size_t size);

描述

posix_memalign()函数应分配size在指定边界上对齐的字节alignment,并应返回指向已分配内存的指针memptr.值alignment应为两倍的幂sizeof(void *).

成功完成后,指向的值memptr应为.的倍数alignment.

如果请求的空间大小为0,则行为是实现定义的; 返回的值memptr应为空指针或唯一指针.

free()函数应释放先前已分配的内存posix_memalign().

返回值

成功完成后,posix_memalign()应归零; 否则,应返回错误编号以指示错误.

现在可以使用其中任何一个或两个来回答这个问题,但是当问题最初被回答时,只有POSIX函数是一个选项.

在幕后,新的对齐记忆功能完成了与问题中概述的大致相同的工作,除了它们能够更容易地强制对齐,并在内部跟踪对齐的内存的开始,以便代码不会必须专门处理 - 它只是释放由使用的分配函数返回的内存.



1> Jonathan Lef..:

原始答案

{
    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得到0x800010.您可以迭代其他偏移量并查看相同的算法是否有效.

最后一步,free()是很容易:你一定要只,返回free()的值之一malloc(),calloc()realloc()退还给你-还有什么别的是一场灾难.你正确地提供mem了保持这个价值 - 谢谢你.免费发布它.

最后,如果您了解系统malloc包的内部结构,您可能会猜测它可能会返回16字节对齐的数据(或者它可能是8字节对齐的).如果它是16字节对齐的,那么你不需要对这些值进行调整.然而,这是狡猾和不可移植 - 其他malloc包具有不同的最小对齐,因此假设有一件事情,当它做不同的事情将导致核心转储.在宽范围内,此解决方案是便携式的.

其他人提到posix_memalign()另一种获得对齐记忆的方法; 这在任何地方都不可用,但通常可以使用此作为基础来实现.注意,对齐方便是2的幂; 其他路线比较混乱.

还有一条评论 - 此代码不会检查分配是否成功.

修订

Windows程序员指出你不能对指针进行位掩码操作,事实上,GCC(经过测试的3.4.6和4.3.1)就是这样抱怨的.因此,基本代码的修改版本 - 转换为主程序,如下.我已经冒昧地增加了15而不是16,正如已经指出的那样.我正在使用,uintptr_t因为C99已经存在很长时间,可以在大多数平台上访问.如果不是PRIXPTRprintf()陈述中使用,那么#include 代替使用就足够了#include .[这段代码包括CR指出的修正案,它重申了Bill K多年前首次提出的一个观点,直到现在我都忽略了这一点.

#include 
#include 
#include 
#include 
#include 

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);  // Not a custom implementation of memset()
}

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 
#include 
#include 
#include 
#include 

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);  // Not a custom implementation of memset()
}

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功能

概要

#include 
void *aligned_alloc(size_t alignment, size_t size);

说明
aligned_alloc函数为对象指定的对象分配空间,对象的alignment大小由指定size,并且其值是不确定的.值alignment应为实现支持的有效对齐,其值size应为.的整数倍alignment.

返回
aligned_alloc函数返回空指针或指向已分配空间的指针.

而POSIX定义posix_memalign():

#include 

int posix_memalign(void **memptr, size_t alignment, size_t size);

描述

posix_memalign()函数应分配size在指定边界上对齐的字节alignment,并应返回指向已分配内存的指针memptr.值alignment应为两倍的幂sizeof(void *).

成功完成后,指向的值memptr应为.的倍数alignment.

如果请求的空间大小为0,则行为是实现定义的; 返回的值memptr应为空指针或唯一指针.

free()函数应释放先前已分配的内存posix_memalign().

返回值

成功完成后,posix_memalign()应归零; 否则,应返回错误编号以指示错误.

现在可以使用其中任何一个或两个来回答这个问题,但是当问题最初被回答时,只有POSIX函数是一个选项.

在幕后,新的对齐记忆功能完成了与问题中概述的大致相同的工作,除了它们能够更容易地强制对齐,并在内部跟踪对齐的内存的开始,以便代码不会必须专门处理 - 它只是释放由使用的分配函数返回的内存.


BTW'+ 15'和'+16'一样有效......但在这种情况下没有实际影响.
来自Menkboy和Greg的'+ 15'评论是正确的,但malloc()几乎可以肯定地将其高达16.使用+16稍微容易解释.广义的解决方案是繁琐的,但可行.
而且我对C++很生疏,但我真的不相信~0x0F会正确地扩展到指针的大小.如果没有,那么所有的地狱都会破裂,因为你也会掩盖指针的最重要部分.我可能错了.
@Aerovistae:这稍微是一个技巧问题,主要取决于你对如何创建任意数字(实际上是内存分配器返回的地址)的理解与某个要求(16的倍数)相匹配.如果你被告知要将53舍入到最接近的16的倍数,你会怎么做?地址的过程并没有太大差异; 只是你经常处理的数字更大.不要忘记,面试问题会被要求找出你的想法,而不是要知道你是否知道答案.
@akristmann:原始代码是正确的,如果你有来自C99的``(至少对于格式字符串 - 可以说,值应该通过强制转换传递:`(uintptr_t)mem,(uintptr_t)ptr `).格式字符串依赖于字符串连接,PRIXPTR宏是`uintptr_t`值的十六进制输出的正确`printf()`长度和类型说明符.另一种方法是使用`%p`但是它的输出因平台而异(有些添加一个前导的`0x`,大多数没有),并且通常用小写的十六进制数字写,我不喜欢; 我写的是跨平台的统一.
@Jonathan:size_t而不是uintptr_t应该适用于所有体系结构,尽管据我所知,不能保证sizeof(size_t)== sizeof(void*),就像uintptr_t一样.

2> Steve Jessop..:

根据您对问题的看法,三个略有不同的答案:

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)不同,只有15个字节被添加到mem,如果你的实现恰好保证了malloc的32字节对齐,这个代码实际上可以减少对齐(不太可能,但理论上C实现可能有32字节)对齐型).如果您所做的只是调用memset_16aligned,那么无关紧要,但如果您将内存用于结构,那么它可能很重要.

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

[ 补充:'标准'技巧是创建'可能是最大对齐类型'的并集来确定必要的对齐.最大对齐类型可能是(在C99中)' long long',' long double',' void *'或' void (*)(void)'; 如果你包括,你可能会使用' intmax_t'代替long long(并且,在Power 6(AIX)机器上,intmax_t会给你一个128位整数类型).可以通过将其嵌入到具有单个char后跟联合的结构中来确定该并集的对齐要求:

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)和align上面计算的值.

在(64位)Solaris 10上,看起来结果的基本对齐malloc()是32个字节的倍数.
]

在实践中,对齐的分配器通常采用参数进行对齐而不是硬连线.因此,用户将传递他们关心的结构的大小(或者大于或等于2的最小功率)并且一切都会很好.

3)使用您的平台提供的内容:posix_memalign适用_aligned_malloc于Windows上的POSIX .

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


使用二进制&与`char*`和`size_t`将导致错误.你必须使用像`uintptr_t`这样的东西.

3> florin..:

您也可以尝试posix_memalign()(当然在POSIX平台上).


和_aligned_malloc在Windows上.
几年之后,"aligned_alloc"功能现已成为C11规范的一部分:http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1516.pdf(第346页) )

4> An̲̳̳drew..:

这是'向上'部分的另一种方法.不是最精彩编码的解决方案,但它完成了工作,这种类型的语法更容易记住(加上可用于不是2的幂的对齐值).该uintptr_t塑像是必要的安抚编译器; 指针运算不是很喜欢除法或乘法.

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


通常,在你有'unsigned long long'的地方,你也有uintptr_t,它被明确定义为足以容纳数据指针(void*).但是,如果由于某种原因,你需要一个不是2的幂的对齐,你的解决方案确实有其优点.不太可能,但可能.

5> Shao..:

不幸的是,在C99中,似乎很难保证任何类型的对齐方式,这种方式可以在符合C99的任何C实现中移植.为什么?因为指针不能保证是"字节地址",人们可以想象使用平坦的内存模型.uintptr_t的表示也没有得到保证,无论如何它本身都是一个可选类型.

我们可能知道一些使用void*(和定义,也是char*)表示的实现,它是一个简单的字节地址,但是对于我们程序员来说,它对C99来说是不透明的.一个实现可能代表一个集合{ segment,offset } 的指针,其中offset实际上可以具有who-know-what alignment.为什么,指针甚至可以是某种形式的哈希表查找值,甚至是链表查找值.它可以编码边界信息.

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

C99给我们的唯一保证是内存分配函数将返回一个适合赋值给指向任何对象类型的指针的指针.由于我们无法指定对象的对齐方式,因此我们无法以明确定义的可移植方式实现自己的分配函数,并负责对齐.

这种说法是错误的.



6> Adisak..:

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

由于任何分配器的最小内存对齐是1个字节,因此15 = max(0,16-1)是保守的答案.但是,如果你知道你的内存分配器将为你提供32位int对齐的地址(这是相当常见的),你可以使用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)



7> Don Wakefiel..:

也许他们会对memalign的知识感到满意?正如Jonathan Leffler指出的那样,有两个更新的优选功能需要了解.

哎呀,弗罗林打败了我.但是,如果您阅读我链接的手册页,您很可能会理解早期海报提供的示例.



8> 小智..:

我很惊讶没有人投票赞成Shao的回答,据我所知,不可能做标准C99中的问题,因为正式将指针转换为整数类型是不明确的行为.(除了允许转换uintptr_t< - > void*的标准外,标准似乎不允许对uintptr_t值进行任何操作然后将其转换回来.)



9> Ian Ollmann..:

我们一直在为Accelerate.framework做一件事,这是一个高度向量化的OS X/iOS库,我们必须始终注意对齐.有很多选择,其中一个或两个我没有看到上面提到的.

像这样的小阵列最快的方法就是将它粘在堆栈上.GCC/clang:

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

不需要free().这通常是两条指令:从堆栈指针中减去1024,然后使用-alignment从堆栈指针中删除.据推测,请求者需要堆上的数据,因为它的生命周期超出了堆栈或递归正在工作或堆栈空间非常重要.

在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用于捕获溢出并且其边界位于页边界处.

一些Accelerate.framework函数接受用户提供的临时缓冲区作为临时空间.在这里,我们必须假设传递给我们的缓冲区严重错位,并且用户正在积极地努力使我们的生活变得困难.(我们的测试用例在临时缓冲区之前和之后粘贴一个保护页面以强调恶意.)这里,我们返回我们需要的最小大小,以保证其中某个位置的16字节对齐段,然后手动对齐缓冲区.这个大小是desired_size + alignment - 1.所以,在这种情况下,这是1024 + 16 - 1 = 1039字节.然后对齐如下:

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

添加alignment-1将使指针移过第一个对齐的地址,然后使用-alignment进行AND运算(例如0xfff ... ff0 for alignment = 16)将其返回到对齐的地址.

正如其他帖子所述,在没有16字节对齐保证的其他操作系统上,你可以调用较大的malloc,稍后将指针放在free()之后,然后如上所述对齐并使用对齐的指针,就像为我们的临时缓冲区描述.

至于aligned_memset,这是相当愚蠢的.您只需循环最多15个字节即可到达对齐的地址,然后在此之后继续使用对齐的存储,并在最后使用一些可能的清理代码.您甚至可以在向量代码中执行清理位,作为与对齐区域重叠的未对齐存储(提供长度至少是向量的长度)或使用类似movmaskdqu的内容.有人只是懒惰.然而,如果面试官想知道你是否对stdint.h,按位运算符和记忆基础知识感到满意,这可能是一个合理的面试问题,所以人为的例子可以被宽恕.

推荐阅读
保佑欣疼你的芯疼
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有