c - glibc的free()的内部工作原理

标签 c heap free glibc

对于glibc 2.15,我正在查看malloc.c,特别是free()函数,并对unlink()宏感到困惑。根据消息来源,正在使用的块看起来像这样:

   chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
                  Size of previous chunk, if allocated            
           +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
                  Size of chunk, in bytes                       
     mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
                  User data starts here...                          .
    .                                                               .
    .             (malloc_usable_size() bytes)                      .
    .                                                               
nextchunk->+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

一个free()的块看起来像这样:
    chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
                         Size of previous chunk                    
            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 `head:'           Size of chunk, in bytes                          
  mem->     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
                  Forward pointer to next chunk in list             
            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
                  Back pointer to previous chunk in list            
            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
                  Unused space (may be 0 bytes long)                .
    .                                                               .
    .                                                               
nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

当使用的块为free()时,它将接收到的内存指针作为参数,并从中减去偏移量以获得块指针。两者之间有很多检查,但如果未映射该块,则通常将其与另一个空闲块进行前向或后向合并。由于free()'d的块已经在bin中,因此它仅在特定的bin中搜索块以将其合并,对吗?在前向合并的情况下,将调用unlink()宏并将其应用于free()之后的块。我不明白这一点,因为当下一个块(称为“nextchunk”)取消链接时,将发生以下代码:
    #define unlink(P, BK, FD) {                                            
    FD = P->fd;                                                          
    BK = P->bk;
    .
    .
    .
    FD->bk = BK;                                                       
    BK->fd = FD;
    .
    .
    .
                             }

考虑到BK指向被free()添加的块,并查看其结构,它没有正向或反向指针,因此如何引用BK->fd。我一定错过了代码中将fd和bk字段添加到free()将被添加到块中的部分,但我不知道在哪里。有人可以帮忙吗?谢谢。

最佳答案

该行在要释放的块中创建一个前向指针:

BK->fd = FD;

BK曾经是一块用户数据,但是现在它是一块空闲数据,因此malloc被允许在其认为合适的地方对内存进行涂鸦。

如果有帮助,您可以将其视为一个联合体:
union {
    struct {
        chunk *fd;
        chunk *bk;
    } freed;
    unsigned char user_data[N];
};

在工会中,您可以写任何工会成员,但只能从最近写入的成员中读取。因此,当调用free时,会将数据写入fdbk中-没关系,唯一的结果是user_data现在可能有垃圾。相比之下,当块包含用户数据(不是免费的)时,fdbk指针是垃圾,因为它们是user_data的别名。

(从技术上讲,由于别名是user_data,因此无论别名是什么,您都可以始终从unsigned char中读取内容,但这并不是很重要。)

更新:这是底层C代码。您可能希望在malloc实现中使用低级C代码。在低级代码中,存在或不存在字段的想法是没有意义的,因为我们在不同类型之间进行转换,并允许指针相互别名。

在低级代码中,字段只是内存偏移量。在我的系统上,fd字段的偏移量可能为0,而bk字段的偏移量可能为8或4,这取决于我编译的体系结构。所以下面的代码:
BK->fd = FD;

这意味着“将值FD写入存储位置BK + 0”。如果您认为BK->fd只是内存中的位置,则可以帮助您了解free的工作方式。 (实际上,这不仅仅是内存中的一个位置,因为在编译时还存在类型信息和别名规则。)

了解底层C:如果您想了解底层C代码,则可以极大地帮助您理解汇编语言。这不是必需的,但有帮助。不管学习哪种汇编语言都可以:x86,MIPS,PowerPC,ARM等。您无需学习太多的汇编语言,只需一点点。您无需学习x86,即使您从未使用过MIPS,也可以学习MIPS。 (实际上,MIPS可能更容易学习。)

只需学习足够的汇编程序,您就可以将一小段C代码转换为汇编程序,以便您了解其幕后工作方式。上面的那一行C代码很可能转换为汇编代码的一行,因为它是如此简单。

在编写C时,尽量不要考虑汇编。编写C时,编译器在编写汇编,这意味着您不是在编写汇编。

关于c - glibc的free()的内部工作原理,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10645722/

相关文章:

c - 我的 C 函数没有返回正确的 int 到二进制值

c - 需要在 C 中使用 mod 输出分钟数方面的帮助吗?

python - 如何使用 python 的 guppy 打印所有行

data-structures - 查找存储为 Ahnentafel 数组的二进制最大堆的最小元素

c - 内存泄漏双指针

C++ 释放内存

c - 如何在字符中显示一个词?

C - fprintf 返回访问冲突写入位置 0x00246D1C

c - 当左右 child 相等且小于其 parent 时,我应该怎么做才能从最小堆中删除一个值

c - Valgrind 缩进(和低于主要)