Bootstrap

动态分配内存

目录

前言

一.malloc,free函数

1.malloc,free函数原型

2.使用方法

3.具体实例

4.注意事项

二.calloc函数

1.calloc函数原型

2.主要特点

3.使用案例

三.realloc函数

1.realloc函数原型

2.使用案例

3.注意事项


前言

动态内存是指在程序运行时,按需分配和释放的内存空间。这种内存分配不在编译时固定,而是在程序执行过程中,根据实际需要分配,并且可以手动释放。

以下介绍的函数都包含于头文件<stdlib.h>

为什么使用动态内存?

1.灵活性高。动态内存可以根据需要分配内存块。

2.避免浪费内存。动态内存允许程序根据需要分配精确的内存量,从而更有效地使用内存资源。

一.malloc,free函数

malloc 是 C 标准库中用于动态内存分配的函数。它从堆(heap)中分配指定字节数的内存,并返回指向这块内存的指针。如果内存分配失败,它会返回 NULL

free 函数是C标准库中的一个函数,用于释放由动态内存分配函数(如 malloccallocrealloc)分配的内存。它的作用是将不再需要的动态内存归还给操作系统,防止内存泄漏

1.malloc,free函数原型

void* malloc(size_t size);

  • size:要分配的内存大小,以字节为单位。
  • 返回值:成功时,返回指向已分配内存块的指针;失败时,返回 NULL
  • void*:返回值类型

void free(void* ptr);

  • 参数:

    • ptr:一个指向要释放内存块的指针,这个指针必须是通过 malloccallocrealloc 返回的指针。如果 ptrNULLfree 不会执行任何操作。
  • 返回值:

    • free 没有返回值。

2.使用方法

(1)内存分配,如:

int* p = (int*)malloc(sizeof(int)); // 为一个 int 类型变量分配内存

(2)判断是否开辟空间成功(如果没有开辟成功,直接使用指针可能回答导致程序崩溃,未定义行为等),如:

if (p == NULL) {
	perror("malloc");//打印错误信息的函数
	return 1;//分配失败,退出程序
}

(3)释放内存(防止内存泄露),如:

free(p);

(4)将指针置为空(防止指针在释放内存地址后还指向原来那个地址,相当于野指针,可能会导致未定义行为或者程序崩溃等等),:

p = NULL;

3.具体实例

#include<stdio.h>
#include<stdlib.h>
int main() {
	int* arr = (int*)malloc(sizeof(int) * 10);
	//或者int* arr=(int*)malloc(40);
	//判断是否分配内存成功
	if (arr == NULL) {
		perror("malloc");//打印错误信息
		return 1;//分配失败,退出程序
	}
    //初始赋值
	for (int i = 0; i < 10; i++) {
		arr[i] = i + 1;
	}
    //打印结果
	for (int i = 0; i < 10; i++) {
		printf("%d ", arr[i]);
	}
	//释放内存
	free(arr);
	//将arr置为空
	arr = NULL;
	return 0;
}

需要注意的是:

在int* arr = (int*)malloc(sizeof(int) * 10);这一语句中

1.(int*)是强制转换类型,将原本的返回值类型void*转化为我们需要定义的数据类型,当然这里我们定义的是int*类型的。

2.sizeof(int)*10是int类型的字节数乘以10,即我们要开辟4*10=40个字节,开辟10个整型的空间,当然我们也可以这么写malloc(40),前提是你要计算好你需要开辟的字节数,否则在没有开辟够这么多的字节数的情况下越界访问数组可能会导致程序崩溃或者无限循环等的行为。

4.注意事项

malloc开辟的空间是没有创建初始值的。

举例如下:

#include<stdio.h>
#include<stdlib.h>
int main() {
	int* p = (int*)malloc(sizeof(int) * 5);
	for (int i = 0; i < 5; i++) {
		printf("%d ", p[i]);
	}
	return 0;
}

结果是随机值:

二.calloc函数

calloc 是 C 语言中的标准库函数,用于动态分配内存。与 malloc 类似,calloc 也用于在堆上分配一块内存

1.calloc函数原型

void* calloc(size_t num, size_t size);

  • num:要分配的元素个数。
  • size:每个元素的大小(以字节为单位)。

2.主要特点

(1)分配并初始化内存为零

calloc 会分配一块内存,并将这块内存中的所有字节初始化为 0。这与 malloc 的行为不同,malloc 仅仅分配内存,但不对内存进行初始化,内存中的内容可能是垃圾值。

(2)两个参数

malloc 不同,calloc 需要两个参数:第一个参数是要分配的元素个数,第二个参数是每个元素的大小。malloc 只需要一个参数(总内存大小),而 calloc 的目的是让用户更加清晰地定义数组类型的内存分配。

3.使用案例

#include <stdio.h>
#include <stdlib.h>

int main() {
    int* arr = (int*)calloc(5, sizeof(int));  // 分配5个int大小的空间,且初始化为0

    if (arr == NULL) {
        printf("Memory allocation failed!\n");
        return 1;
    }

    // 输出分配并初始化的数组
    for (int i = 0; i < 5; i++) {
        printf("%d ", arr[i]);  // 输出结果将是 0 0 0 0 0
    }
    printf("\n");

    // 使用完成后释放内存
    free(arr);
    arr=NULL; 

    return 0;
}

结果如图:

三.realloc函数

realloc 函数用于调整已经分配的动态内存的大小

1.realloc函数原型

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

  • ptr:指向已经通过 malloc, calloc 或者 realloc 分配的内存。如果 ptrNULLrealloc 的行为与 malloc 类似,分配一块新的内存。
  • size:新分配的内存大小,以字节为单位。如果 size 为 0,realloc 的行为与 free 类似,会释放 ptr 指向的内存。

2.使用案例

(1)扩大内存

#include <stdio.h>
#include <stdlib.h>

int main() {
    int* arr = (int*)malloc(5 * sizeof(int));  // 分配5个int的内存

    if (arr == NULL) {
        perror("malloc");
        return 1;
    }

    // 初始化数组
    for (int i = 0; i < 5; i++) {
        arr[i] = i;
    }
    //第一次打印
    for (int i = 0; i < 5; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
    // 调整数组大小,增加到10个int
    int* new_arr = (int*)realloc(arr, 10 * sizeof(int));

    if (new_arr == NULL) {
        perror("realloc");
        free(arr);  // 如果失败,释放原来的内存
        arr = NULL;
        return 1;
    }

    // 如果重新分配成功,继续使用新数组
    arr = new_arr;

    // 初始化新增加的部分(第二次打印)
    for (int i = 5; i < 10; i++) {
        arr[i] = i;
    }

    // 输出数组
    for (int i = 0; i < 10; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");

    // 释放内存
    free(arr);
    arr = NULL;
    return 0;
}

结果如图:

(2)缩小内存

#include <stdio.h>
#include <stdlib.h>

int main() {
    // 分配 10 个 int 大小的内存
    int* arr = (int*)malloc(10 * sizeof(int));

    if (arr == NULL) {
        perror("malloc");
        return 1;
    }

    // 初始化数组
    for (int i = 0; i < 10; i++) {
        arr[i] = i + 1;
    }

    printf("初始10个元素:\n");
    for (int i = 0; i < 10; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");

    // 使用 realloc 缩小数组到 5 个 int
    int* new_arr = (int*)realloc(arr, 5 * sizeof(int));

    if (new_arr == NULL) {
        perror("realloc");
        free(arr);  // 如果 realloc 失败,释放原来的内存
        arr = NULL;
        return 1;
    }

    // 如果 realloc 成功,继续使用缩小后的数组
    arr = new_arr;

    printf("缩小后的元素:\n");
    for (int i = 0; i < 5; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");

    // 释放缩小后的内存
    free(arr);
    arr = NULL;

    return 0;
}

结果如图:

注意事项:

  • 在缩小内存时,数组后面的数据会丢失。程序只能访问缩小后剩余的那部分数据。
  • 缩小内存后,应确保访问的索引在新分配的范围内,避免越界访问。

3.注意事项

  • 扩展/缩小内存realloc 可以扩展或者缩小已经分配的内存块。如果扩展,之前的数据会保留;如果缩小,超出部分会被释放。
  • 原内存的保留:如果 realloc 需要移动内存块(例如在当前内存空间不足的情况下),它会自动分配新内存并将旧内存的数据复制过去,原来的内存块会被释放。
  • 失败时不释放原内存:如果 realloc 失败,它不会释放原来的内存,程序可以继续使用原来的

除了这三点,我们还要注意一点,我们来看一下代码:

就是用原来的指针接收申请的空间

#include <stdio.h>
#include <stdlib.h>
int main()
{
	int* ptr = (int*)malloc(100);
	if (ptr != NULL)
	{
		//业务处理 
	}
	else
	{
		return 1;
	}
	//扩展容量 
	ptr = (int*)realloc(ptr, 1000);//这样可以吗?(如果申请失败会如何?) 
    free(ptr);
    return 0;
}

问题:

  • 如果 realloc 申请失败,会返回 NULL。此时,ptr 的原有地址(即之前分配的内存)将会被丢失,导致 内存泄漏。由于 ptr 被覆盖成 NULL,无法再释放之前申请的内存,也无法继续使用。
;