C中的内存对齐:返回地址中的偏移量是如何考虑的?

Posted

技术标签:

【中文标题】C中的内存对齐:返回地址中的偏移量是如何考虑的?【英文标题】:Memory alignment in C: How is offset factoring in the return address? 【发布时间】:2021-03-10 18:05:38 【问题描述】:

我遇到了以下代码here。

/* Allocate aligned memory in a portable way.
 *
 * Memory allocated with aligned alloc *MUST* be freed using aligned_free.
 *
 * @param alignment The number of bytes to which memory must be aligned. This
 *  value *must* be <= 255.
 * @param bytes The number of bytes to allocate.
 * @param zero If true, the returned memory will be zeroed. If false, the
 *  contents of the returned memory are undefined.
 * @returns A pointer to `size` bytes of memory, aligned to an `alignment`-byte
 *  boundary.
 */
void *aligned_alloc(size_t alignment, size_t size, bool zero) 
    size_t request_size = size + alignment;
    char* buf = (char*)(zero ? calloc(1, request_size) : malloc(request_size));

    size_t remainder = ((size_t)buf) % alignment;
    size_t offset = alignment - remainder;
    char* ret = buf + (unsigned char)offset;

    // store how many extra bytes we allocated in the byte just before the
    // pointer we return
    *(unsigned char*)(ret - 1) = offset;

    return (void*)ret;


/* Free memory allocated with aligned_alloc */
void aligned_free(void* aligned_ptr) 
    int offset = *(((char*)aligned_ptr) - 1);
    free(((char*)aligned_ptr) - offset);

解释:

char *ret = buf + (unsigned char)offset; 在这里,我们设置了一个新指针,它比 buf 的基地址提前了偏移字节。

例如我们想在一个 16 位对齐的内存中分配 68 个字节,它看起来像这样:

    requested_size = 68+16 = 84 假设 buf 的基地址是 buf = 0x112223341 然后 remainder = sizeof(buf)%16 = (84%16) = 4 offset = 16 - 4 = 12 (i.e. 0x0C) ret = &amp;buf + offset = 0x11223341+0x0C = 0x1122334D

问题

以下行是做什么的? 我在理解这种语法和实现结果时遇到了一些麻烦。

*(unsigned char*)(ret - 1) = offset

当我们返回 ret 时,那些被分配但绝不是 ret 基地址的一部分的额外字节会发生什么?也就是说,如果我们分配了 16 个额外的字节,但只需要 12 个字节进行对齐,那么剩下的字节会发生什么?

=======更新问题中的代码=======

感谢@ThomasMailund 和他的见解,我认为我可以安全地修改上述代码以简化一些类型转换,如下所示:

/* Allocate aligned memory in a portable way.
 *
 * Memory allocated with aligned alloc *MUST* be freed using aligned_free.
 *
 * @param alignment The number of bytes to which memory must be aligned. This
 *  value *must* be <= 255.
 * @param bytes The number of bytes to allocate.
 * @param zero If true, the returned memory will be zeroed. If false, the
 *  contents of the returned memory are undefined.
 * @returns A pointer to `size` bytes of memory, aligned to an `alignment`-byte
 *  boundary.
 */
void *aligned_alloc(size_t alignment, size_t size, bool zero) 
    size_t request_size = size + alignment;
    unsigned char *buf = zero ? calloc(1, request_size) : malloc(request_size);

    size_t remainder = ((size_t)buf) % alignment;
    size_t offset = alignment - remainder;
    unsigned char *ret = buf + (unsigned char)offset;

    // store how many extra bytes we allocated in the byte just before the
    // pointer we return
    *(ret - 1) = offset;

    return ret;


/* Free memory allocated with aligned_alloc */
void aligned_free(void* aligned_ptr) 
    int offset = *(((char*)aligned_ptr) - 1);
    free(((char*)aligned_ptr) - offset);

【问题讨论】:

【参考方案1】:

对于将来正在查看此内容的任何人:

当我查看void aligned_free 函数时,我想我现在明白了。 我们只是保存在 (ret-1) 的地址处计算的偏移字节数,然后在准备释放内存时将其读回,以释放这些额外的字节。 即从(ret-1)读取偏移量,从基地址中减去那些我们要求释放的许多#字节以获得原始buf基地址,然后释放buf,这样我们就不会遇到内存泄漏!

注意:那些额外的偏移字节,它们仍然分配在内存中,只是没有使用!

【讨论】:

是的。您需要免费提供从 malloc 获得的相同地址,因此您需要返回那里。将输入指针转换为 char * 意味着 -1 向后移动一个字节,您可以在其中获取存储在那里的偏移量。有了偏移量就可以得到原地址。 char 是最小的可寻址单元。通常是一个字节,是的。您可以将 char * 指向任何地方。与其说它指向多少内存,不如说是指针算法在做什么。添加或减去指针,然后将地址 sizeof() 移动到基础类型(有一些限制)。对于 char * sizeof() 是 1。所以如果一个 char 是一个字节,而 p 是 char *,那么 p - 1 是 p 之前的一个字节。如果 ip 是 int * 并且 sizeof(int) 是 4,那么 ip - 1 将是 ip 之前的四个字节。在 void * 上进行指针运算不符合标准,因此必须进行强制转换。 如果您的编译器与 C11 兼容,则不应使用此代码。现在标准中有一个aligned_alloc。 (ret - 1) 是一个字符指针,但它不是您可以分配给它的东西(所谓的左值)。您可以分配给变量,也可以写入地址,但如果您尝试 (res - 1) = offset 您将尝试将地址 (res-1) 更改为其他内容。那是没有意义的。代码的实际作用是 *(res-1) = offset。在这里, *(res-1) 是地址指向的内容,因此您将一个值写入该地址。 Casting真的跟它没什么关系,只不过offset的类型必须是我们可以放入左边的类型中的东西。 如果 res 以 unsigned char * 开头,而不是 char *,则可以避免强制转换。为什么它不是我不了解代码的原因。您可以避免在整个过程中使用无符号字符进行大量转换。

以上是关于C中的内存对齐:返回地址中的偏移量是如何考虑的?的主要内容,如果未能解决你的问题,请参考以下文章

关于堆内存中的块对齐

什么是偏移量 怎么计算

C语言内存对齐详解

stm32中的WWDG->CFR&=0XFF80是啥意思?

结构体在内存中的存储方式

内存字节对齐