C语言程序开发中,如何对一段内存进行 16 字节对齐操作?

上一节我们讨论了C语言程序中“内存对齐”的概念以及原因,其实归根结底都是为了效率最大化,毕竟高效率是C语言程序的一个重要特性。

来看看这个面试题

浏览外文网站时,我发现了一个关于“内存对齐”的面试题目,原题主对这一个面试题完全没有概念,即使谷歌之也没有办法。题目是这样的:

memset_16aligned() 函数需要一个 16 字节对齐的指针作为参数,否则就会崩溃。
a) 你能分配 1024 字节内存,并且将其 16 字节对齐吗?
b) 分配后,将其传递给 memset_16aligned() 函数执行后,释放这段内存。
{    
   void *mem;
   void *ptr;

   // answer a) here

   memset_16aligned(ptr, 0, 1024);

   // answer b) here    
}

解析

题目要求传递给 memset_16aligned() 函数的 ptr 参数是 16 字节对齐的,这很好实现,只要保证 ptr 的地址值是 16 的整数倍就可以了。进一步分析问题,还能够发现,ptr 应该指向一段 1024 字节的内存,因此相关的C语言代码可以这样写:

{
    void *mem = malloc(1024+16);
    void *ptr = ((char *)mem+16) & ~(char *)0x0F;
    memset_16aligned(ptr, 0, 1024);
    free(mem);
}

第一步就是分配一块足够大的内存,由于内存必须是 16 字节对齐的,以防万一,我们多分配了 16 字节,便于调整 ptr 指针的值。16 个连续数字里,必定至少有一个数能够被 16 整除,因此在前 16 个字节的某处,必定有一个 16 字节对齐的地址。

下一步是将 void 指针转换为 char 指针,这是为了尽量避免对 void 指针指向指针算术。然后对转换后的指针加上 16。

假设 malloc() 函数分配的内存起始地址为 0x800001,显然未 16 字节对齐。现在对起始地址加 16,得到 0x800011,现在我想四舍五入到 16 字节边界,所以可以将最后的 4 位置 0,也即 & ~0x0F。此时我们得到了 0x800010,这个地址显然满足 16 字节对齐。

读者可以自己尝试对其他地址执行上述操作,观察是否能够得到 16 字节对齐的地址。

最后一步,释放分配的内存很简单,只需调用 free() 函数就可以了。但是应该注意,传递给 free() 函数的必须是 malloc() 函数返回的地址,也即 mem,而不能是 ptr,否则C语言程序就会崩溃。

题外话

可能有读者知道自己使用的系统中 malloc() 函数的内部实现,可能它返回的地址必定是 16 字节对齐的(或者 8 字节对齐,4 字节对齐等等),那么似乎就不再需要 ptr,直接使用 mem 就可以了。

然而,这是不可靠的也是不可移植的实现,因为其他平台中的 malloc() 实现可能具有不同的最小对齐。作为C语言程序员,除非有某些限制,否则总是应该尝试写出可移植的C语言代码。

其他问题

这个面试题目也引发了一些争论,我觉得比较有意思的是这样的一个观点:“题目要求分配的是 [1024] 个字节!”也即这个观点强调 1024 字节,而我们上面的C语言代码分配的不止 1024 个字节。

其实,上面的C语言代码是将题目理解成“分配一款足以容纳 1024 字节数据的内存”了,如果面试官真的强调了 1024 字节,那么问题就更加有趣了,我揣测此时面试官应该有两种意思。

一是要求我们分配 1024 字节的内存,并对这段内存做 16 字节对齐处理。可是这样我们最终得到的可用内存实际上是 1008 到 1024 之间的大小。

再就是要求我们自定义一个内存分配器,该内存分配器返回的内存起始地址必定是 16 字节对齐的。这样一来,我们在内存分配器内部可能的实现就是基于上面的C语言代码示例了,只不过,我们会把多出的字节隐藏在模块内部了。

阅读更多:   C语言
添加新评论

icon_redface.gificon_idea.gificon_cool.gif2016kuk.gificon_mrgreen.gif2016shuai.gif2016tp.gif2016db.gif2016ch.gificon_razz.gif2016zj.gificon_sad.gificon_cry.gif2016zhh.gificon_question.gif2016jk.gif2016bs.gificon_lol.gif2016qiao.gificon_surprised.gif2016fendou.gif2016ll.gif