020+limou+C语言内存管理

news2024/11/23 22:21:01

0.在Linux下验证C语言地址空间排布

这里是limou3434的博文系列。接下来,我会带您了解在C语言程序视角下的内存分布,会涉及到一点操作系统的知识,但是不多,您无需担忧。

注意:只能在Linux下验证,因为Windows的空间布局不是严格按照这种规则的。

另外如果您感兴趣的话,可以看看我的其他博客内容。
在这里插入图片描述

在Linux环境中输入下面的指令和代码来验证

$ vim test.c 
# ---------------- 
//在vim中的输入 
#include <stdio.h> 
#include <stdlib.h> 
int g_value_2;
int g_value_1 = 10; 
int main() 
{
   printf("code addr: %p\n", main);//<代码区>
   printf("\n");
   const char *str = "hello word!";//注意“hello word!”是存储在静态数据区(字符常量区)的,而str变量的空间开辟在栈上,但是str这个指针变量保存的是处于静态数据区内的“hello word!”里'h'的地址,故打印str就是打印静态数据区的地址
   printf("read only addr: %p\n", str);//<静态区>   printf("\n");
   printf("init g_value_1 global addr: %p\n", &g_value_1);//<已初始化全局变量区>
   printf("uninit g_value_2 global addr: %p\n", &g_value_2);//<未初始化全局变量区>
   printf("\n");
   int *p1 = (int*)malloc(sizeof(int) * 10);
   int *p2 = (int*)malloc(sizeof(int) * 10);
   printf("heap addr: %p\n", p1);//<堆区>
   printf("heap addr: %p\n", p2);
   printf("\n");
   printf("stack addr: %p\n", &str);//<栈区>
   printf("stack addr: %p\n", &p1);
   printf("stack addr: %p\n", &p2);
   printf("\n");
   free(p1);
   free(p2);
   return 0; 
} 
# ---------------- 
$ gcc test.c 
$ ./a.out 
code addr: 0x40060d 
  
read only addr: 0x4007ef 
  
init g_value_1 global addr: 0x60104c 
  
uninit g_value_2 global addr: 0x601054 
  
heap adder: 0x1e16010 
heap adder: 0x1e16040 
  
stack addr: 0x7ffeaaa87f98 
stack addr: 0x7ffeaaa87f90 
stack addr: 0x7ffeaaa87f88

可以看到在Linux中的确遵守这一顺序排布空间,并且栈和堆之间的有比较巨大的空间,并且的确是相向生长的。在申请堆空间的时候会大于预期申请空间,这部分多出来的空间是用来维护堆的,这在后面也有讲到。

另外如果在上面的代码中加入使用static修饰变量x,可以观察到这个变量x的地址和全局变量的地址很是接近,这也就证明了这个变量不是在栈上开辟的而是在全局数据区开辟的,所以作用域不变,而生命周期变成全局。

$ vim test.c 
# ---------------- //在vim中的输入 
#include <stdio.h> 
#include <stdlib.h> 
int g_value_2; 
int g_value_1 = 10; 
int main() 
{
   static int x = 100;
   printf("code addr: %p\n", main);//<代码区>
   printf("\n");
   const char *str = "hello word!";//注意“hello word!”是存储在静态数据区(字符常量区)的,而str变量的空间开辟在栈上,但是str这个指针变量保存的是处于静态数据区内的“hello word!”里'h'的地址,故打印str就是打印静态数据区的地址
   printf("read only addr: %p\n", str);//<静态区>
   printf("\n");   printf("init g_value_1 global addr: %p\n", &g_value_1);//<已初始化全局变量区>
   printf("uninit g_value_2 global addr: %p\n", &g_value_2);//<未初始化全局变量区>
   printf("\n");
   int *p1 = (int*)malloc(sizeof(int) * 10);
   int *p2 = (int*)malloc(sizeof(int) * 10);
   printf("heap addr: %p\n", p1);//<堆区>
   printf("heap addr: %p\n", p2);
   printf("\n");
   printf("stack addr: %p\n", &str);//<栈区>
   printf("stack addr: %p\n", &p1);
   printf("stack addr: %p\n", &p2);
   printf("\n");
   printf("%p", &x);
   free(p1);
   free(p2);
   return 0;
} 
# ---------------- 
$ gcc test.c 
$ ./a.out 
code addr: 0x40060d 
read only addr: 0x4007ff 
init g_value_1 global 
addr: 0x60104c
uninit g_value_2 global addr: 0x601058 
heap adder: 0x9b1010 
heap adder: 0x9b1040 
stack addr: 0x7ffd420b19c8 
stack addr: 0x7ffd420b19c0 
stack addr: 0x7ffd420b19b8 
0x601050

另外上面所讲的C程序地址空间概念并不是内存分布,但是想要搞清楚这其中的概念,就必须学习操作系统理论,所以这些就以后再来谈了(这已经不归在C语言的学习范畴了…)。

1.动态内存基础

1.1.malloc和free的使用

int* arr = (int*)malloc(sizeof(int) * 4);//申请内存
for(int i = 0; i < 4; i++)
{
	arr[i] = i;
}
for(int i = 0; i < 4; i++) 
{
  	printf("%d ", arr[i]);
}
free(arr);//释放内存

1.2.为什么需要动态内存

  1. 动态内存的大小:自动变量都是在栈空间里开辟的,但是栈空间是有限的,不如堆空间大,因此需要动态内存。
  2. 动态内存的灵活:很多情况下,程序员自己也不知道自己需要用到多少内存,而malloc只有在运行的时候被调用,这个时候才会申请空间,因此提供了很大的灵活性(在栈空间申请的内存由编译器决定,已经写“死”了,内存是确定的,比如“int arr[10]”就固定了数组大小为10,定义是简单了,但是不够灵活改变大小)。

2.野指针的概念

野指针就是,该指针变量指向了一个不该被访问的空间(因为该空间正在被使用)。

3.malloc申请的空间对应C的空间布局

malloc是在堆空间上申请内存的。

4.常见内存错误与对策

4.1.指针没有指向一块合法的内存

  • 结构体成员没有初始化
struct student
{
	char *name;
  	int score;
}str, *pstu;
int main()
{
	strcpy(str.name, "limou");//这里的指针name指向的是一个随机地址,stacpy的内部对其解引用了,访问了非法空间
  	str.score = 100;
}
  • 函数的入口处校验指针有效性
//野指针是没有办法校验的,因此所有指针如果没有被直接使用,就必须设置为NULL,这样子函数的入口参数的校验,就转化为指针是否为空的问题(空则不合法、非空则合法),这个时候就诞生了assert的用法
void function(int *p)
{
	assert(p);
 	printf("合法!\n"); 	
}
int main()
{
  	int *p = NULL;
  	function(p);
	return 0;
}
//而assert函数在判断指针为NULL的时候就会直接中断程序,一般是在调试代码的时候使用
//综上所述就是使用“编码规范”+“assert”来判断一个指针是否合法

4.2.没有为指针分配足够的内存

struct student 
{ 
  	char *name;
  	int score;
}str, *pstu; 
int main() 	
{ 
  	str.name = (struct student*)malloc(sizeof(struct student*));//错误的原因是没有申请好足够的空间,这里应该改成(struct student*)malloc(sizeof(struct student)) 
  	strcpy(str.name, "limou");
  	str.score = 100; 
  	return 0;
}

4.3.内存分配成功,但是没有初始化(不是大问题)

int* arr = (int*)malloc(sizeof(int) * 10)memset(arr, 0, sizeof(arr));

4.4.内存越界

内存越界有可能修改到正在使用的空间,导致程序奔溃,但是有的时候这是不会报错的,这是因为有可能越界访问到的内存并没有被控制使用,这个时候访问也没有太大的问题,但是终究是一个隐患。

例如下面这个例子就是一种越界访问

int main()
{ 
  	int *p = (int*)malloc(sizeof(int) * 5); 
  	int i = 0; 
  	for(i = 0; i < 5; i++) 	
    { 
      	p[i] = i;
    }
  	printf("%p\n", p);
  	ferr(p); 
  	printf("%p\n", p);//可以看到释放前和释放后p存储的依旧是申请时的地址,如果后面一不小心使用了p来解引用,就会造成非法访问
  	p = NULL;//置空,不让p成为野指针 
	return 0;
}

从上面的代码例子中可以看出释放动态内存的过程就像:面对已经分手(释放)的前任(动态内存),有些人总是会念念不忘(p依旧保存着指向之前开辟好动态内存的地址)。也就是说p变成了野指针(痴情汉?or痴情女?)因此free的作用就是改变指针和对用动态内存之间的对应关系(而这些“关系”也是需要靠数据去维护的)。而解决p是野指针的方法就是将p置空(p = NULL),编译其并不会直接帮助你处理掉这个野指针。

4.5.内存泄露

不断使用malloc申请空间而忘记释放空间,不断执行含有malloc的函数就会造成内存泄漏。但是如果程序退出了,则内存泄露的问题就会消失,这是因为操作系统进行了回收(不是编译器回收,一旦代码运行起来就和编译器没有关系了)。因此内存泄露最经典的现象就是,运行程序久了,内存空间被不断吃掉,导致变“卡”,而此时如果退出程序,操作系统就会进行自动回收。

void function(void)
{
	int* p = (int*)malloc(sizeof(int) * 1000);
}
int main()
{
	while(1)
	{
  		function();
	}
}

有些存在bug的杀毒软件一旦退出电脑就不卡了,有些服务器如果出现内存错误也会带来严重经济损失。

因此,这类常驻进程一旦加载到内存中就不会轻易退出是最怕内存泄露的。

4.6.重复释放内存

int main()
{
	int* p = (int*)malloc(int);
  	//某些代码使用了p
  	free(p);
  	//某些代码,但是忘记之前是否释放了
  	free(p);//回想起来使用了malloc但是没有想起自己早就释放了,结果再次释放,此时出现了问题
}

那么为什么会出错呢?这个就要先提到一个奇怪的现象:为什么free函数可以不需要知道malloc了多少空间就可以直接释放呢?这是因为maloc申请的空间实际上是超出我们的预期的,这些超出我们需要的空间用来维护这些堆空间,即:这部分空间就会存储本次申请的详细信息(比如申请了多大的空间),而free就可以利用这些空间里的信息来释放堆空间。

另外这部分多出来的内存也叫“内存cookie”,而若要再往后深入探究就涉及到操作系统的知识了,这超出了C语言的范畴,所以我们搁一边暂且不谈。

不过这个现象也能传递我们另外一个信息,使用malloc申请堆空间应该是申请较大空间的比较好,申请小空间的话cookie占比会比较多,因此小空间在栈上开辟就是最好的,而我们会发现栈和堆形成一种互补的关系。

5.体现动态内存管理的例子

那么通常书中的内存管理的“管理二字”体现在哪里,仅仅只是mallor和free么?

C语言的动态内存管理实际上就是:“1.空间什么时候申请2.申请多少空间3.什么时候释放4.释放多少”的问题,对比其他语言,比如Java语言这样的高级语言,其本身是自带内存管理的,程序员只需使用即可,这就让程序员使用起来更加得省心。

而C是偏底层得语言,相比Java来说更加自由,其动态内存管理是直接暴露给用户的,给程序员提供了更多的灵活性,但是也相应带来更多的安全隐患。

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

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

相关文章

外观模式(十三)

每天都是全新的一天&#xff0c;感谢今日努力的自己。 上一章简单介绍了组合模式(十二), 如果没有看过, 请观看上一章 一. 外观模式 引用 菜鸟教程里面的外观模式介绍: https://www.runoob.com/design-pattern/facade-pattern.html 外观模式&#xff08;Facade Pattern&…

商品编号篡改测试-业务安全测试实操(7)

商品编号篡改测试,邮箱和用户篡改测试 手机号码篡改测试-业务安全测试实操(6)_luozhonghua2000的博客-CSDN博客 邮箱和用户篡改测试 测试原理和方法 在发送邮件或站内消息时,篡改其中的发件人参数,导致攻击者可以伪造发信人进行钓鱼攻击等操作,这也是一种平行权限绕过漏洞…

2023年CPSM-3中级项目管理专业人员认证招生简章

CPSM-3中级项目管理专业人员认证&#xff0c;是中国标准化协会&#xff08;全国项目管理标准化技术委员会秘书处&#xff09;联合中国国际人才交流基金会&#xff0c;面向社会开展项目管理专业人员能力的等级证书。旨在构建多层次从业人员培养培训体系&#xff0c;建立健全人才…

软体机器人对工业应用的影响

原创 | 文 BFT机器人 软机器人模仿生物体的运动和动作&#xff0c;使它们具有高度的多功能性和迷人性。 软机器人领域正在迅速发展。它旨在为各种行业创造灵活的设备&#xff0c;包括医疗保健、太空探索、食品生产、地理、物流、康复、国防和家庭应用。 软机器人的独特之处在于…

windows下使用cmake编译c++

好久没有更新博客了 最近在做c相关的&#xff0c;编译起来确实很痛苦。 所以心血来潮&#xff0c;继续更新一下 主要还是一些跨平台的库&#xff0c;比如zlib、libpng、opencv、ffmpeg 编译工具使用mingw作为主要编译环境支持&#xff0c;使用msys进行编译。 一、下载mingw…

Python--输入和输出

Python--输入和输出 <font colorblue>一、输入&#xff1a;input()函数<font colorblue>二、输出&#xff1a;print()函数<font colorblue>1.print函数说明<font colorblue>2.格式化输出<font colorblue>方法一&#xff1a;使用占位符&#xff0…

MySQL哈希索引

介绍 建表时存储引擎选择 MEMORY&#xff0c;则创建索引就是哈希索引&#xff1a;如create index nameidx on student(name);哈希索引底层数据结构就是链式哈希表&#xff0c;链式就是指冲突时用链表法解决哈希表中的元素没有任何顺序可言&#xff0c;则只能进行等值比较。如果…

PinYin4j库的使用

一、PinYin4j库简介 1、PinYin4j简介 Pinyin4j 是一个流行的 Java 库&#xff0c;支持汉字和大多数流行的拼音系统之间的转换&#xff08;汉语拼音&#xff0c;罗马拼音等&#xff09;。可自定义拼音输出格式&#xff0c;功能强大。 官网地址&#xff1a;http://pinyin4j.sou…

道岔表示故障电路如何进行检查

一、分线盘区分提速道岔表示电路故障的方法 定位时可以通过测量X1、X2 (或者反位时X1、X3)端子间的交直流电压和BD1-7的3号端子上的电流&#xff0c;来判断表示电路的故障和范围。表示电路正常工作时&#xff0c;在分线盘端子X1、X2之间可以测到电压交流60V左右&#xff0c;直…

小白也能学会的电脑C盘空间释放技巧大集合

引言 电脑C盘快装满了怎么办&#xff1f;这是很多人使用电脑时面临的困扰。电脑的运行速度会变得很慢&#xff0c;甚至出现蓝屏等问题。那么&#xff0c;如何解决电脑C盘快装满的问题呢&#xff1f;接下来&#xff0c;本文将详细介绍解决电脑C盘快装满的几种方法。 先记录一下…

浪涌保护器的工作原理

浪涌保护器&#xff08;SPD&#xff09;旨在通过限制瞬态电压和转移浪涌电流来保护电气系统和设备免受浪涌事件的影响。 浪涌可能来自外部&#xff0c;最强烈的是雷击&#xff0c;也可能来自内部的电气负载切换。这些内部浪涌的来源占所有瞬变的65%&#xff0c;可能包括负载打…

CSS | CSS中height:100vh和height:100%的区别

目录 1、对于设置height:100%;有下面几种情况 2、对于设置height:100vh时有如下的情况 首先&#xff0c;我们得知道1vh它表示的是当前屏幕可见高度的1/100&#xff0c;而1%它表示的是父元素长或者宽的1% 1、对于设置height:100%;有下面几种情况 &#xff08;1&#xff09;当…

【备战秋招】每日一题:5月13日美团春招:题面+题目思路 + C++/python/js/Go/java带注释

2023大厂笔试模拟练习网站&#xff08;含题解&#xff09; www.codefun2000.com 最近我们一直在将收集到的各种大厂笔试的解题思路还原成题目并制作数据&#xff0c;挂载到我们的OJ上&#xff0c;供大家学习交流&#xff0c;体会笔试难度。现已录入200道互联网大厂模拟练习题&a…

macOS降级,从 Ventura 13.0至Monterey 12.6.1,适用于m芯片电脑

Mac资源&#xff1a;macw 准备工作&#xff1a; 请确保已经备份Mac上的重要资料&#xff0c;我当时将重要资料保存在了iCloud&#xff0c;你也可以将资料备份到其他地方&#xff0c;anyway。 将对应文件夹拖进iCloud Drive&#xff0c;等待其完成上传 准备一个外置存储器&am…

【机器学习】十大算法之一 “K-means”

作者主页&#xff1a;爱笑的男孩。的博客_CSDN博客-深度学习,活动,python领域博主爱笑的男孩。擅长深度学习,活动,python,等方面的知识,爱笑的男孩。关注算法,python,计算机视觉,图像处理,深度学习,pytorch,神经网络,opencv领域.https://blog.csdn.net/Code_and516?typeblog个…

第六章 彩色图像处理

第六章 彩色图像处理 6.1彩色基础 颜色特性&#xff1a; 亮度&#xff1a;表达了无色的强度概念色调&#xff1a;光波混合中与波长有关的属性&#xff08;即颜色&#xff09;饱和度&#xff1a;即相对纯净度&#xff0c;或一种颜色混合白光的数量。饱和度与所加白光成反比 …

tf卡和sd卡引脚定义和性能指标

sd卡和tf卡的引脚定义和引脚对应关系&#xff0c;见下图 tf卡的性能 选择tf卡时&#xff0c;我们主要是考虑下边几个性能&#xff0c;这几个性能一般都是可以直接看内存卡看出来的。 注&#xff1a;只介绍通过卡的标识来判断性能&#xff0c;卡的真假自己识别&#xff0c;不考…

踩坑笔记 Spring websocket并发发送消息异常

文章目录 示例代码WebSocketConfig配置代码握手拦截器代码业务处理器代码 问题复现原因分析解决方案方案一 加锁同步发送方案二 使用ConcurrentWebSocketSessionDecorator方案三 自研事件驱动队列&#xff08;借鉴 Tomcat&#xff09; 总结 今天刚刚经历了一个坑&#xff0c;非…

Linux系统基础知识与自学方法

大部分非计算机相关的朋友也经常使用电脑&#xff0c;所以我们频繁接触的是Windows系统。关于这个系统的评价不一&#xff0c;一部分人觉得简洁快捷&#xff0c;一部分人觉得问题&#xff08;病毒、弹窗&#xff09;多多&#xff0c;总之对Windows系统系统的评价参差不齐&#…

案例精述 | FortiEDR双活终端安全方案护航金融多云多分支场景

金融行业多云、多分支等特点&#xff0c;在数字化时代迎来更多安全挑战。尤其在勒索软件等威胁猖獗的大背景下&#xff0c;“安全运营”理念要求金融企业不仅要对威胁攻击“知其然”&#xff0c;还要“知其所以然”。因此&#xff0c;某金融企业希望提升端点安全防护&#xff0…