What?

在程序执行的时候,根据用户指定的size,动态分配一块size大小的内存空间。这块内存空间分配后可以缩小扩大,也可以手动释放。

Why?

动态分配内存的特性,是传统数据类型是不能做到的,比如传统数组,声明的时候如果没有初始化,就必须给定长度,且该长度必须通过常整数的形式给定,不能是变量。一旦定义了长度,后期就不能更改,也不能手动释放。

HOW?

四个动态分配内存的函数:

需要引入

  1. 函数原型:**void *malloc(size)**

    动态分配一个size大小的连续空间,返回首地址。分配不成功返回NULL

  2. 函数原型:void *calloc(n,size)

    动态分配 n 个 size大小的连续空间,返回起始地址,分配不成功返回NULL

  3. 函数原型:void free(void *p)

    释放指定的动态内存空间

  4. 函数原型:void *realloc(void *p,size)

    重新分配动态空间的大小,分配失败返回NULL

  1. int *p = (int *)malloc(4);//分配一块int整型数据类型的空间,长度为4个字节。
  2. /*
  3. 为什么需要(int *)
  4. 因为malloc的原型是 void * malloc()
  5. 在旧版本中,void类型指针可以指向任意类型,但是不能赋值给不同类型的指针
  6. 在高版本中,是自动转换的。
  7. */
  8. free(p);//释放内存 避免内存泄漏

注意:

不要越界操作动态分配的内存空间:

  1. 释放会出错

    1、第一种说法 在分配堆内存空间时,malloc会自己管理一个链表用来维护堆中的内存(这种维护可以管理内存碎片,可以提高内存的利用率),由于malloc通过链表来维护,就必不可少的会利用空间来存放next指针域,这个next指针域就紧紧的挨在malloc分配的内存的后面。 所以,如果越界访问malloc分配的内存空间,就会破坏next域,从而破坏了链表结构 2、第二种说法 动态分配内存空间,实际上会在返回的地址前后分配额外的内存,用于记录申请空间的大小等元信息 如果越界访问改写了额外的内存,free()是会出错的,因为free就依赖这里的信息。

  2. 为什么有时候会出错有时候不会

    系统提供给我们的内存块是对齐的,这样就可以保证存储任何数据类型。也就是说系统会根据我们请求分配的块以及内存空间快的大小来决定是否以及如何对齐。所以实际上返回的内存块可能会比我们请求的大。所以这时候你以为的越界,并没有越界。 (这个答案可以从原版的《深入理解计算机系统》P878 得到答案)

什么是内存对齐

cpu在读取内存时是一块一块进行读取的,块的大小可以是2,4,8,16(总之是2的倍数)。因此CPU在读取内存时是一块一块进行读取的。
假设CPU要读取一个int型4字节大小的数据到寄存器中,分两种情况讨论:

  1. 数据从0字节开始
  2. 数据从1字节开始

再次假设内存读取粒度为4。
当该数据是从0字节开始时,CPU只需读取内存一次即可把这4字节的数据完全读取到寄存器中。 当该数据是从1字节开始时,问题变的有些复杂,此时该int型数据不是位于内存读取边界上,这一类就是内存未对齐的数据
此时CPU先访问一次内存,读取0—3字节的数据进寄存器,并再次读取4—5字节的数据进寄存器,接着把0字节和6,7,8字节的数据剔除,最后合并1,2,3,4字节的数据进寄存器。对一个内存未对齐的数据进行了这么多额外的操作,大大降低了CPU性能。

动态内存分配为什么要对齐

  1. 为了提取数据的效率(内存是一块块的提取数据的)
  2. 为了这块内存能尽可能的存储各种数据类型
  3. 为了空闲块能够对齐