> 文章列表 > C 学习笔记 —— 动态分配内存(malloc)

C 学习笔记 —— 动态分配内存(malloc)

C 学习笔记 —— 动态分配内存(malloc)

文章目录

    • 分配内存
      • malloc
      • calloc
      • realloc
      • 创建数组方式
      • free的重要性
      • 举例
    • 常见动态分配内存错误
        • 忘记检查所请求的内存
        • 对NULL指针进行解引用
        • 对分配的内存越界访问
        • 释放一块内存后,继续使用
        • 释放一块内存的一部分是不允许的
        • 内存泄漏

分配内存

当一个数组声明时,需要指定数组大小,这个内存空间在编译的时候就已经被分配了。
但是如果我们要使用一个数组,我们事先并不知道他的大小,而想在运行时计算出来,那么也可以在程序运行时动态分配内存。

上面的内存分配与回收都是系统帮我们做了,而我们不用管。如果我们想自己更灵活的分配和回收内存可以使用malloc和free。
需要引入#include <stdlib.h>库头文件,内存分配函数在这里面。

malloc

void * malloc(size_t size);
给malloc一个参数,表示要申请的内存大小,所需的byte数。
malloc会在内存池中找到一块空闲(连续)内存,返回内存块的首字节地址,所以可以把该地址赋值给一个指针,后面可以使用该指针访问这块内存。
这块内存通常比malloc申请的大一些,因为要存储一些信息。

如果malloc分配内存失败,则他会返回一个空指针 NULL,我们可以通过是否为空指针来判断是否成功分配了内存,这个很重要。

返回值类型是一个void类型的指针,该类型相当于一个通用指针,该指针通常被强转为一个匹配的类型并不需要考虑类型不匹配的问题,并且我们应该坚持这样做来保证代码的可读性,C中虽然不要求强制转换,但是C++中必须使用,那么我们坚持这样做来保证可移植性。

double *pt = (double *) malloc (30 * sizeof(double));

这段代码为30个double类型的值申请了一块内存,并设置pt指向该内存首元素地址。
回忆一下,数组名为该数组的首元素地址。所以pt指向该块内存的首元素,那么我们可以用数组名来使用它。可以使用数组名来表示指针,也可以使用指针来表示数组名
我们可以使用pt[0]来访问该块的首元素,pt[1]来访问第二个元素。

calloc

函数原型

void * calloc(size_t num_elements, size_t element_size);

calloc函数

long* num;
num = (long *)calloc(100, sizeof(long));

calloc第一个参数是存储单元数量,第二个参数是存储单元大小。
他的返回值也是void* 需要强转成需要的类型。
他还有一个特性是会把分配后的所有值都初始化为0. malloc不保证这一点,他的内存值并没有初始化为0。

realloc

void *realloc(void *ptr, size_t new_size);

realloc函数用于修改一个原先已经分配的内存块的大小。
使用这个函数可以让一个内存扩大或者缩小。
如果扩大内存,则原内容保留,扩大到内存部分并不保证数据的初始化。
如果缩小内存,则改内存尾部部分会被删掉 ,剩余内存保留原县内容。

操作系统中可能原内存块无法改变大小,这是操作系统会分配一块新内存出来,新内存遵循上面两个规则,并将新指针返回。
如果realloc的第一个参数为NULL,则他的行为和malloc函数一摸一样。

创建数组方式

所以现在我们有两种方式创建数组
第一种:直接创建double item[30];
第二种:动态创建

int n = 6; 
double *pt = (double *) malloc(n * sizeof(double));

第一种我们必须指定一个常量作为数组的大小。
而第二种的优势是我们可以创建一个变长数组。
使用malloc在运行时才确定内存数组的大小。

使用的时候,我们不仅可以使用指针,还可以使用下标来访问内存,如下例子:

#include <stdio.h>
#include <stdlib.h> /* for malloc(), free() */int main(void)
{double * ptd;int max;int number;int i = 0;max = 6;ptd = (double *) malloc(max * sizeof (double)); //分配动态数组if (ptd == NULL) //如果分配失败则退出{puts("Memory allocation failed. Goodbye.");exit(EXIT_FAILURE);//异常退出程序}/* ptd now points to an array of max elements */puts("Enter the values (q to quit):");while (i < max && scanf("%lf", &ptd[i]) == 1)++i;printf("Here are your %d entries:\\n", number = i);for (i = 0; i < number; i++){printf("%7.2f ", ptd[i]); //使用指针也即数组名访问元素if (i % 7 == 6)putchar('\\n');}if (i % 7 != 0)putchar('\\n');puts("Done.");free(ptd);//释放内存return 0;
}

free的重要性

我们使用free函数来释放malloc分配的内存,free接受参数为malloc返回的指针。
free的参数要么是NULL,要么是malloc,calloc,realloc返回的值。
当不断分配内存而忘记释放,可能会耗尽内存,这类问题称为内存泄漏。

举例

#include <stdio.h>
#include <stdlib.h>
#include <string.h>int static_store = 30;
const char * pcg = "String Literal";
int main()
{int auto_store = 40;char auto_string[] = "Auto char Array";int * pi;char * pcl;pi = (int *) malloc(sizeof(int));*pi = 35;pcl = (char *) malloc(strlen("Dynamic String") + 1);strcpy(pcl, "Dynamic String");printf("static_store: %d at %p\\n", static_store, &static_store);printf("  auto_store: %d at %p\\n", auto_store, &auto_store);printf("         *pi: %d at %p\\n", *pi, pi);printf("  %s at %p\\n", pcg, pcg);printf(" %s at %p\\n", auto_string, auto_string);printf("  %s at %p\\n", pcl, pcl);printf("   %s at %p\\n", "Quoted String", "Quoted String");free(pi);free(pcl);return 0;
}
// static_store: 30 at 0x601048     静态区域
//   auto_store: 40 at 0x7fff5f25f8dc   自动区域
//          *pi: 35 at 0xeea010     堆
//   String Literal at 0x4007a0     静态区域
//  Auto char Array at 0x7fff5f25f8c0   自动区域
//   Dynamic String at 0xeea030     堆
//    Quoted String at 0x40080e     静态区域

常见动态分配内存错误

忘记检查所请求的内存

对NULL指针进行解引用

对分配的内存越界访问

如果我们通过数组下标访问了arr[-1]或者arr[11],那么arr[-1]是向前越界了,arr[11]是向后越界了。一般越界是越到变量分配的内存区域的后面区域,很少越界到当前访问的变量内存前面去的。有人可能会说,怎么可能会越界到负的下标(arr[-1])上去了呢?我们还真遇到过,后面我们给大家专门讲一个向前越界的实例。

释放一块内存后,继续使用

释放一块内存的一部分是不允许的

free(pi+5)

内存泄漏