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

来源:互联网 发布:兰州知豆电动车怎么租 编辑:程序博客网 时间:2024/06/06 10:39
来源:heli.bokee.com 作者:demonstrate 发布时间:2007-11-21 21:26:00内容:Memory1. 几个基本概念,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 thatvaries. 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 -.-b14. root 能调用一些相关函数将某个 page 锁住,这样不能被 paged out,目的是避免交换出去造成再读入的开销。相关函数在 sys/mmem.hint mlock (const void *addr, size_t len)int munlock (const void *addr, size_t len)int mlockall (int flags)int munlockall (void)这些是 POSIX.1b 的标准。
原创粉丝点击