malloc()和calloc()

痞子三分冷 提交于 2020-11-17 07:15:46

malloc()和calloc()

进程对动态内存的请求被认为是不紧迫的。例如,当进程的可执行文件被装入时,进程并不一定立即对所有的代码进行访问。类似地,当进程调用malloc() 请求动态内存时,并不意味着进程很快就会访问所有获得的内存。因此一般来说,内核总是尽量推迟给用户态进程动态分配内存。
    The kernel succeeds in deferring the allocation of dynamic memory to processes by using a new kind of resource. When a User Mode process asks 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 of page frames to processes.缺页异常处理程序最终为进程获取当前所需的page frames. 摘自《深入理解linux内核(第二版)》P269 -- 进程地址空间一章

    brk()是最常用的系统调用,用户进程通过它向内核申请空间(memory region或许还有page frames)。通过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 <stdio.h>
#include <stdlib.h>
#include <string.h>

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

    char *b;
    b = (char *)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)
    所以在释放指针所指进程空间后,应立刻将该指针置为空指针。该指针就没有机会再访问以前的进程空间了!

malloc()和calloc()都是用于分配内存的函数。
函数malloc()和calloc()都可以用来动态分配内存空间,但两者稍有区别。

malloc()函数有一个参数,即要分配的内存空间的大小:
void*malloc(size_tsize);

calloc()函数有两个参数,分别为元素的数目和每个元素的大小,这两个参数的乘积就是要分配的内存空间的大小。
void*calloc(size_tnumElements,size_tsizeOfElement);

如果调用成功,函数malloc()和函数calloc()都将返回所分配的内存空间的首地址。

函数malloc()和函数calloc() 的主要区别是前者不能初始化所分配的内存空间,而后者能。如果由malloc()函数分配的内存空间原来没有被使用过,则其中的每一位可能都是0;反之, 如果这部分内存曾经被分配过,则其中可能遗留有各种各样的数据。也就是说,使用malloc()函数的程序开始时(内存空间还没有被重新分配)能正常进行,但经过一段时间(内存空间还已经被重新分配)可能会出现问题。

函数calloc() 会将所分配的内存空间中的每一位都初始化为零,也就是说,如果你是为字符类型或整数类型的元素分配内存,那麽这些元素将保证会被初始化为0;如果你是为指 针类型的元素分配内存,那麽这些元素通常会被初始化为空指针;如果你为实型数据分配内存,则这些元素会被初始化为浮点型的零。

memset

  功 能: 将s所指向的某一块内存中的每个字节的内容全部设置为ch指定的ASCII值,
  块的大小由第三个参数指定,这个函数通常为新申请的内存做初始化工作
  用 法: void * memset (void *s, char ch, unsigned n);
  程序例:
  #include <string.h>
  #include <stdio.h>
  #include <mem.h>
  int main(void)
  {
  char buffer[] = "Hello world/n";
  printf("Buffer before memset : %s/n", buffer);
   memset (buffer, '*', strlen(buffer) );
  printf("Buffer after memset : %s/n", buffer);
  return 0;
  }
  输出结果:
  Buffer before memset : Hello world
  Buffer after memset : ***********
  编译平台:
  Microsoft Visual C++ 6.0
  也不一定就是把内容全部设置为ch指定的ASCII值,而且该处的ch可为int或者其他类型,并不一定要是char类型。例如下面这样:
  int array[5] = {1,4,3,5,2};
  for(int i = 0; i < 5; i++)
  cout<<array <<" ";
  cout<<endl;
   memset (array,0,5*sizeof(int));
  for(int k = 0; k < 5; k++)
  cout<<array [k] <<" ";
  cout<<endl;
  输出的结果就是:1 4 3 5 2
  0 0 0 0 0
  后面的表大小的参数是以字节为单位,所以,对于int或其他的就并不是都乘默认的1(字符型)了。而且不同的机器上int的大小也可能不同,所以最好用sizeof()。
   要注意的是,memset 是对字节进行操作,所以上述程序如果改为
  int array[5] = {1,4,3,5,2};
  for(int i = 0; i < 5; i++)
  cout<<array <<" ";
  cout<<endl;
   memset (array,1,5*sizeof(int));// 注意 这里与上面的程序不同
  for(int k = 0; k < 5; k++)
  cout<<array [k] <<" ";
  cout<<endl;
  输出的结果就是:1 4 3 5 2
  16843009 16843009 16843009 16843009 16843009
   为什么呢?
   因为memset 是以字节为单位 就是对array指向的内存的5个字节进行赋值,每个都用ASCII为1的字符去填充,转为二进制后,1就是00000001,占一个字节。一个INT元素是4字节,合一起就是1000000010000000100000001,就等于16843009,就完成了对一个INT元素的赋值了。
  所以用 memset 对非字符型数组赋初值是不可取的!
  楼上说的很对,只是程序执行结果是0 0 0 0;程序不同的地方不在那里。程序如下:
  int array[5] = {1,4,3,5,2};
  for(int i = 0; i < 5; i++)
  cout<<array<<" ";
  cout<<endl;
   memset (array,1,5*sizeof(int)); //这里才是不同的地方
  for(int k = 0; k < 5; k++) //不同不在这里,k=1只是少循环了一次而已
  cout<<array[k]<<" ";
  cout<<endl;
  例如有一个结构体Some x,可以这样清零:
   memset ( &x, 0, sizeof(Some) );
  如果是一个结构体的数组Some x[10],可以这样:
  menset( x, 0, sizeof(Some)*10 );

memset 函数详细说明

  1。void * memset (void *s,int c,size_t n)
  总的作用:将已开辟内存空间 s 的首 n 个字节的值设为值 c。
  2。例子
  main(){
  char *s="Golden Global View";
  clrscr();
   memset (s,'G',6);//貌似这里有点问题//
  printf("%s",s);
  getchar();
  return 0;
  } 
  【这个问题相当大,程序根本就运行不下去了,你这里的S志向的是一段只读的内存,而你 memset 又试图修改它,所以运行时要出错,修改办法char *s修改为char s[]】
修改后如下:

#include <stdio.h>
#include <string.h>

int main()
{
 char s[]="Golden Global View";
 memset(s,'G',strlen(s));//貌似这里有点问题//
 printf("%s",s);
 return 0;
}


 
  3。 memset () 函数常用于内存空间初始化。如:
  char str[100];
   memset (str,0,100);
  4。 memset ()的深刻内涵:用来对一段内存空间全部设置为某个字符,一般用在对定义的字符串进行初始化为‘ memset (a, '/0', sizeof(a));
  memcpy用来做内存拷贝,你可以拿它拷贝任何数据类型的对象,可以指定拷贝的数据长度;例:char a[100],b[50]; memcpy(b, a, sizeof(b));注意如用sizeof(a),会造成b的内存地址溢出。
  strcpy就只能拷贝字符串了,它遇到'/0'就结束拷贝;例:char a[100],b[50];strcpy(a,b);如用strcpy(b,a),要注意a中的字符串长度(第一个‘/0’之前)是否超过50位,如超过,则会造成b的内存地址溢出。
  5.补充:某人的一点心得
   memset 可以方便的清空一个结构类型的变量或数组。
  如:
  struct sample_struct
  {
  char csName[16];
  int iSeq;
  int iType;
  };
  对于变量
  struct sample_strcut stTest;
  一般情况下,清空stTest的方法:
  stTest.csName[0]='/0';
  stTest.iSeq=0;
  stTest.iType=0;
  用 memset 就非常方便:
   memset (&stTest,0,sizeof(struct sample_struct));
  如果是数组:
  struct sample_struct TEST[10];
  则
   memset (TEST,0,sizeof(struct sample_struct)*10);
  6。strcpy
  原型:extern char *strcpy(char *dest,char *src);
  用法:#i nclude
  功能:把src所指由NULL结束的字符串复制到dest所指的数组中。
  说明:src和dest所指内存区域不可以重叠且dest必须有足够的空间来容纳src的字符串。
  返回指向dest的指针。
  memcpy
  原型:extern void *memcpy(void *dest, void *src, unsigned int count);
  用法:#i nclude
  功能:由src所指内存区域复制count个字节到dest所指内存区域。
  说明:src和dest所指内存区域不能重叠,函数返回指向dest的指针。
   memset
  原型:extern void * memset (void *buffer, int c, int count);
  用法:#i nclude
  功能:把buffer所指内存区域的前count个字节设置成字符c。

  说明:返回指向buffer的指针。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!