C语言1之数据类型的本质

本文深入探讨了程序运行为何需要内存、内存管理方法、内存位宽与编址寻址、内存与数据类型的关系及数据类型的本质等内容,揭示了内存管理的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

时间:2018.1.26  作者:Tom   工作:HWE 说明:如需转载,请注明出处。 说明:如需转载,请注明出处。

1.程序的运行为什么需要内存呢?

 好多人都应该明白程序运行的目的就是计算结果,程序运行的过程就是计算方法。程序运行的本质就是使用函数将数据加工成结果。显而易见程序运行需要代码和数据。

 什么是代码:函数。

 什么是数据:全局变量和局部变量。

 那程序的运行为什么需要内存呢?听过最多的就是因为内存是存储变量的但是程序的运行为什么需要变量呢?我不需要内存程序就不能运行了吗?变量非要存储在内存上面吗?搞清楚上述问题就要从计算机的起源开始说起。

 首先软件是在硬件的基础上发展出来的。第一台电子计算机使用的都是电子管,当时是用改变线路连接的方法来编排程序的。我们知道PN节改变了世界(http://power.zol.com.cn/519/5191311_all.html):

                                       PN节------>二极管---------->三极管------->MOS管-------->运算放大器-------->数字电路的RS触发器------>D触发器------>计数器----->移位寄存器------>集成电路

随后出现了晶体管(二极管、三极管)、MOS管等一些电子元件的基本单元。然后出现运算放大器,数字电路的RS触发器、D触发器及其应用的计数器、移位寄存器等等。到此为止人们可以利用上述应用制作一些简单的硬件功能模块电路。

 那么问题来了,当时对于同一个计算单元,只能执行一条指令,比如一个计算公式1+2-4,运算器只有一个,1+2是一条指令,那计算完了怎么去自动计算3-4呢?有的人说直接做一个电路可以运行两个运算不就行了。这个说法是很有道理的,但你能无限制去加吗?这不现实!这个时候就需要一个存储3这个数字的地方,这就是我们所说的存储器,那怎样去控制这个加减的过程呢,这就需要一个控制器。整个过程就是冯诺依曼体系结构!!

 有一句话说冯诺依曼是计算机之父,而图灵是计算机科学之父,实际上就是说冯诺依曼把图灵的想法给实现了(https://www.zhihu.com/question/27883465),具体这里不再展开,我们只关注冯诺依曼计算机体系结构,其包括五个部分:输入设备、输出设备、运算器、控制器与存储设备。这里的存储设备其实就是我们所说的内存和外存。

 内存储器包括寄存器、高速缓冲存储器(Cache)和主存储器。寄存器在CPU芯片的内部,高速缓冲存储器目前也制作在CPU芯片内,而主存储器指插在主板内存插槽中的内存条。外部存储器有软盘、硬盘、光盘、U盘、磁带等,主要用来长期或永久存放程序和数据。内存储器和外存储器的区别是:内存储器速度快价格贵,容量小,用来存放当前正在执行的数据和程序,但仅用于暂时存放程序和数据,关闭电源或断电,数据会丢失。外存储器单位价格低,容量大,速度慢,断电后数据不会丢失,能长期保存信息。

 总结来说:内存是用来存储可变数据的,数据在程序中表现为全局变量、局部变量等(在gcc中,其实常量也是存储在内存中的)(大部分单片机中,常量是存储在flash中的,也就是在代码段),所以内存对程序来说几乎是本质需求。内存管理是一门学问,我们以前学过的了解过的很多编程的关键其实都是为了内存,譬如说数据结构(数据结构是研究数据如何组织的,数据是放在内存中的)和算法(算法是为了用更优秀更有效的方法来加工数据)。

 内存分为动态内存SRAM和静态内存DRAM。(1)首先都是RAM(随机存储器),又分为静态随机存储器和动态随机存储器。(2)SRAM优点:速度快,不需要刷新就能保存数据,功耗低,但是断电就不能保存数据了。 DRAM需要不停地刷新才能保存数据,切断电源也是会丢失数据的。SRAM速度快成本高主要用于二级高速缓存,DRAM成本相对较低,速度不如SRAM,一般主要用于DDR。

2.如何管理内存?

 写程序时如何管理内存就成了很大的问题。如果管理不善,可能会造成程序运行消耗过多的内存,这样迟早内存都被你这个程序吃光了,当没有内存可用时程序就会崩溃。所以内存对程序来说是一种资源,所以管理内存对程序来说是一个重要技术和话题。

 先从操作系统角度讲:操作系统掌握所有的硬件内存,因为内存很大,所以操作系统把内存分成1个1个的页面(其实就是一块,一般是4KB),然后以页面为单位来管理。页面内用更细小的方式来以字节为单位管理。操作系统内存管理的原理非常麻烦、非常复杂、非常不人性化。那么对我们这些使用操作系统的人来说,其实不需要了解这些细节。操作系统给我们提供了内存管理的一些接口,我们只需要用API(Application Programming Interface)即可管理内存。譬如在C语言中使用malloc,free这些接口来管理内存。

 没有操作系统时:在没有操作系统(其实就是裸机程序)中,程序需要直接操作内存,编程者需要自己计算内存的使用和安排。如果编程者不小心把内存用错了,错误结果需要自己承担。

 再从语言角度来讲:不同的语言提供了不同的操作内存的接口。

 譬如汇编:根本没有任何内存管理,内存管理全靠程序员自己,汇编中操作内存时直接使用内存地址(譬如0xd0020010),非常麻烦;

 譬如C语言:C语言中编译器帮我们管理直接内存地址,我们都是通过编译器提供的变量名等来访问内存的,操作系统下如果需要大块内存,可以通过API(malloc、free)来访问系统内存。裸机程序中需要大块的内存需要自己来定义数组等来解决。

 譬如C++语言:C++语言对内存的使用进一步封装。我们可以用new来创建对象(其实就是为对象分配内存),然后使用完了用delete来删除对象(其实就是释放内存)。所以C++语言对内存的管理比C要高级一些,容易一些。但是C++中内存的管理还是靠程序员自己来做。如果程序员new了一个对象,但是用完了忘记delete就会造成这个对象占用的内存不能释放,这就是内存泄漏

 Java/C#等语言:这些语言不直接操作内存,而是通过虚拟机来操作内存。这样虚拟机作为我们程序员的代理,来帮我们处理内存的释放工作。如果我的程序申请了内存,使用完成后忘记释放,则虚拟机会帮我释放掉这些内存。听起来似乎C# java等语言比C/C++有优势,但是其实他这个虚拟机回收内存是需要付出一定代价的,所以说语言没有好坏,只有适应不适应。当我们程序对性能非常在乎的时候(譬如操作系统内核)就会用C/C++语言;当我们对开发程序的速度非常在乎的时候,就会用Java/C#等语言。

3.内存位宽与编址寻址

 从硬件角度讲:硬件内存的实现本身是有宽度的,也就是说有些内存条就是8位的,而有些就是16位的。那么需要强调的是内存芯片之间是可以并联的,通过并联后即使8位的内存芯片也可以做出来16位或32位的硬件内存。

 从逻辑角度讲:内存位宽在逻辑上是任意的,甚至逻辑上存在内存位宽是24位的内存(但是实际上这种硬件是买不到的,也没有实际意义)。从逻辑角度来讲不管内存位宽是多少,我就直接操作即可,对我的操作不构成影响。但是因为你的操作不是纯逻辑而是需要硬件去执行的,所以不能为所欲为,所以我们实际的很多操作都是受限于硬件的特性的。譬如24位的内存逻辑上和32位的内存没有任何区别,但实际硬件都是32位的,都要按照32位硬件的特性和限制来干活。

 内存的编址是以字节为单位。在程序运行时,计算机中CPU实际只认识内存地址,而不关心这个地址所代表的空间在哪里,怎么分布这些实体问题。

因为硬件设计保证了按照这个地址就一定能找到这个格子,所以说内存单元的2个概念:地址和空间是内存单元的两个方面。

 内存的位宽与编址寻址的特性决定了C语言中与一切皆指针的概念。

4.内存与数据类型的关系

 C语言中的基本数据类型有:char     short    int      long     float      double

 int整形(整数类型,这个整就体现在它和CPU本身的数据位宽是一样的)譬如32位的CPU,整形就是32位,int就是32位。

 数据类型和内存的关系就在于:数据类型是用来定义变量的,而这些变量需要存储、运算在内存中。所以数据类型必须和内存相匹配才能获得最好的性能,否则可能不工作或者效率低下。

 在32位系统中定义变量最好用int,因为这样效率高。原因就在于32位的系统本身配合内存等也是32位,这样的硬件配置天生适合定义32位的int类型变量,效率最高。也能定义8位的char类型变量或者16位的short类型变量,但是实际上访问效率不高。

 在很多32位环境下,我们实际定义bool类型变量(实际只需要1个bit就够了)都是用int来实现bool的。也就是说我们定义一个bool b1;时,编译器实际帮我们分配了32位的内存来存储这个bool变量b1。编译器这么做实际上浪费了31位的内存,但是好处是效率高。

 问题:实际编程时要以省内存为大还是要以运行效率为重?答案是不定的,看具体情况。很多年前内存很贵机器上内存都很少,那时候写代码以省内存为主。现在随着半导体技术的发展内存变得很便宜了,现在的机器都是高配,不在乎省一点内存,而效率和用户体验变成了关键。所以现在写程序大部分都是以效率为重。

 我们在C中int a;定义一个int类型变量,在内存中就必须分配4个字节来存储这个a。有这么2种不同内存分配思路和策略:

 第一种:0 1 2 3                        对齐访问

 第二种:1 2 3 4    或者 2 3 4 5或者 3 4 5 6         非对齐访问

 内存的对齐访问不是逻辑的问题,是硬件的问题。从硬件角度来说,32位的内存它 0 1 2 3四个单元本身逻辑上就有相关性,这4个字节组合起来当作一个int硬件上就是合适的,效率就高。

 对齐访问很配合硬件,所以效率很高;非对齐访问因为和硬件本身不搭配,所以效率不高。(因为兼容性的问题,一般硬件也都提供非对齐访问,但是效率要低很多。)这一节可联系到结构体对齐。

5.数据类型的本质

 C语言操作内存的手法:对内存地址的封装(用变量名来访问内存、数据类型的含义、函数名的含义)

 C语言中数据类型的本质含义是:表示一个内存格子的长度和解析方法。

数据类型决定长度的含义:我们一个内存地址(0x30000000),本来这个地址只代表1个字节的长度,但是实际上我们可以通过给他一个类型(int),让他有了长度(4),这样这个代表内存地址的数字(0x30000000)就能表示从这个数字(0x30000000)开头的连续的n(4)个字节的内存格子了(0x30000000 + 0x30000001 + 0x30000002 + 0x30000003)。

数据类型决定解析方法的含义:譬如我有一个内存地址(0x30000000),我们可以通过给这个内存地址不同的类型来指定这个内存单元格子中二进制数的解析方法。譬如我 (int)0x30000000,含义就是(0x30000000 + 0x30000001 + 0x30000002 + 0x30000003)这4个字节连起来共同存储的是一个int型数据;那么我(float)0x30000000,含义就是(0x30000000 + 0x30000001 + 0x30000002 + 0x30000003)这4个字节连起来共同存储的是一个float型数据;

之前讲过一个很重要的概念:内存单元格子的编址单位是字节。

6.一切皆指针

 关于类型(不管是普通变量类型int float等,还是指针类型int * float *等),只要记住:类型只是对后面数字或者符号(代表的是内存地址)所表征的内存的一种长度规定和解析方法规定而已C语言中的指针,全名叫指针变量,指针变量其实跟普通变量没有任何区别。譬如int a和int *p其实没有任何区别,a和p都代表一个内存地址(譬如是0x20000000),但是这个内存地址(0x20000000)的长度和解析方法不同。a是int型所以a的长度是4字节,解析方法是按照int的规定来的;p是int *类型,所以长度是4字节,解析方法是按照int *的规定来的(0x20000000开头的连续4字节中存储了1个地址,这个地址所代表的内存单元中存放的是一个int类型的数)。

  C语言中,函数就是一段代码的封装。函数名的实质就是这一段代码的首地址。所以说函数名的本质也是一个内存地址

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值