C语言修行之基础篇(二) 内存话题

本文介绍了程序的本质,强调了内存对程序的重要性。讲解了冯诺依曼结构和哈佛结构的区别,阐述了内存的用途、管理和分配方式,包括从操作系统和编程语言的角度。讨论了内存对齐、C语言中的内存地址封装以及栈和堆的区别。同时,提到了局部变量未初始化时的随机值现象以及堆内存的动态分配和释放。
摘要由CSDN通过智能技术生成


概述

内存是程序的立足之本。 就像我们立足之本是什么呢?是物联网?是网络游戏?当然是土地,我们生存必须要土地生存空间,总不能悬空生存是吧。回到程序,对于程序来说内存是非常重要的。所以作为一个程序员对内存的了解是非常有必要的。对内存了解越深刻,写出来的代码也就越优秀。现在就开启内存这个大话题来认识下内存。

程序的本质

在认识内存之前,先来聊聊程序是什么?程序为什么要不断的更新程序?
程序的目的是是为了去运行;程序的运行是为了得到一定的目的;为了得到新的目的,所以不断更新程序。
计算机用来计算的,这里的计算就是在计算数据。
计算机程序 = 代码 + 数据
代码(动作) + 数据(加工原材料) 经过运行后得到结果
函数形参就是代加工的数据(函数内部的需要临时变量,也就是局部变量)
函数本体就是代码
函数的返回值就是结果 (返回值 — 函数重在过程,返回函数值 — 函数重在结果)

函数的运行过程就是过程

计算机程序的运行过程,其实就是程序中很多函数相继运行的过程,程序是由很多个函数过程,函数本质就是加工数据的动作。

冯诺依曼结构和哈佛结构

冯诺依曼结构:数据和代码在一起
哈佛结构:数据和代码分开存放(放在不同的介质中)
这里的数据指的是:全局变量、局部变量

这里举例说明说明下两种结构:
Linux系统运行应用程序,这时代码和数据都在DRAM上 ———— 冯诺依曼结构
单片机中,把程序代码写到flash中,程序原地运行,程序中用到的数据不能放在flash(只读),要放在RAM(修改数据)中。 ———— 哈佛结构
(单片机将代码一句句读到CPU中运行)

为什么要内存?

内存是用来存储数据的,数据包含全局变量、局部变量等 。(在GCC中常量也是存储在flash中的)
内存对程序来说是本质的需求,越简单的程序需要的内存越少,而越庞大越复杂的程序(如:Linux系统)需要越大的内存。
跟数据有关的代码就和内存有密切的关联。
计算机内存越大,存放的数据就越多。但是内存管理不善的话,计算机会不断的消耗内存,最后会导致系统的内存不足而崩溃。所以内存管理就很重要,是操作计算机的一门技术。

内存如何管理?

从操作系统角度看

对于Linux操作系统中,分配大量的内存空间(假设4G)。Linux系统会将内存划分为一个一个页面(其中一块,一般4KB),然后以页面为单位管理。页面内用字节为单位管理。
有OS操作系统中:操作系统给使用者提供了申请/释放内存API接口。可以通过这些接口向操作系统申请/释放内存,操作系统会管理整个内存。
无OS操作系统中:在裸机中,程序需要直接操作内存空间,内存的使用和安排需要自己安排。
在汇编中:没有提供API接口,都是直接操作内存地址实现的

从编程语言角度看

在C中:编译器帮我们管理内存内存地址,程序通过编译器提供的变量名等来访问内存。如果需要大块的内存,可以用API去申请/释放(malloc/free)内存
在C++中:使用API中申请/释放(new/delect)内存空间
在Jave/C#中:不直接操作内存,而是通过虚拟机操作内存。如果在程序中申请内存没有释放掉,操作系统会自动帮你释放掉内存。
在这些语言中,程序对性能非常在乎的话,使用C/C++语言
对开发程序的速度非常在乎的话,使用Jave/C#等语言

什么是内存?

内存可以随机访问(随机访问的意思是指只要给一个内存地址就能访问该地址的空间),并且可以读写(也可以限制它只读或只写),内存在编程中是用来存放变量。

内存抽象图(例32位内存)

在这里插入图片描述
内存实际上有很多内存单元格组成的。每个单元格和一个地址关联,且永久绑定。
逻辑上来说,内存无限大。现实中,内存是有限制的。(32位系统最大内存4G,32位系统地址线32位对应的内存大小为2^32=4G),内存一般是小于等于4G

32/16/8位内存从逻辑角度来说是都可以实现数据(int类型)存储,但是实际上影响的是访问效率(硬件相关)
硬件上查看是几位的内存?原理图上查看数据线,若是16根数据线(Date0~Date15)则为16位内存

内存位宽

从逻辑上内存位宽是任意的,可以是24位。但是这种实际的硬件是买不到的。由于硬件的特性实现不了24位内存。

内存大小单位

位(bit):1bit
字节(Byte):8bit
半字:一般16bit 为字的一半
字:一般32bit
双字:字的2倍
字/半字/双字依赖于平台的(32位系统字为32bit)

内存的编制方法

在这里插入图片描述
内存在逻辑上就是一个个格子,可以用来装东西,每个格子有一个编号,这个编号就是内存的地址,这个内存地址和这个格子的空间是一一对应且永久绑定的。计算机只认识内存地址,不关心内存是如何分配的。地址和空间是内存单元的两面性
每个内存地址的空间大小一个字节(7bit)

内存和数据类型的关系

C语言中的数据类型:int、short、long、double、char
int整形(整体现在它和CPU数据位宽是一样的)在32位系统中,int为32位

数据类型和内存的关系在于:
数据类型是用于定义变量的,而这些变量需要存储、运算在内存中,所以数据类型必须和内存匹配才能获得最好的性能,否则可能不工作或者效率低下
注:在很多32位环境下,定义bool类型变量(实际只需要1bit就够了)都要用int来实现bool的(也就是系统自动给它分配了32位内存空间存储)

内存对齐

在这里插入图片描述
在C中定义一个int a,在内存就必须分配4个字节来存储这个a,分配方式有两种:
第一种:0、1 、2、 3 // 对齐访问
第二种:1、 2、 3、 4或者2、 3、 4、 5或者3、 4、 5、 6 // 非对齐访问
内存的对齐访问不是逻辑问题,是硬件的问题。32位系统里0、 1、 2、 3四个单元本身逻辑上就有相关性,这4个字节组合起来当做int类型硬件上合适的,效率高。

C语言对内存地址的封装

int a;         // 编译器帮我们申请了1个int类型的内存格子,长度4字节,并且a和格子地址(编译器内部分配)绑定
a = 5;         // 编译器发现a赋值5,会将5放到a这个格子里
a = a+4;       // 编译器发现要计算,会先将a读出来,加上4,把计算出来的值放入a格子里

抽象图
在这里插入图片描述
数据类型本质的含义是表示一个内存地址的长度和解析方法
(int *)0 (float *)0 (short)0 (int)0
数据类型决定内存地址:有一个内存地址(0x34000000),表示一个内存空间,给这个内存地址加上数据类型(int),内存空间加大为4个字节(0x34000000 + 0x34000001 + 0x34000002 + 0x34000003)
数据类型决定解析方法:有一个内存地址(0x34000000),通过给这个内存地址不同的类型来指定这个内存单元格子中二进制数的解析方法

C语言中函数是代码的封装,函数名的含义就是这段代码的首地址。

内存管理

内存管理之结构体

数据结构研究数据如何组织(在内存中排布),如何加工的学问。
为什么要有数组?
程序中有好多个类型相同、意义相关的变量需要管理。这时候如果用单独的变量来做程序看起来比较乱,用数组来管理。
**数组优势:**数组比较简单,访问用下标,可以随机访问。
数组缺陷: 1、数组中所有元素类型必须相同
2、数组大小必须定义时给出,而且一旦确定不能再改
结构体为了解决数组的第一个缺陷:数组中所有类型必须相同
在数据包中元素类型不同就只能用结构体而不能用数据。

// 数组定义
int age[5]
// 结构体定义
struct ages
{
    int age;
    char name[20];
};

结构体内数据类型相同的就是简化为数组。

数组为什么可以用下标访问?
数组存储抽象图
在这里插入图片描述
由于数组每个元素的大小是一样的,所以给age的地址可以算出age[1]、age[2]、age[3]、age[4]的地址
结构体每个成员的大小都不一样的,给第一个成员的地址无法算出第二个成员的地址。因此通过“.”进行访问,编译器会得到每个成员的地址

数据和结构体区别就在于在内存里面,成员的地址如何获取。
在C语言中结构体,如例子中所示,既有普通成员,又有相关的功能。整个集合就相当于C++中的一个class(一个类)。C语言面向过程与C++语言面向对象

struct s
{
    int a;
    short b;
    char name[20];
    void (*Init)(void);
};

内存管理之栈(堆栈)

C语言中使用栈保存局部变量。
在这里插入图片描述
在这里插入图片描述
栈存在两个指针一个top,一个buttom。固定buttom的指针,只能移动top。
刚开始top和buttom的指针指向同一个地址(说明这个栈是空的);
当要存储int a=6; 先将6存进内存里,后向上移动top指针(指向空的区域);
当要取出数据a 将TOP指针移动到a的起始地址,后取出数值6。
这就是栈的先进后出特性。(当存入6后再存入8,去数值时是先取出8后取6)
在这里插入图片描述
与栈向反的是,FIFO(队列),两端的指针没有固定。当存入6后再存入8,去数值时是先取出6后取8。

C语言中定义局部变量就是使用栈的方式

编译器会在栈中分配一段空间给这个变量使用(在分配空间的时候,栈顶指针会移动给出空间)。
注:这里的栈指针移动和内存分配都是自动实现的,不需要人为操作
当函数退出的时候,会释放变量所占用的栈空间。栈的操作是弹栈(出栈),这个也是移动TOP指针释放空间。这个过程也不需要人为去控制。
栈的优点: C语言中栈的操作是方便的。分配和最后的回收栈空间不需要程序员去操心。C语言自动完成。

为什么C语言中定义局部变量未初始化,使用时值为随机的?

定义局部变量,其实就是在栈中通过移动栈指针来给程序提供一个内核空间和这个局部变量名绑定。
这段内存空间在栈上,而栈是反复使用的。
出栈时只是把栈中的值拿出去值(相当于拷贝一份出去),这部分的空间不会清零,栈中原有的值还是上一次的值,等下一次使用这块栈的时候,没有初始化的话,还是取到这个值。所以栈是反复使用,栈是脏的。

**栈的缺点:**栈是有长度的。栈内存不灵活,大小不好设置。(如果栈太小怕溢出,栈太大浪费)
在C语言中定义局部变量时不能定义太大,如数组成员不能太大int a[1000000];

内存管理之堆

在这里插入图片描述
**堆的特点:**使用自由(随时申请、释放,大小可随意)。堆内存是操作系统划归给堆管理器管理(Linux中的一段代码,属于内存管理单元),向用户提供API接口(malloc,free等)
**什么时候使用堆?*用户使用内存比较大,需要反复使用和释放。(链表、结构体会使用堆管理内存)
堆内存的特点: 1、申请内存容量不限。2、申请malloc和释放free都需要程序员写代码手动实现。如果申请的内存没有释放,就会造成内存泄漏。
申请内存空间的函数:
malloc; // 直接传入要申请的空间大小 malloc(40); malloc(10
sizeof(int))
calloc; // 第一个参数为单元个数;第二参数每个单元size字节 calloc(10, sizeof(int));
realloc; // 修改已申请的内存空间大小

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Bazinga bingo

您的鼓励就是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值