C语言实现动态内存分配

 更新时间:2026年07月05日 08:37:15   作者:雨白  
本文主要介绍了C语言实现动态内存分配,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

栈区与堆区初探

C程序会对内存进行分区,主要分为5个区域:

  • 栈区(Stack)
  • 堆区(Heap)
  • 全局/静态区
  • 常量区(Constant)
  • 代码区

我们先主要了解前两个:

栈内存由编译器自动分配和释放,我们不需要操心。每调用一个函数,都会在栈区为该函数分配一块内存区域,这块区域就叫做函数栈帧。其中主要存放一些非静态的局部变量、函数参数等。

例如,下面代码中的函数形参 b、定义的局部变量 a 所用到的内存,都会由编译器自动开辟,开辟的方式是静态内存开辟。当 add 函数执行完毕返回时,对应的函数栈帧就会被销毁,自然这些占用的内存会被编译器自动回收。

int add(int b) {
	int a = 10;
	return a + b;
}

堆内存由我们程序员手动分配(malloccalloc)和释放(free)。

malloc 和 calloc 的区别在于:malloc 分配的内存存储的都是未初始化的随机垃圾值,而 calloc 会自动将分配的内存全部初始化为 0。

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

int main() {
	// 申请了32MB的内存
	int* arr = (int*) malloc(8 * 1024 * 1024 * sizeof(int)); // 返回值类型是void*,表示无类型指针,我们可以强转赋予它类型
	
	// 每次动态分配内存后,都要检查返回值是否为 NULL
	if (arr == NULL) {
		// 防止操作到空指针,导致程序崩溃
		printf("Memory allocation failed!\n");
		return -1; 
	}

	// 释放内存        
	free(arr);
	// 置空,防止野指针
	arr = NULL;
	
	return 0;
}

堆内存的开辟方式是动态内存开辟,这些内存不会自动回收,如果不手动回收,就会造成内存泄漏。

此外,栈空间通常很小(1MB),堆空间则很大,和系统可用的内存有关。

运行时决定内存大小

动态内存开辟的使用场景有很多:数据长度只在运行时才确定、栈空间不满足需求、需要延长变量的生命周期、内存大小需要动态改变等。

我们以第一种场景为例:运行时由用户输入决定人员的数量。

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

int main() {
	// 用户输入
	int num = 0;
	printf("Please enter the number of people.\n");
	scanf_s("%d", &num);

	// 开辟对应大小的空间
	int* arr = (int*)malloc(num * sizeof(int));
	
	// 检查内存是否开辟成功
	if (arr == NULL) {
		printf("Memory allocation failed!\n");
		return -1; // 退出程序
	}

	for (int i = 0; i < num; i++)
	{
		// 输入年龄
		int age = 0;
		printf("Please enter the age of the %d member at this position.\n", i + 1);
		scanf_s("%d", &age);
		arr[i] = age;
	}

	// 输出每个人的年龄
	for (int i = 0; i < num; i++)
	{
		printf("The age of the %d member is %d\n", i + 1, arr[i]);
	}

	// 释放并置空
	free(arr);
	arr = NULL;
	
	return 0;
}

scanf_s 是 Visual Studio 环境下特有的安全函数,在非 VS 环境中请使用 scanf。

运行结果:

realloc 的扩容机制与暗坑

再来看看第四种场景,普通的数组一旦定义后,长度就固定了,而动态内存的大小可以使用 realloc 进行重新调整,根据自己的需要扩容或缩容。

使用 realloc 进行扩容时,有两种情况:

原地扩容:如果原位置后有足够的连续内存空间,它会直接在原地址后追加空间,返回的地址和原地址相同。

异地扩容:如果原位置所需的连续地址空间不足,它会尝试在堆区找到一块合适的内存空间,将之前的数据拷贝到新位置,并自动释放之前的旧内存,最后返回指向这块新内存空间的指针。

因为异地扩容很常见,所以我们应该总是要使用新的指针去接收返回值。同时,如果发生后了异地扩容,原来的指针就变为了野指针,应该置为空。

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

int main() {
	// 初始可以存储8个整型
	int* p = (int*)malloc(8 * sizeof(int));
	if (p == NULL) {
		printf("Initial memory allocation failed.\n");
		return -1;
	}

	for (int i = 0; i < 8; i++)
	{
		p[i] = i + 1;
	}

	printf("Before capacity expansion\n");
	for (int i = 0; i < 8; i++)
	{
		printf("%d ", p[i]);
	}

	// 扩容至16
	printf("\nAfter capacity expansion\n");
	
	// 使用新指针变量接收,防止因扩容失败导致原内存地址 p 丢失
	int* new_p = (int*)realloc(p, 16 * sizeof(int));
	
	if (new_p == NULL) {
		printf("\nFailed to allocate memory for expansion.\n");
		// 扩容失败,旧内存 p 依然有效,程序结束前记得释放
		free(p);
		p = NULL;
		return -1;
	}

	// 扩容成功,原指针 p 可能已在异地扩容中被自动释放而失效,为防止误用,我们将其置空
	p = NULL; 

	for (int i = 8; i < 16; i++)
	{
		new_p[i] = i + 1;
	}
	for (int i = 0; i < 16; i++)
	{
		printf("%d ", new_p[i]);
	}

	// 此时由 new_p 管理这块空间,我们只需释放 new_p
	free(new_p);
	new_p = NULL;

	return 0;
}

如果 realloc 扩容失败,它将返回 NULL 空指针,但旧内存不会被释放,我们需要手动处理。

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

int main() {
	int* p = (int*)malloc(8 * sizeof(int));
	if (p == NULL) return -1;
	
	// 尝试申请一块非常大的内存,模拟失败的情况
	int* new_p = (int*)realloc(p, 8 * 1024LL * 1024 * 1024 * sizeof(int));

	if (new_p == NULL)
	{
		printf("Failed to allocate memory.\n");
		// 虽然申请新内存失败,但是旧内存块 p 依然存在,需要由我们手动释放
		free(p);
		p = NULL;
	}
	else 
	{
		printf("Success to allocate memory.\n");
		// 如果成功,释放新指针 new_p 即可
		free(new_p);
		new_p = NULL;
		p = NULL; // 置空防误用
	} 
	
	return 0;
}

注意:永远不要多次释放同一块内存,可能会导致程序崩溃。

到此这篇关于C语言实现动态内存分配的文章就介绍到这了,更多相关C语言 动态内存分配内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • C 语言基础教程(我的C之旅开始了)[四]

    C 语言基础教程(我的C之旅开始了)[四]

    C 语言基础教程(我的C之旅开始了)[四]...
    2007-02-02
  • C++ system()函数的常用用法(全网最新)

    C++ system()函数的常用用法(全网最新)

    system()用于从C /C++程序调用操作系统命令,这里给大家讲解下C++ system()函数的常用用法,感兴趣的朋友跟随小编一起看看吧
    2023-01-01
  • C语言实现学生选修课程系统设计

    C语言实现学生选修课程系统设计

    这篇文章主要为大家详细介绍了C语言实现学生选修课程系统设计,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-02-02
  • 学习 C++能带给我们什么

    学习 C++能带给我们什么

    这篇文章主要介绍了学习 C++能带给我们什么的相关总结,主要来自于前辈们,这里汇总给大家,需要的朋友可以参考下
    2016-03-03
  • C++初阶教程之类和对象

    C++初阶教程之类和对象

    C++是面向对象编程的,这也是C++与C语言的最大区别,而类和对象就是C++面向对象的基础,下面这篇文章主要给大家介绍了关于C++初阶教程之类和对象的相关资料,需要的朋友可以参考下
    2022-02-02
  • 利用Matlab实现时域分析功能的示例详解

    利用Matlab实现时域分析功能的示例详解

    利用MATLAB可以方便地进行控制系统的时域分析。这篇文章主要通过简单的示例为大家介绍了Matlab进行时域分析的具体操作,需要的可以参考一下
    2023-02-02
  • C语言关键字auto与register的深入理解

    C语言关键字auto与register的深入理解

    本篇文章是对c语言关键字auto与register的使用进行了详细的分析介绍,需要的朋友参考下
    2013-05-05
  • c++ 队列相关知识总结

    c++ 队列相关知识总结

    这篇文章主要介绍了c++ 队列相关知识总结,帮助大家更好的理解和学习使用c++,感兴趣的朋友可以了解下
    2021-03-03
  • 函数指针的强制类型转换实现代码

    函数指针的强制类型转换实现代码

    函数指针的强制类型转换实现代码。需要的朋友可以过来参考下,希望对大家有所帮助
    2013-10-10
  • QT QML的元素布局的实现

    QT QML的元素布局的实现

    这篇文章主要介绍了QT QML的元素布局的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-02-02

最新评论