GNU C library 笔记(2) --- 内存相关

来源:heli.bokee.com 作者:demonstrate 发布时间:2007-11-21 21:26:00 内容:Memory 1. 几个基本概念,page、frame、paging、segment。 进程分配内存的两种模式,一个使用 exec 系列函数,一个使用 programmatically(malloc 等函数)。 重要的 segment 有 text segment(存放代码等等,一般在进程的生命周期中不变)、data segment (存放数据,能用一些函数来调整大小,不过低位端位置不变)、stack segment(随着使用的堆栈 变大而变大,但不变小...) 2. 内存的静态分配和自动分配。前者是对于 static 变量或全局变量,一旦开始就分配,即一直存在到最后。 后者是临时变量,如调用函数。值得注意的是: In GNU C, the size of the automatic storage can be an expression that varies. In other C implementations, it must be a constant. 3. 内存的动态分配不为 C 语言本身支持,不像 C++。基本方法是 void * malloc (size_t size) 分配到的内存没有初始化(calloc 会做清零,clear allocate),因此能用 memset 来 进行初始化。分配后应检测返回指针是否为 NULL。malloc 返回的一个块多数情况下对齐 (这样能存储任意类型的数据)过了,地址为 8 的倍数(64 位系统里面是 16 的倍数), 在一些特别情况下(page 的边界)能利用 memalign、posix_memalign、vlign 来返回 对齐(2 的幂次)的内存块。free 的内存非常少被返还给操作系统,多数情况被留作后面 malloc 使用。如果需要调整已 malloc 的块的大小,使用 realloc。glibc 不会将分配的块 对 2 的幂次进行向上取整。 4. 一次性分配非常大的内存(大于一个 page)会使用向 2 的幂次取整的策略,使用 mmap 相关的函数 mallopt,这种分配得到的内存在 free 时会返回给操作系统。 5. 怎么调整 malloc 的行为?使用 mallopt 调整一些参数的值(malloc.h),如 M_TRIM_THRESHOLD(返回给 OS 的内存的一个阈值)、M_MMAP_THRESHOLD (大于此值的内存分配请求使用 mmap 系统调用)等等。 6. 分配的内存来自堆(heap),能用 int mcheck (void (*abortfn) (enum mcheck_status status)) 来检查分配内存的一致性,调用在 malloc 之前。 enum mcheck_status mprobe (void *pointer) 为特定的一块内存做检查。这都是 GNU extension。(mcheck.h) 7. 为了方便调试,glibc 为用户提供了 malloc 等等函数的钩子(hook),如 __malloc_hook 对应的是个函数指针, void *function (size_t size, const void *caller) 其中 caller 是调用 malloc 返回值的接受者(一个指针的地址)。另外有 __malloc_initialize_hook 函数指针,仅仅会调用一次(第一次分配动态内存时)。(malloc.h) 8. 一些使用 malloc 的统计量(SVID 扩展)能用 struct mallinfo 储存, 可调用 struct mallinfo mallinfo (void) 获得。 9. 怎么检测 memory leakage?glibc 提供了一个函数 void mtrace (void) 及其反作用 void muntrace (void) 这时会依赖于一个环境变量 MALLOC_TRACE 所指的文件,把一些信息记录在该文件中 用于侦测 memory leakage,其本质是安装了前面提到的 hook。一般将这些函数用 #ifdef DEBUGGING 包裹以便在非调试态下减少开销。产生的文件据说不建议自己去读, 而使用 mtrace 程式(perl 脚本来进行分析)。下面用一个简单的例子说明这个过程,这是 源程式: #include #include #include intmain( int argc, char *argv[] ){ int *p, *q ;#ifdef DEBUGGING mtrace( ) ;#endif p = malloc( sizeof( int ) ) ; q = malloc( sizeof( int ) ) ; printf( "p = %p\nq = %p\n", p, q ) ; *p = 1 ; *q = 2 ; free( p ) ; return 0 ;} 非常简单的程式,其中 q 没有被释放。我们设置了环境变量后并且 touch 出该文件 执行结果如下: p = 0x98c0378q = 0x98c0388 该文件内容如下 = Start@ ./test30:[0x8048446] + 0x98c0378 0x4@ ./test30:[0x8048455] + 0x98c0388 0x4@ ./test30:[0x804848f] - 0x98c0378 能知道带有 + 的表示 malloc 了内存,而 - 表示释放,后面的 0x4 是分配的内存大小 可见正常情况 + - 数目应该相同多,目前不相同表明出现了 leakage。接着我们看看前面的 地址样的东西是啥,objdump 之得 08048424 :... 8048441: e8 d2 fe ff ff call 8048318 8048446: 89 45 f4 mov %eax,0xfffffff4(%ebp) 8048449: c7 04 24 04 00 00 00 movl {fckeditor}x4,(%esp) 8048450: e8 c3 fe ff ff call 8048318 ; 8048455: 89 45 f8 mov %eax,0xfffffff8(%ebp) 8048458: 8b 45 f8 mov 0xfffffff8(%ebp),%eax 804845b: 89 44 24 08 mov %eax,0x8(%esp) 804845f: 8b 45 f4 mov 0xfffffff4(%ebp),%eax 8048462: 89 44 24 04 mov %eax,0x4(%esp) 8048466: c7 04 24 54 85 04 08 movl {fckeditor}x8048554,(%esp) 804846d: e8 c6 fe ff ff call 8048338 8048472: 8b 45 f4 mov 0xfffffff4(%ebp),%eax 8048475: c7 00 01 00 00 00 movl {fckeditor}x1,(%eax) 可见是调用函数开始地方的地址。下面使用 mtrace 命令行工具进行分析,其基本调用方式为 mtrace binary tracefile。当使用 gcc -DDEBUGGING -g 编译才能获得最佳的效果,会报告出 哪一行上的没有被释放掉。 10. obstack 是什么? 能看做任意“对象”的 stack,用户能建立多个 obstack,其使用类似于一个 stack 相关的定义在 obstack.h 中。使用一个 obstack 是通过一个结构 struct obstack 实现的。 obstack 中的东西放在 chunk 中,chunk 使用用户自定义的函数(其实是个 macro )分配 后来的东西是用一些接口函数放到 obstack 中,真是标准的 C 实现啊... 比较特别的是一种能 grow 的对象的放入。建立一个 obstack 只需要弄一个结构,然后用 static struct obstack myobstack; obstack_init (&myobstack); 当然,malloc 一个也行的。不过首先应该告诉编译器用什么分配 chunk -.- #define obstack_chunk_alloc malloc #define obstack_chunk_free free 分配的 chunk 大小是用下面的 macro 决定的 int obstack_chunk_size (struct obstack *obstack-ptr) 调用方式如下: obstack_chunk_size (obstack_ptr) = new-chunk-size; 如果 trunk 分配失败,则会调用 obstack_alloc_failed_handler 对应的函数指针。 下面看看怎么将一些东西放进去。最直接的就是通过分配一块地方 void * obstack_alloc (struct obstack *obstack-ptr, int size) 然后手动 memcpy 好了... 这样麻烦,因此有个 void * obstack_copy (struct obstack *obstack-ptr, void *address, int size) 减少工作量,对于字符串呢更有更简单的 void * obstack_copy0 (struct obstack *obstack-ptr, void *address, int size) 这样最后的 0x0 会自动被添加。如果想释放,能调用 void obstack_free (struct obstack *obstack-ptr, void *object) 这时 obstack 中在此 object 之后添加的都会被释放掉(这才是 stack 嘛~)注意,如果 object == NULL,此时 就不仅仅释放了所有的 obstack 中的东西,obstack 自己也被恢复到未经初始化的状态了。 下面看看怎么添加能 grow 的对象,最基本的 void obstack_blank (struct obstack *obstack-ptr, int size) 分配空间,但不初始化, void obstack_grow (struct obstack *obstack-ptr, void *data, int size) 让这部分空间变大(size 能为负,那就是变小,但不会缩过头 @@),为了方便字符串等等数据类型 能使用下面系列函数 void obstack_grow0 (struct obstack *obstack-ptr, void *data, int size) void obstack_1grow (struct obstack *obstack-ptr, char c) void obstack_ptr_grow (struct obstack *obstack-ptr, void *data) void obstack_int_grow (struct obstack *obstack-ptr, int data) grow 完了最后要 finish 一下 void * obstack_finish (struct obstack *obstack-ptr) 能在 finish 之前测一下这个家伙有多大 int obstack_object_size (struct obstack *obstack-ptr) 不过因为这样做每 grow 一点都会检查是否需要分配新的 chunk 因此较慢,当有连续的 grow 产生而对 chunk 的把握正确的时候能用对应的 fast grow 系列函数,如 void obstack_1grow_fast (struct obstack *obstack-ptr, char c) void obstack_blank_fast (struct obstack *obstack-ptr, int size) 为了方便之前检查 chunk 剩余空间是否够用能用 int obstack_room (struct obstack *obstack-ptr) 下面是一些其他的和 obstack 函数 void * obstack_base (struct obstack *obstack-ptr) 返回下一个 object 的地址(栈顶),如果有 growing object 则是该 object 地址。 如果需要进行对齐,下面的 macro 设置其掩膜 int obstack_alignment_mask (struct obstack *obstack-ptr) 使用类似设置 chunk size。 11. 一些 macro 调用的问题 obstack 是在老的 C 编译器上可能不好正常工作,特别是对利用 maco 产生了重定义的函数不能取址 不过遵循 ISO C 编译器能,不过不确保在使用宏调用时参数会被多次利用产生的后果,如 obstack_alloc (get_obstack (), 4); 中 get_obstack () 或 *obstack_list_ptr++ 这类参数。不过 GNU C 下这类参数不会被多次展开。 12. 可变大小自动释放类型,一个是 BSD extension,在 stdlib.h 中定义,使用 alloca 分配的 intopen2 (char *str1, char *str2, int flags, int mode){ char *name = (char *) alloca (strlen (str1) + strlen (str2) + 1); stpcpy (stpcpy (name, str1), str2); return open (name, flags, mode);} 这样一个好处在于 longjmp() 时不必再手工释放这部分内存。并且使用 alloca 分配的内存是 统一管理,不会造成内存碎片化。不过非 GNU 系统可能不支持,如果分配的内存太大会使程式崩溃 另外,还能用 GCC 的方式: int open2 (char *str1, char *str2, int flags, int mode){ char name[strlen (str1) + strlen (str2) + 1]; stpcpy (stpcpy (name, str1), str2); return open (name, flags, mode);} 两种方式并不相同,后者可能仍然在栈内分配的内存,因此作用域结束即释放,而前者在程式结束 才释放。前者可用于循环体内,后者不可。 13. brk() 和 sbrk() 用于调整 data segment 的 high end,有什么用? int brk (void *addr) int sbrk (ptrdiff_t delta) 名字由来是原来进程里面 data segment 和 stack 对着干,一个从上向下长,一个从下向上长 中间隔开他们的是 break -.-b 14. root 能调用一些相关函数将某个 page 锁住,这样不能被 paged out,目的是避免交换出去 造成再读入的开销。相关函数在 sys/mmem.h int mlock (const void *addr, size_t len) int munlock (const void *addr, size_t len) int mlockall (int flags) int munlockall (void) 这些是 POSIX.1b 的标准。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值