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


421

在工作面试中,我刚刚完成了一次测试,一个问题困扰着我,甚至是使用Google作为参考。我想看看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    
}

89
hmmm ...对于长期的代码生存力,“写了memset_16aligned的人开枪并修复或替换它,以使其没有特殊的边界条件,该怎么办”
Steven A. Lowe

29
当然要问一个有效的问题-“为什么要进行特殊的内存对齐”。但这可能有充分的理由-在这种情况下,memset_16aligned()可以使用128位整数,并且如果已知内存是对齐的,则这样做会更容易。等等
乔纳森·勒夫勒

5
编写memset的任何人都可以使用内部16字节对齐方式清除内部循环,并使用少量数据序言/事件日志来清除不对齐的结尾。这将比使编码人员处理额外的内存指针容易得多。
Adisak

8
为什么有人要使数据与16字节边界对齐?可能将其加载到128位SSE寄存器中。我认为(较新的)未对齐的移动(例如movupd,lddqu)的速度较慢,或者它们的目标是没有SSE2 / 3的处理器

11
对齐地址可以优化缓存的使用,并在不同级别的缓存和RAM之间(对于大多数常见工作负载)提供更高的带宽。参见此处stackoverflow.com/questions/381244/purpose-of-memory-alignment
Deepthought 2013年

Answers:


585

原始答案

{
    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()应该返回已充分为对齐的指针任何。目的然而,“任何”的意义主要是对于像基本类型- ,longdoublelong double,,long long和对象的指针和指向函数当你在做更专业的事情(例如玩图形系统)时,他们可能需要比系统其他部分更严格的对齐方式-因此是这样的问题和答案。)

下一步是将void指针转换为char指针;尽管有GCC,您也不应该对空指针进行指针算术(并且GCC有警告选项可以告诉您何时滥用它)。然后将16添加到开始指针。假设malloc()返回了一个不可能对齐的指针:0x800001。将16加到0x800011。现在我想向下舍入到16字节边界-所以我想将最后4位重置为0。0x0F将最后4位设置为1;将0x0F设置为1。因此,~0x0F除了最后四个以外,所有位都设置为1。将0x800011与Anding得出0x800010。您可以遍历其他偏移量并看到相同的算法。

最后一个步骤free()很容易:您总是并且唯一地返回free()一个值malloc()calloc()或者realloc()返回给您-其他任何事情都是灾难。您正确提供mem了该值,谢谢。免费发布它。

最后,如果您了解系统malloc软件包的内部结构,您可能会猜测它很可能返回16字节对齐的数据(或者可能是8字节对齐的)。如果它是16字节对齐的,则无需使用这些值。但是,这是狡猾且不可移植的-其他malloc软件包具有不同的最小对齐方式,因此,假设一件事情做一些不同的事情会导致核心转储。在广泛的范围内,该解决方案是便携式的。

提到posix_memalign()其他人是获取对齐内存的另一种方法。并非到处都有,但通常可以以此为基础来实现。请注意,对齐方式是2的幂是很方便的。其他路线则比较混乱。

还有一个注释-此代码不检查分配是否成功。

修正案

Windows Programmer指出您不能对指针执行位掩码操作,并且确实,GCC(已测试3.4.6和4.3.1)确实有这样的抱怨。因此,随后是基本代码的修改版本-转换为主程序。正如我已经指出的,我也自由选择只添加15而不是16。我使用uintptr_tC99的时间已经足够长,可以在大多数平台上访问了。如果不是PRIXPTRprintf()语句中使用,则只需#include <stdint.h>使用即可#include <inttypes.h>[此代码包括CR指出的修复程序,该声明重申了Bill K数年前首次提出的观点,直到现在我一直忽略了这一点。]

#include <assert.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

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 <assert.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

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 <stdlib.h>
void *aligned_alloc(size_t alignment, size_t size);

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

返回
aligned_alloc函数返回一个空指针或指向分配的空间。

POSIX定义posix_memalign()

#include <stdlib.h>

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

描述

posix_memalign()函数应分配size由指定的边界上对齐的字节alignment,并应返回指向中分配的内存的指针memptr。的值alignment应为的2的幂sizeof(void *)

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

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

free()功能应取消分配先前由分配的内存posix_memalign()

返回值

成功完成后,posix_memalign()应返回零;否则,返回零。否则,将返回一个错误编号以指示错误。

现在,可以使用这两种方法中的一种或两种方法来回答问题,但是当最初回答问题时,仅POSIX功能是一个选项。

在幕后,新的对齐内存功能与问题中概述的功能大致相同,不同之处在于它们能够更轻松地强制对齐,并在内部跟踪对齐内存的开始,从而使代码不会必须专门处理-它只是释放使用的分配函数返回的内存。


13
而且我对C ++感到生疏,但我真的不相信〜0x0F会正确扩展到指针的大小。否则,所有地狱都会崩溃,因为您还将掩盖指针的最高有效位。我对此可能是错的。
比尔K

66
顺便说一句,“ + 15”和“ +16”一样工作……虽然在这种情况下没有实际影响。
Menkboy

15
Menkboy和Greg的“ + 15”注释是正确的,但是无论如何malloc()几乎肯定会将其舍入到16。使用+16稍微容易解释。广义的解决方案有些古怪,但可行。
乔纳森·莱夫勒

6
@Aerovistae:这是一个小技巧,主要取决于您对如何使任意数字(实际上是内存分配器返回的地址)与特定要求(16的倍数)相匹配的理解。如果要求您将53舍入为16的最接近倍数,您将如何处理?地址的处理没有太大不同;只是您通常要处理的数字更大。别忘了,面试问题被询问来找出您的想法,而不是找出您是否知道答案。
乔纳森·勒夫勒

3
@akristmann:如果<inttypes.h>C99可用,则原始代码是正确的(至少对于格式字符串而言-可以说,值应使用强制转换:)传递(uintptr_t)mem, (uintptr_t)ptr。格式字符串依赖于字符串连接,PRIXPTR宏是printf()十六进制输出uintptr_t值的正确长度和类型说明符。替代方法是使用,%p但是输出结果因平台而异(有些会加上前导0x,大多数则不会),并且通常使用小写的十六进制数字写,我不喜欢;我写的是跨平台统一的。
Jonathan Leffler

58

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

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字节缓冲区,并假设看到的最差对齐方式是保证的对齐方式。如果输入错误,则会浪费内存。有更好的主意的人,请这么说...

[ 添加:“标准”技巧是创建“可能是最大对齐类型”的并集,以确定必要的对齐方式。最大对齐类型可能是(在C99中)“ long long”,“ long double”,“ void *”或“ void (*)(void)”;如果包含<stdint.h>,则可以使用' 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)使用平台提供的:Windows上的posix_memalignPOSIX _aligned_malloc

4)如果使用C11,则最简洁的方法-便携式且简洁-将使用aligned_alloc此版本的语言规范中引入的标准库函数。


1
我同意-我认为问题的意图是释放内存块的代码只能访问“煮熟的” 16字节对齐指针。
Michael Burr

1
对于一般的解决方案-您是对的。但是,问题中的代码模板清楚地显示了两者。
乔纳森·莱夫勒

1
当然,在一次很好的面试中,发生的事情是您给出答案,然后如果面试官确实希望看到我的答案,他们就会改变问题。
史蒂夫·杰索普

1
我反对使用ASSERT(mem);来检查分配结果;assert用于捕获编程错误,而不是缺少运行时资源。
hlovdal

4
将二进制&与a char *和a一起使用size_t将导致错误。您必须使用uintptr_t
Marko 2014年


20

这是“汇总”部分的另一种方法。这不是最出色的编码解决方案,但是它可以完成工作,并且这种语法类型更容易记住(加号适用于不是2的幂的对齐值)。该uintptr_t塑像是必要的安抚编译器; 指针算术不是很喜欢除法或乘法。

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

2
通常,在“ unsigned long long”的地方,还具有uintptr_t,它明确定义为足以容纳数据指针(void *)。但是,如果由于某种原因需要的对齐方式不是2的幂,则您的解决方案确实具有优点。
乔纳森·勒夫勒

@Andrew:赞成使用这种语法有点容易记住(加上将适用于不是2的幂的对齐值)
legends2k

19

不幸的是,在C99中,很难保证以任何方式对齐都可以在符合C99的任何C实现中移植。为什么?因为不能保证指针是“字节地址”,所以使用平面内存模型可能会想到。也不能保证uintptr_t的表示,它本身也是可选类型。

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

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

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

错误地认为这一主张将是很好的。


C11有aligned_alloc()。(C ++ 11/14 / 1z仍然没有)。 _Alignas()和C ++ alignas()对动态分配不做任何事情,仅对自动和静态存储(或结构布局)做任何事情。
彼得·科德斯

15

在16字节与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)

8

也许他们会对memalign的知识感到满意?正如乔纳森·莱夫勒(Jonathan Leffler)指出的那样,有两个更新的更好的功能需要了解。

糟糕,弗洛林击败了我。但是,如果您阅读了我链接到的手册页,则很可能会理解早期海报提供的示例。


1
请注意,引用页面的当前版本(2016年2月)显示 “该memalign功能已过时,应改为使用aligned_allocposix_memalign”。我不知道它在2008年10月说了什么,但可能没有提到,aligned_alloc()因为它已添加到C11中。
乔纳森·勒夫勒

5

我们一直在为Accelerate.framework做这种事情,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故障。

俗气:打开防护malloc或类似功能。大小为n * 16字节的缓冲区(例如此缓冲区)将对齐n * 16字节,因为VM用于捕获溢出并且其边界位于页面边界。

一些Accelerate.framework函数采用用户提供的临时缓冲区作为暂存空间。在这里,我们必须假定传递给我们的缓冲区完全没有对齐,并且用户正在积极尝试使我们的生活变得艰难。(我们的测试用例在临时缓冲区的前后都贴了一个保护页,以突出显示恶意。)在这里,我们返回需要保证在其中某处有16字节对齐段的最小大小,然后再手动对齐缓冲区。此大小为所需的大小+对齐方式-1。因此,在这种情况下为1024 + 16-1 = 1039字节。然后对齐:

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

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

如其他文章所述,在没有16字节对齐保证的其他操作系统上,您可以调用更大的malloc,稍后为free()留出指针,然后按照上述方法进行对齐,并使用对齐的指针,描述了我们的临时缓冲区情况。

至于aligned_memset,这很愚蠢。您只需循环最多15个字节即可到达对齐的地址,然后在此之后进行对齐的存储,并在末尾添加一些可能的清除代码。您甚至可以在矢量代码中执行清除位,或者作为与对齐区域重叠的未对齐存储(假设长度至少是矢量的长度),或者使用诸如movmaskdqu之类的方法。有人只是在偷懒。但是,如果面试者想知道您是否对stdint.h,按位运算符和内存基础知识感到满意,那么这可能是一个合理的面试问题,因此可以原谅人为的示例。


5

令我惊讶的是,没有人投票赞成Shao答案,据我所知,这是不可能完成标准C99中要求的操作的,因为将指针正式转换为整数类型是未定义的行为。(除了允许uintptr_t<-> 转换的标准外void*,但该标准似乎不允许对uintptr_t值进行任何操作,然后再将其转换回去。)


不需要uintptr_t类型存在,也不要求其位与基础指针中的位有任何关系。如果要过度分配存储,请将指针存储为unsigned char* myptr;然后计算`mptr + =(16-(uintptr_t)my_ptr)&0x0F,将在定义my_ptr的所有实现上定义行为,但是结果指针是否对齐将取决于uintptr_t位和地址之间的映射。
超级猫


3

阅读此问题时突然想到的第一件事是定义一个对齐的结构,实例化它,然后指向它。

因为没有其他人建议我失踪,这是根本原因吗?

附带说明一下,由于我使用了一个char数组(假设系统的char是8位(即1个字节)),因此我看不到有__attribute__((packed))必要(如果我错了,请纠正我),但是我把它以任何方式。

这可以在我尝试过的两个系统上使用,但是有可能是我没有意识到编译器优化,因为我对代码的有效性不满意。我gcc 4.9.2在OSX和gcc 5.2.1Ubuntu上使用过。

#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,大小)即可。

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

如果您想要100%可移植的解决方案,那么在C11之前没有。因为没有可移植的方法来测试指针的对齐方式。如果不必100%可移植,则可以使用

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

假定将指针转换为unsigned int时,指针的对齐方式存储在最低位。转换为unsigned int会丢失信息并由实现定义,但这并不重要,因为我们不会将结果转换回指针。

当然,最糟糕的部分是原始指针必须保存在某个地方才能用它调用free()。所以总的来说,我真的会怀疑这种设计的智慧。


1
aligned_malloc在OS X的哪里找到?我使用的是Xcode 6.1,它没有在iOS SDK中的任何位置定义,也没有在中的任何位置声明/usr/include/*
托德·雷曼

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改变的错误消息。
乔纳森·勒夫勒

0

您还可以添加一些16个字节,然后通过在指针下面添加(16-mod)将原始ptr推向16bit对齐:

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);
}

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);

如果您先分配一个N字节的块然后释放,然后再请求另一个N字节的块,则很有可能再次返回原始块。因此,如果第一个分配不满足对齐要求,则很有可能发生无限循环。当然,这避免了浪费单个字节,而浪费了很多CPU周期。
乔纳森·勒夫勒

您确定以有意义的方式%定义了运算符void*吗?
Ajay Brahmakshatriya

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字节边界就足够了。
乔纳森·莱夫勒

0
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 2013年

@Sam应该是add += 16 - (add % 16)(2 - (2 % 16)) == 0
SS安妮
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.