动态内存分配
Dynamic Memory Allocation
malloc() 如何存储元数据?
void* p;
void* q;
p = malloc(sizeof(char));
q = malloc(sizeof(int));
我知道 return 值 p[0] 指向已分配内存块的开始,
比起我迭代和打印
p[-1], p[-2]..... q[-1], q[-2].....
或p[1]、p[2]、p[3]、p[4]....
或 q[1]、q[2]、q[3]、q[4]...
我找到了一些帮助 malloc() 存储数据的值,但是我无法理解
元数据的确切含义..我只知道其中一些用于块大小,用于下一个空闲块的地址但我在网上找不到更多
拜托,你能给我一些关于这些值的详细解释吗?
在您的示例中访问 p[-1]
等是非法的。结果将是不确定的,可能会导致内存损坏或分段错误。通常,您根本无法控制 malloc()
或有关它在做什么的信息。
就是说,一些 malloc() "replacement" 库会为您提供更细粒度的控制或信息 - 您 link 将它们放入系统 malloc() 的二进制文件 "on top" 中。
C 标准未指定 malloc
如何存储其元数据(或者即使它具有可访问的元数据);因此,元数据格式和位置 依赖于实现 。因此,可移植代码绝不能尝试访问或解析元数据。
一些 malloc
实现作为扩展提供了像 malloc_usable_size
or malloc_size
这样的函数,它可以告诉您已分配块的大小。虽然这些功能的存在也依赖于实现,但如果它们存在,它们至少是获取所需信息的可靠和正确的方法。
此元数据的工作方式和使用方式完全取决于您的 libc 中的内存管理。这里有一些有用的文章可以帮助您入门:
- Malloc - 通常是经典 malloc。
- DLMalloc - Doug Lee 的 Malloc。
- GC Malloc - 带垃圾回收的 Malloc。
- TC Malloc - 线程缓存 malloc。
每一个都有不同的目标、好处和可能的不足。例如,您可能担心可能出现的堆溢出问题和保护措施。这可能导致一个选择。也许您正在寻找更好的片段管理。这可能会导致选择 Doug Lee 的 malloc。您确实需要指定您正在使用哪个库或研究它们以了解如何使用元数据来维护 bin、合并调整自由区域等。
David Hoelzer 有一个很好的答案,但我想进一步跟进:
"Meta Data" for malloc 是 100% 实现驱动的。以下是我编写或使用的一些实现的快速概述:
- 元数据存储 "next" 块指针。
- 它存储金丝雀值以确保您没有写过一个块的末尾
- 根本没有元数据,因为所有块的大小都相同,并且它们被放置在堆栈中以允许 O(1) 访问 - 一个单元分配器。在这种情况下,您会从相邻的块中获取内存。
- 数据存储下一个块指针、前一个块指针、对齐 ID、块大小和一个标识符,该标识符告诉内存调试器分配发生的确切位置。对泄漏跟踪非常有用。
- 它命中了前一个区块的信息,因为数据存储在区块的尾部而不是头部...
或混合搭配以上所有内容。在任何情况下,读取 mem[-1]
都是一个坏主意,并且在某些情况下(考虑嵌入式系统),如果读取恰好超出当前内存页面并进入禁止访问,确实可能仅在读取时导致段错误内存区域。
根据 OP 更新
我描述的第 4 个方案是每个块有相当多信息的方案 - 16 字节的信息并不少见,因为该大小不会影响常见的对齐方案。它将包含一个指向下一个分配块的 4 字节指针,一个指向前一个分配块的 4 字节指针,一个对齐标识符 - 对于 0-256 字节对齐的常见大小,单个字节可以直接处理这个,但那个字节也可以代表 256 个可能的对齐枚举值,3 个字节的 pad 或 canary 值,以及一个 4 字节的唯一标识符,用于标识在代码中进行调用的位置,尽管它可以变得更小。这可以是调试 table 的查找值,其中包含 __file__
、__line__
以及您希望与分配一起保存的任何其他信息。
这将是较重的变体之一,因为它有很多信息需要在分配和释放值时更新。
malloc() 如何存储元数据?
void* p;
void* q;
p = malloc(sizeof(char));
q = malloc(sizeof(int));
我知道 return 值 p[0] 指向已分配内存块的开始,
比起我迭代和打印
p[-1], p[-2]..... q[-1], q[-2].....
或p[1]、p[2]、p[3]、p[4].... 或 q[1]、q[2]、q[3]、q[4]...
我找到了一些帮助 malloc() 存储数据的值,但是我无法理解 元数据的确切含义..我只知道其中一些用于块大小,用于下一个空闲块的地址但我在网上找不到更多
拜托,你能给我一些关于这些值的详细解释吗?
在您的示例中访问 p[-1]
等是非法的。结果将是不确定的,可能会导致内存损坏或分段错误。通常,您根本无法控制 malloc()
或有关它在做什么的信息。
就是说,一些 malloc() "replacement" 库会为您提供更细粒度的控制或信息 - 您 link 将它们放入系统 malloc() 的二进制文件 "on top" 中。
C 标准未指定 malloc
如何存储其元数据(或者即使它具有可访问的元数据);因此,元数据格式和位置 依赖于实现 。因此,可移植代码绝不能尝试访问或解析元数据。
一些 malloc
实现作为扩展提供了像 malloc_usable_size
or malloc_size
这样的函数,它可以告诉您已分配块的大小。虽然这些功能的存在也依赖于实现,但如果它们存在,它们至少是获取所需信息的可靠和正确的方法。
此元数据的工作方式和使用方式完全取决于您的 libc 中的内存管理。这里有一些有用的文章可以帮助您入门:
- Malloc - 通常是经典 malloc。
- DLMalloc - Doug Lee 的 Malloc。
- GC Malloc - 带垃圾回收的 Malloc。
- TC Malloc - 线程缓存 malloc。
每一个都有不同的目标、好处和可能的不足。例如,您可能担心可能出现的堆溢出问题和保护措施。这可能导致一个选择。也许您正在寻找更好的片段管理。这可能会导致选择 Doug Lee 的 malloc。您确实需要指定您正在使用哪个库或研究它们以了解如何使用元数据来维护 bin、合并调整自由区域等。
David Hoelzer 有一个很好的答案,但我想进一步跟进:
"Meta Data" for malloc 是 100% 实现驱动的。以下是我编写或使用的一些实现的快速概述:
- 元数据存储 "next" 块指针。
- 它存储金丝雀值以确保您没有写过一个块的末尾
- 根本没有元数据,因为所有块的大小都相同,并且它们被放置在堆栈中以允许 O(1) 访问 - 一个单元分配器。在这种情况下,您会从相邻的块中获取内存。
- 数据存储下一个块指针、前一个块指针、对齐 ID、块大小和一个标识符,该标识符告诉内存调试器分配发生的确切位置。对泄漏跟踪非常有用。
- 它命中了前一个区块的信息,因为数据存储在区块的尾部而不是头部...
或混合搭配以上所有内容。在任何情况下,读取 mem[-1]
都是一个坏主意,并且在某些情况下(考虑嵌入式系统),如果读取恰好超出当前内存页面并进入禁止访问,确实可能仅在读取时导致段错误内存区域。
根据 OP 更新
我描述的第 4 个方案是每个块有相当多信息的方案 - 16 字节的信息并不少见,因为该大小不会影响常见的对齐方案。它将包含一个指向下一个分配块的 4 字节指针,一个指向前一个分配块的 4 字节指针,一个对齐标识符 - 对于 0-256 字节对齐的常见大小,单个字节可以直接处理这个,但那个字节也可以代表 256 个可能的对齐枚举值,3 个字节的 pad 或 canary 值,以及一个 4 字节的唯一标识符,用于标识在代码中进行调用的位置,尽管它可以变得更小。这可以是调试 table 的查找值,其中包含 __file__
、__line__
以及您希望与分配一起保存的任何其他信息。
这将是较重的变体之一,因为它有很多信息需要在分配和释放值时更新。