解密C语言内存分配奥秘,遨游动态内存管理海洋

news2024/11/23 23:20:08

一.C语言内存分区

C语言内存区从低地址到高地址分为代码区、常量区、全局(静态)区、堆区、栈区。

1.栈区 

栈区介绍

  • 栈区由编译器自动分配释放,由操作系统自动管理,无须手动管理
  • 栈区上的内容只在函数范围内存在,当函数运行结束,这些内容也会被自动销毁。
  • 栈区高地址向低地址使用,其大小在编译时确定,速度快,但自由性差,最大空间小。
  • 栈区先进后出原则,即先入栈的最后出栈;就如同进屋子,乌泱泱涌进去一堆人,最先进去的在里面,最后进去的在门口;先进去的人要等门口的人出去了才能出去。

栈区存放内容

  • 临时创建的局部变量const定义的局部变量
  • 函数调用和返回时,参数和返回值

2.堆区

堆区介绍

  • 堆区由程序员分配内存和释放。
  • 堆区低地址向高地址方向生长,其大小由系统内存/虚拟内存上限决定,速度较慢但自由行高,空间也大。

3.全局(静态)区

全局(静态)区介绍

  • 存储全局变量和静态变量
  • 全局段由.bss段和.data段组成

其中

  • .bss段存储未初始化或初始化为0的静态变量和全局变量,
  • .bss段不占用可执行文件的空间,内容由操作系统初始化
  • .data段存储已初始化的静态变量和全局变量
  • .data段占用可执行文件空间,内容由程序初始化。

4.常量区

  • 字符串,数字等常量存放在常量区
  • const修饰的全局变量存放在常量区
  • 在程序运行期间,常量区的内容不可更改

5.代码区

  • 我们写的代码存放在代码区,其内容不能修改
  • 字符串常量和define定义的常量也可能存放在代码区

二.动态内存管理

了解了C语言的内存分区,现在我们就来学习一下动态内存管理。动态内存函数在<stdlib.h>库中首先,为什么要有动态内存分配?

1.为什么要有动态内存分配

我们已经掌握的内存开辟方式是在栈上开辟的,而由上分可得知,这个东西会被销毁掉。而且这种开辟空间的方式还有两个特点

  • 空间开辟大小是固定的
  • 数组在声明的时候,必须指定数组的长度,数组空间无法修改。
	//在栈上开辟空间
	int a = 1;
	int arr[10];//在栈上开辟10个连续的空间

但是,我们对于空间的需求不仅仅是上述情况,有时候我们需要的空间在程序运行时才知道,拿数组在编译时开辟空间的方式就无法满足需求了。

于是,C语言引入了动态内存开辟,让程序员自己可以申请和释放空间。而动态内存开辟操作的内存空间是堆区。

2.malloc和free

C语言提供了一个动态内存开辟的函数:

	void* malloc(size_t size);

这个函数向内存申请一块连续可用的空间,空间大小为size_t size的大小,并返回指向这块空间的指针。

  • 如果开辟成功,则返回一个指向开辟好的空间的指针。
  • 如果开辟失败,则返回一个NULL指针,因此我们需要检查malloc的返回值
  • 返回值的类型由自己决定
  • 如果size为0,则malloc的行为是标准中未定义的,取决于编译器。

相对应的,C语言也提供了另外一个函数free,专用用来释放和回收动态开辟的内存。

函数原型如下:

	void* free(void* ptr);
  •  如果参数ptr指向的空间不是动态开辟的,则free函数的行为是未定义的
  • 如果参数ptr是NULL指针,则函数则什么事都不做。

现在我们使用一下这个函数

#include <stdio.h>
#include <stdlib.h>
int main()
{
	int* ptr = (int*)malloc(10 * sizeof(int));//开辟10个整型空间
	if (NULL != ptr)//判断动态内存有没有开辟成功
	{
		int i = 0;
		for (i; i < 10; i++)
		{
			*(ptr + i) = i;
		}
	}
	free(ptr);//释放ptr所指向的动态内存
	ptr = NULL;//及时置空,防止别人使用
	return 0;
}

3.calloc和realloc

3.1calloc函数

calloc函数的原型如下:

void* calloc(size_t num, size_t size);
  • 函数功能是为num个大小为size的元素开辟一块空间,并把这块空间的字节全部初始化为0。
  • 这个函数开辟的空间和malloc开辟的空间的区别在于这个函数可以将空间的字节初始化为0。

现在我们来使用一下这个函数 

所以如果我们要求对申请的空间内容初始化的话,使用calloc函数就会很方便。 

3.2realloc函数

在使用动态内存过程中,我们可能会发现我们过去申请的空间过大了或者过小了,从而导致我们的内存不够灵活,这时我们就需要对已经动态申请的内存进行一些调整,这时就需要我们的realloc函数。realloc函数的存在便是为了调整这块空间。

realloc函数的函数原型如下:

void* realloc (void* ptr, size_t size);
  • ptr是要调整的内存地址
  • size是调整之后的新大小
  • 返回值是调整之后的内存的起始位置
  •  这个函数在调整原内存空间大小的基础上,还会将原来的数据移动到新的内存空间内。
  • realloc在调整内存空间是存在两种情况的

             情况1:原有空间之后有足够大的空间。

             情况2:原有空间之后没有足够大的空间。

如果是情况1的话,直接在原有内存之后追加空间即可,原有的数据不会发生变化。

如果是情况2的话,原有空间之后没有足够多的空间时,编译器会在堆空间中另外找一个大小合适的连续空间来使用。这样函数返回的就是一个新的内存地址

代码出真知,实践一波。

#include <stdio.h>
#include <stdlib.h>
int main()
{
	int* arr = (int*)malloc(10 * sizeof(int));
	if (NULL == arr)
	{
		perror("开辟失败");
		return 1;
	}
	int* tmp = (int*)realloc(arr, sizeof(int) * 15);
	//使用tmp是为了防止内存开辟失败
	if (NULL == tmp)
	{
		perror("开辟失败");
		return 1;
	}
	arr = tmp;//把tmp给arr。
	return 0;
}

 可以看出,这时属于情况1。但是如果我们把realloc调整的空间从15改为100会怎么样呢?

	int* arr = (int*)malloc(10 * sizeof(int));
	int* tmp = (int*)realloc(arr, sizeof(int) * 100);

可以看出,这时内存找了另外一块空间来存放数据,也就是情况2. 

4.常见的动态内存的错误

4.1对NULL指针的解引用操作

这点很容易理解,指向空的指针自然不会有内容可以被提取出来。

void test1()
{
	int* p = (int*)malloc(sizeof(int));
	int* p = NULL;
	free(p);
}

4.2对动态开辟空间的越界访问

我们在对数组进行访问时知道,不可越界访问其内容;对于动态开辟的内存也同理。

void test2()
{
	int* p = (int*)malloc(10*sizeof(int));
	if (!p)
	{
		return -1;
	}
	for (int i = 0; i <= 10; i++)
	{
		*(p + i) = i;//当i等10时会越界访问。
	}
	free(p);
}

4.3对非动态开辟内存使用free释放

我们刚刚已经介绍了free是用来释放动态开辟的内存空间的,如果用来释放非动态开辟的内存空间自然会出现bug。

void test4()
{
	int* p = 10;
	free(p);
}

4.4使用free释放一块动态开辟内存的一部分

我们是可以通过修改指针指向的方式让我们创建的指针不再指向动态开辟的内存的首地址的。

修改之后的指针,再进行释放,就会导致动态开辟的内存释放不充分。

void test3()
{
	int* p = (int*)malloc(10*sizeof(int));
	p++;
	free(p);//此时没有完全释放动态开辟的内存空间
}

4.5对同一块动态内存多次释放

释放过一次了,再释放一次,不过是徒增代码量罢了。

void test5()
{
	int* p = (int*)malloc(10 * sizeof(int));
	free(p);
	free(p);//重复释放
}

4.6动态开辟内存忘记释放(内存泄漏)

如果我们动态开辟的内存使用完毕之后不释放的话,可能会导致内存泄漏。

内存泄漏即这块内存不能被程序再次使用,也不会被系统自动分配。从而导致了空间浪费。

因此特别重要的一点是,我们动态开辟的空间一定要记得free掉。

释放后还要记得置空!!!

6.柔性数组

6.1柔性数组简介

在C99标准中,结构中的最后一个元素允许是未知大小的数组的

例如:

typedef struct trousers
{
	int i;
	int arr[0];//柔性数组成员
}trousers;

这段代码或许会在某些编译器上报错,我们也可以这样写

typedef struct trousers
{
	int i;
	int arr[];//柔性数组成员
}trousers;

 6.2柔性数组的特点

柔性数组有以下特点

  • 结构中的柔性数组成员前面必须至少有一个其他成员。
  • sizeof返回的结构大小不包括柔性数组的内存,即使用内存对齐计算结构体大小时不会计算柔性数组成员的大小。
  • 包含柔性数组成员的结构体使用malloc函数进行内存的动态分配,并且分配的内存一定要大于结构体大小,以便于柔性数组有地方存。

6.3柔性数组的使用

typedef struct trousers
{
	int i;
	int arr[];//柔性数组成员
}trousers;
int main()
{
	trousers* p = (trousers*)malloc(sizeof(trousers) + 100 * sizeof(int));
	for (int i = 0; i < 100; i++)
	{
		p->arr[i] = i;
	}
    free(p);
    p=NULL;
	return 0;
}

这样,我们便给予了柔性数组100个数据。 

6.4柔性数组的优势

上一段代码,其实我们还可以这样设计

typedef struct trousers
{
	int i;
	int *p;
}trousers;
int main()
{
	trousers* pa = (trousers*)malloc(sizeof(trousers));
	pa->p = (int*)malloc(100 * sizeof(int));
	for (i = 0; i < 100; i++)
	{
		pa->p[i] = i;
	}
	free(pa->p);
    pa->p=NULL;
	free(pa);
	p = NULL;
	return 0;
}

 我们通过这样的方式也实现了柔性数组的效果,但是使用柔性数组是更有优势的,这是为什么呢?

好处1:方便内存释放和置空

如果我们的代码是在⼀个给别⼈⽤的函数中,你在⾥⾯做了⼆次内存分配,并把整个结构体返回给⽤⼾。用户调⽤free可以释放结构体,但是用户并不知道这个结构体内的成员也需要free,所以你不能指望⽤⼾来发现这个事。所以,如果我们把结构体的内存以及其成员要的内存⼀次性分配好了,并返回给⽤⼾⼀个结构体指针,⽤⼾做⼀次free就可以把所有的内存也给释放掉。

好处2:有利于提高访问速度

连续的内存地址有益于提高访问速度,也有益于减少内存碎片。
 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1633096.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【软件开发规范篇】JAVA后端开发编码命名规范

作者介绍&#xff1a;本人笔名姑苏老陈&#xff0c;从事JAVA开发工作十多年了&#xff0c;带过大学刚毕业的实习生&#xff0c;也带过技术团队。最近有个朋友的表弟&#xff0c;马上要大学毕业了&#xff0c;想从事JAVA开发工作&#xff0c;但不知道从何处入手。于是&#xff0…

阿里云服务器(Ubuntu22)上的MySQL8更改为大小写不敏感

因为windows上默认的mysql8.0是大小写不敏感的&#xff0c;部署到服务器上之后发现ubuntu默认的是大小写敏感&#xff0c;所以为了不更改代码&#xff0c;需要将mysql数据库设置为大小写不敏感的。 &#xff01;&#xff01;&#xff01;重要一定要做好数据库的备份&#xff0…

docker如何关闭证书认证

目录 前言关闭Docker认证的步骤修改pom 前言 当docker认证证书过期了&#xff0c;项目又要马上上线怎么办&#xff1f;重新生成证书&#xff0c;时间来不及&#xff0c;这时最快的方法就是关闭证书认证。 关闭Docker认证的步骤 停止Docker服务 systemctl stop docker编辑Do…

【C++】对文章分词,并对词频用不同排序方法排序,比较各排序算法效率(功能全面,通俗易懂)

文章分词 1&#xff0e;问题描述2&#xff0e;需求分析3&#xff0e;概要设计3.1 主程序流程3.2 函数调用关系 4&#xff0e;主函数实现4.1 main.h4.2 main.cpp 5. 函数实现5.1 processDic函数5.2 forwardMax函数5.3 countWordFreq函数5.4 quickResult函数5.5 其它排序算法效率…

异地组网、网络部署、无线覆盖,贝锐蒲公英一步到位

面对网络架构复杂的企业总部&#xff0c;分散在各地的分支机构&#xff0c;以及出差的远程办公人员&#xff0c;如何才能高效异地组网&#xff1f; 为了确保总部、分部网络实现远程稳定、高速互访&#xff0c;以及远程人员安全访问总部业务系统&#xff0c;基于自研SD-WAN的贝…

elementui el-date-picker禁止选择今年、今天、之前、时间范围限制18个月

1、禁止选择今年之前的所有年份 <el-date-pickerv-if"tabsActive 0":clearable"false"v-model"yearValue"change"yearTimeChange"type"year"placeholder"选择年"value-format"yyyy":picker-options…

手搓数组栈(C语言)

stack.h #pragma once#include <stdio.h> #include <stdlib.h> #include <assert.h> #include <stdbool.h> // 支持动态增长的栈 typedef int STDataType; typedef struct Stack {STDataType* a;int top; // 栈顶int capacity; // 容量 }Stack; //…

光伏储能系统的主要作用都有什么?

光伏储能系统&#xff0c;结合了光伏技术和储能技术&#xff0c;已经成为当今可再生能源领域的重要一环。它不仅在电力供应中扮演着关键角色&#xff0c;还在许多其他领域展现出其广泛的应用价值。本文将详细探讨光伏储能系统的主要作用。 首先&#xff0c;光伏储能系统在家庭住…

Java苍穹外卖03-Redis-营业状态-HttpClient-微信小程序开发-微信登录以及浏览

一、Redis入门 1.Redis简介 是对MySQL数据库的补充 2.下载安装 启动redis&#xff1a; 再开一个cmd&#xff1a;连接本地redis数据库 如果想连接其他地方的redis数据库&#xff1a;h为ip&#xff0c;p为端口 a为密码 3.数据类型 哈希适合存储对象&#xff0c;列表适合存储…

【问题实操】银河麒麟高级服务器操作系统实例,CPU软锁报错触发宕机

1.服务器环境以及配置 处理器&#xff1a; Kunpeng 920 内存&#xff1a; 256G DDR4 整机类型/架构&#xff1a; TaiShan 200 (Model 2280) 内核版本 4.19.90-23.8.v2101.ky10.aarch64 2.问题现象描述 两台搭载麒麟v10 sp1的机器均在系统CPU软锁报错时&#xff0c;触…

基于遗传优化算法的TSP问题求解matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 5.完整程序 1.程序功能描述 基于遗传优化算法的TSP问题求解&#xff0c;分别对四个不同的城市坐标进行路径搜索。 2.测试软件版本以及运行结果展示 MATLAB2022A版本运行 3.核心程序 ....…

迅睿CMS图集多文件Files调用指南

在构建企业网站、B2B/B2C商城&#xff0c;或任何功能性质以图片展示为主的平台时&#xff0c;使用多图或图集功能变得至关重要。特别是当展示大量产品的详细视图、项目案例图片&#xff0c;或任何需要以图集形式呈现的内容时&#xff0c;多文件Files功能便发挥着无可替代的作用…

Facebook的语言学:社交媒体如何影响我们的沟通方式

1. 引言 社交媒体已经成为人们日常生活中不可或缺的一部分&#xff0c;而Facebook作为其中最具影响力的平台之一&#xff0c;不仅改变了人们之间的社交方式&#xff0c;也对我们的语言学产生了深远的影响。本文将深入探讨Facebook的语言学特点&#xff0c;以及它如何塑造和改变…

AI大模型探索之路-训练篇4:大语言模型训练数据集概览

系列文章目录&#x1f6a9; AI大模型探索之路-训练篇1&#xff1a;大语言模型微调基础认知 AI大模型探索之路-训练篇2&#xff1a;大语言模型预训练基础认知 AI大模型探索之路-训练篇3&#xff1a;大语言模型全景解读 文章目录 系列文章目录&#x1f6a9;前言一、常用的预训练…

优秀的控制台厂家需要考虑哪些方面

在当今数字化时代&#xff0c;控制台已成为众多行业不可或缺的一部分。面对市场上众多的控制台厂家&#xff0c;如何选择合适的供应商成为了一个重要的问题。本文嘉德立将从需求分析、产品质量、价格、服务、口碑等多个方面&#xff0c;为您详细解析如何选择一家优秀的控制台厂…

珠宝品牌如何借助3D技术,成就营销破局之路?

随着电商的快速发展&#xff0c;一直以实体店销售为主的珠宝行业&#xff0c;也开始向线上销售转型&#xff0c;但珠宝作为客单价较高的商品&#xff0c;在线上销售的过程中&#xff0c;会面临图片展示效果不佳&#xff0c;无法亲身体验佩戴效果等问题。 而3D、VR、AR等技术的出…

数海启航:数学与人工智能的深度交织

在人类文明的长河中&#xff0c;数学始终扮演着探秘未知、构建理论框架的基石角色。随着科技的飞速发展&#xff0c;尤其是人工智能&#xff08;AI&#xff09;的兴起&#xff0c;数学与这一前沿领域的结合愈发紧密&#xff0c;成为推动AI进步的最强引擎。 一、数学&#xff1a…

20232801 2023-2024-2 《网络攻防实践》实践八报告

20232801 2023-2024-2 《网络攻防实践》实践八报告 1.实践内容 1.动手实践任务: 对提供的rada恶意代码样本&#xff0c;进行文件类型识别&#xff0c;脱壳与字符串提取&#xff0c;以获得rada恶意代码的编写作者. 2.动手实践任务二&#xff1a;分析Crackme程序 在WinXP Attac…

自定义一个RedisTemplate

1.引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>redis.clients</groupId><artifactId>jedis&…

动手学深度学习——矩阵

1. 基本概念 1.1 标量 标量由只有一个元素的张量表示。 所以标量计算与程度开发中的普通变量计算没有差异。 import torchx torch.tensor(3.0) y torch.tensor(2.0)x y, x * y, x / y, x**y(tensor(5.), tensor(6.), tensor(1.5000), tensor(9.))1.2 向量 向量泛化自标量…