malloc 和calloc

来源:互联网 发布:网络机房建设合同 编辑:程序博客网 时间:2024/05/18 20:51
进程对动态内存的请求被认为是不紧迫的。例如,当进程的可执行文件被装入时,进程并不一定立即对所有的代码进行访问。类似地,当进程调用malloc()请求动态内存时,并不意味着进程很快就会访问所有获得的内存。因此一般来说,内核总是尽量推迟给用户态进程动态分配内存。
    The kernel succeeds in deferring the allocation of dynamic memoryto processes by using a new kind of resource. When a User Mode processasks for dynamic memory, it doesn't get additional page frames;instead, it gets the right to use a new range of linear addresses,which become part of its address space. This interval is called a"memory region."

   内核使用一种资源成功实现了对进程动态内存的推迟分配。当用户态进程请求动态内存时,并没有获得请求的页框,而仅仅获得对一个新的线性地址区的使用权,而这一线性地址区间就成为进程地址空间的一部分。这个区间叫做线性区(memory region).
    the Page Fault exception handler in deferring the allocation ofpage frames to processes.缺页异常处理程序最终为进程获取当前所需的page frames.摘自《深入理解linux内核(第二版)》P269 -- 进程地址空间一章

    brk()是最常用的系统调用,用户进程通过它向内核申请空间(memory region或许还有pageframes)。通过malloc()一类的C语言库函数间接地用到brk()。如果把malloc()想像成零售,brk()就是批发。库函数malloc()为用户进程维护一个小仓库,当进程需要使用更多的内存空间时就向小仓库要,小仓库中存量不足时就通过brk()向内核批发。alloc()是如何间接用到brk()的?《情景分析(上)》P160




malloc()
-------------------------------------------
    malloc()函数用来分配内存:将总共需要的字节数作为参数传递给该函数,返回值是指向最新分配的内存的指针,而如果内存没有分配好,则返回值是NULL。
malloc()的使用技术:
    some_type *pointer;
    pointer = malloc(count * sizeof(*pointer));

注:
(1) 这个方法保证malloc()会分配正确数量的内存,而不用考虑pointer的生命。如果pointer的类型后来变了,sizeof算子自动确保要分配的字节数仍然正确。
(2) malloc()返回的内存是“没有“初始化的。这块内存可能包含任何随机的垃圾,你可以马上用有效数据或者至少是用零来初始化这块内存。要用0初始化,可以用
void *memset(void *s, int c, size_t n);
(3) malloc()最终通过缺页异常获取的物理内存中的原有数据,大多数情况下是0(但不能保证一定是0)



calloc()
-------------------------
    calloc()函数是malloc的简单包装。它的主要优点是把动态分配的内存清零。
    void *calloc(size_t nmemb, size_t size);
    用经验的程序员更喜欢使用calloc(),因为这样的话新分配内存的内容就不会有什么问题,调用calloc()肯定会清0,并且可以避免调用memset().



malloc()实践
-----------------------------------------------
#include
#include
#include

int main()
{
    char *a;
    a = malloc(10 * sizeof(*a)); //(1)
    memset(a, 'a', 10);          //(2)
    printf("a = %s", a);
    free(a);                    //(3)
    a = NULL;                  //(5)

    char *b;
    b = malloc(10 * sizeof(*b)); //(4)
    memset(b, 'b', 10);
    printf("a = %s", a);
    printf("b = %s", b);
    free(b);
}


注:
-----------------------------------------
(1)
用户地址空间(虚拟内存)
          |        |
          |--------|            
          |        |           
          |        |            10bytes
          |        |           
a ------->|        |0x804a008  
          |--------|           
          |        |
a = malloc();执行后


(2)
真正的物理内存在此通过缺页异常获得

|------------|------------|--------------|
| 0000100000 | 0001001010 | 000000001000 |
|------------|------------|--------------|
   |                 |                  |
   |
-|---------|    |   |---------|    |    |---------|
   |
-|         |    |   |         |    |    |         |
   |
-|         |    |   |         |    |    |---------|
   |
-|         |    |   |         |    |    |    a    |
   |
-|         |    |   |         |    |    |   ...   |10bytes
   | -|         |    |   |         |    |    |   ...   |
   +->|         |--+ +-->|         |--+ |    |    a    |
      |         |
-|     |         | -| +--->|- -------|
      |         |
-|     |         | -|      |         |
      |---------|
-+---->|---------| -+----->|---------|
         页目录表                 页表             存放有效数据的物理页
                        物理内存

memset(a, 'a', 10);执行后


(3)
    执行free(a)以后a依然指向0x804a008处的一段用户空间,但是该空间已经不再属于a指针管理了,相应的物理内存也被回收并清0了。如果释放指针a之后立刻再次使用a访问其所指空间,缺页异常将再次为该进程空间分配新的物理内存(可能还是释放前的那块物理空间,但是已经清0了)。

    如果有新的动态内存请求b =malloc(),指针a所释放的进程空间可能会被重新分配。用指针a然依然可以访问(读或者写)该进程空间所对应的物理空间(该物理空间是访问新指针时由缺页异常为新指针分配的,可能就是为指针p所分配的物理内存)(不会报错),但是这样的操作是非法的,因为该空间可能已经被新的主人(新指针)所管理。

(4)
用户地址空间
          |        |
          |--------|
          |        |
          |        |         10bytes
          |        |
b ------->|        |0x804a008
(a)       |--------|
          |        |
b = malloc();执行后
   此时a和b同时指向相同的进程空间(该进程空间所对应的物理空间也相同),对a或b的操作都将会操作相同的空间(物理空间),这是很危险的,因为这块空间的正真主人是b,而a也有能力修改b所指的空间(哇塞!以前的房主混进房子里来了,新房主的财产可就危险了)

(5)
    所以在释放指针所指进程空间后,应立刻将该指针置为空指针。该指针就没有机会再访问以前的进程空间了!