C语言和其他高级语言的最大的区别是什么?

面包板社区 2021-06-05 13:27


提到C语言,我们知道c语言和其他高级语言的最大的区别就是C语言是要操作内存的!

 

我们需要知道——变量,其实是内存地址的一个抽像名字罢了。在静态编译的程序中,所有的变量名都会在编译时被转成内存地址。机器是不知道我们取的名字的,只知道地址。

 

内存的使用时程序设计中需要考虑的重要因素之一,这不仅由于系统内存是有限的(尤其在嵌入式系统中),而且内存分配也会直接影响到程序的效率。因此,我们要对C语言中的内存管理,有个系统的了解。

 

在C语言中,定义了4个内存区间:代码区;全局变量和静态变量区;局部变量区即栈区;动态存储区,即堆区;具体如下:

 

1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。


2、堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,呵呵。

3、全局区(静态区)(static)—全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的 另一块区域。- 程序结束后由系统释放。

4、常量区 —常量字符串就是放在这里的。程序结束后由系统释放

5、程序代码区—存放函数体的二进制代码。


我们来看张图:

 


首先我们要知道,源代码编译成程序,程序是放在硬盘上的,而非内存里!只有执行时才会被调用到内存中!


我们来看看程序结构,ELF是是Linux的主要可执行文件格式。ELF文件由4部分组成,分别是ELF头(ELF header)、程序头表(Program header table)、节(Section)和节头表(Section header table)。具体如下:


1、Program header描述的是一个段在文件中的位置、大小以及它被放进内存后所在的位置和大小。即要加载的信息;


2、Sections保存着object 文件的信息,从连接角度看:包括指令,数据,符号表,重定位信息等等。在图中,我们可以看到Sections中包括:

      (1)  .text   文本结 存放指令;

      (2)  .rodata   数据结  readonly;

      (3)  .data  数据结 可读可写; 


3、Section头表(section header table)包含了描述文件sections的信息。每个section在这个表中有一个入口;每个入口给出了该section的名字,大小,等等信息。相当于 索引!
     

而程序被加载到内存里面,又是如何分布的呢?我们看看上图中:


1、正文和初始化的数据和未初始化的数据就是我们所说的数据段,正文即代码段;


2、正文段上面是常量区,常量区上面是全局变量和静态变量区,二者占据的就是初始化的数据和未初始化的数据那部分;


3、再上面就是堆,动态存储区,这里是上增长;


4、堆上面是栈,存放的是局部变量,就是局部变量所在代码块执行完毕后,这块内存会被释放,这里栈区是下增长;


5、命令行参数就是$0 $1之类的,环境变量什么的前面的文章已经讲过,有兴趣的可以去看看。

 

我们知道,内存分为动态内存和静态内存,我们先讲静态内存。

一、静态内存


内存管理---存储模型


存储模型决定了一个变量的内存分配方式和访问特性,在C语言中主要有三个维度来决定:1、存储时期 2、作用域 3、链接


1、存储时期




存储时期:变量在内存中的保留时间(生命周期)


存储时期分为两种情况,关键是看变量在程序执行过程中会不会被系统自动回收掉。


 1)  静态存储时期 Static


在程序执行过程中一旦分配就不会被自动回收。


通常来说,任何不在函数级别代码块内定义的变量。


无论是否在代码块内,只要采用static关键字修饰的变量。


 2) 自动存储时期  Automatic


除了静态存储以外的变量都是自动存储时期的,或者说只要是在代码块内定义的非static的变量,系统会肚脐自动非配和释放内存;

 

2、作用域




作用域:一个变量在定义该变量的自身文件中的可见性(访问或者引用)


 在C语言中,一共有3中作用域:


1)  代码块作用域


在代码块中定义的变量都具有该代码的作用域。从这个变量定义地方开始,到这个代码块结束,该变量是可见的;


2)  函数原型作用域


出现在函数原型中的变量,都具有函数原型作用域,函数原型作用域从变量定义处一直到原型声明的末尾。


3)  文件作用域


一个在所有函数之外定义的变量具有文件作用域,具有文件作用域的变量从它的定义处到包含该定义的文件结尾处都是可见的;

 

3、链接




链接:一个变量在组成程序的所有文件中的可见性(访问或者引用);


C语言中一共有三种不同的链接:


1)  外部链接


如果一个变量在组成一个程序的所有文件中的任何位置都可以被访问,则称该变量支持外部链接;


2)  内部链接


如果一个变量只可以在定义其自身的文件中的任何位置被访问,则称该变量支持内部链接。


3)  空链接   


如果一个变量只是被定义其自身的当前代码块所私有,不能被程序的其他部分所访问,则成该变量支持空链接

 

我们来看一个代码示例:


[cpp] view plain copy

1. #include <stdio.h>  

2.   

3. int a = 0;// 全局初始化区    

4. char *p1; //全局未初始化区    

5.   

6. int main()    

7. {    

8. int b; //b在栈区  

9. char s[] = "abc"; //栈    

10. char *p2; //p2在栈区  

11.   

12. char *p3 = "123456"; //123456\0在常量区,p3在栈上。    

13. static int c =0; //全局(静态)初始化区  

14.     

15. p1 = (char *)malloc(10);    

16. p2 = (char *)malloc(20);  //分配得来得10和20字节的区域就在堆区。    

17.   

18. strcpy(p1, "123456"); //123456\0放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。    

19. }    

二、动态内存


当程序运行到需要一个动态分配的变量时,必须向系统申请取得堆中的一块所需大小的存储空间,用于存储该变量。当不在使用该变量时,也就是它的生命结束时,要显示释放它所占用的存储空间,这样系统就能对该空间 进行再次分配,做到重复使用有线的资源。下面介绍动态内存申请和释放的函数。


1.1 malloc 函数


 malloc函数原型:


[cpp] view plain copy

1. #include <stdio.h>  

2.   

3. void *malloc(size_t size);  


size是需要动态申请的内存的字节数。若申请成功,函数返回申请到的内存的起始地址,若申请失败,返回NULL。我们看下面这个例子:


[cpp] view plain copy

1. int *get_memory(int n)  

2. {  

3.     int *p;  

4.     p = (int *)malloc(sizeof(int));  

5.     if(p == NULL)  

6.     {  

7.         printf("malloc error\n");  

8.         return p;  

9.     }  

10.   

11.     memset(p,0,n*sizeof(int));  

12. }  


使用该函数时,有下面几点要注意:


1)只关心申请内存的大小;

2)申请的是一块连续的内存。记得一定要写出错判断;
3)显示初始化。即我们不知这块内存中有什么东西,要对其清零;

 

1.2 free函数


在堆上分配的额内存,需要用free函数显示释放,函数原型如下:


[cpp] view plain copy

#include <stdlib.h>  

void free(void *ptr); 

 

使用free(),也有下面几点要注意:


1)必须提供内存的起始地址;


调用该函数时,必须提供内存的起始地址,不能够提供部分地址,释放内存中的一部分是不允许的。


2)malloc和free配对使用;


编译器不负责动态内存的释放,需要程序员显示释放。因此,malloc与free是配对使用的,避免内存泄漏。


[cpp] view plain copy

free(p);  

p = NULL; 


p = NULL是必须的,因为虽然这块内存被释放了,但是p仍指向这块内存,避免下次对p的误操作;


3)不允许重复释放


因为这块内存被释放后,可能已另分配,这块区域被别人占用,如果再次释放,会造成数据丢失;

2、我们经常将堆和栈相比较:



2.1申请方式  


stack:  由系统自动分配。例如,声明在函数中一个局部变量 int b; 系统自动在栈中为b开辟空间  


heap:  需要程序员自己申请,并指明大小,在c中malloc函数 ,如p1 = (char *)malloc(10);  
  
2.2  申请后系统的响应  


栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。  


堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。 


2.3申请大小的限制  


栈:栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。 


堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。 


2.4申请效率的比较:  


栈由系统自动分配,速度较快。但程序员是无法控制的。  


堆是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便。 


2.5堆和栈中的存储内容  


栈: 在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。  当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。

 
堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容由程序员安排。 

2.6存取效率的比较 


char s1[] = "aaaaaaaaaaaaaaa";  
char *s2 = "bbbbbbbbbbbbbbbbb";  
aaaaaaaaaaa是在运行时刻赋值的;  
而bbbbbbbbbbb是在编译时就确定的;  
但是,在以后的存取中,在栈上的数组比指针所指向的字符串(例如堆)快。 

比如:  

[cpp] view plain copy

1. #include    

2. void main()    

3. {    

4. char a = 1;    

5. char c[] = "1234567890";    

6. char *p ="1234567890";    

7. a = c[1];    

8. a = p[1];    

9. return;    

10. }    

对应的汇编代码 

[cpp] view plain copy

1. 0: a = c[1];    

2. 00401067 8A 4D F1 mov cl,byte ptr [ebp-0Fh]    

3. 0040106A 88 4D FC mov byte ptr [ebp-4],cl    

4. 11: a = p[1];    

5. 0040106D 8B 55 EC mov edx,dword ptr [ebp-14h]    

6. 00401070 8A 42 01 mov al,byte ptr [edx+1]    

7. 00401073 88 45 FC mov byte ptr [ebp-4],al    


第一种在读取时直接就把字符串中的元素读到寄存器cl中,而第二种则要先把指针值读到edx中,再根据edx读取字符,显然慢了。 


总结

堆和栈的区别可以用如下的比喻来看出:  

栈就象我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是自由度小。  

堆就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由度大。


—END—

热门推荐:




调制的理解

超详细的电子元器件基础知识大全


PCB设计指南:安规、布局布线、EMC、热设计、工艺


频谱基础


C语言指针最详尽的讲解


一文看懂差分线



 内容合作 视频、课程合作 | 开发板合作转载开白 

 请联系小助手微信:15889572951(微信同号)


点击阅读原文,下载《C语言参考手册》

面包板社区 面包板社区——中国第一电子人社交平台 面包板社区是Aspencore旗下媒体,整合了电子工程专辑、电子技术设计、国际电子商情丰富资源。社区包括论坛、博客、问答,拥有超过250万注册用户,加入面包板社区,从菜鸟变大神,打造您的电子人脉社交圈!
评论
  • //```c #include "..\..\comm\AI8051U.h"  // 包含头文件,定义了硬件寄存器和常量 #include "stdio.h"              // 标准输入输出库 #include "intrins.h"         &n
    丙丁先生 2024-12-20 10:18 87浏览
  • 汽车行业的变革正愈演愈烈,由交通工具到“第三生活空间”。业内逐渐凝聚共识:汽车的下半场在于智能化。而智能化的核心在于集成先进的传感器,以实现高等级的智能驾驶乃至自动驾驶,以及更个性、舒适、交互体验更优的智能座舱。毕马威中国《聚焦电动化下半场 智能座舱白皮书》数据指出,2026年中国智能座舱市场规模将达到2127亿元,5年复合增长率超过17%。2022年到2026年,智能座舱渗透率将从59%上升至82%。近日,在SENSOR CHINA与琻捷电子联合举办的“汽车传感系列交流会-智能传感专场”上,艾
    艾迈斯欧司朗 2024-12-20 19:45 137浏览
  • 随着工业自动化和智能化的发展,电机控制系统正向更高精度、更快响应和更高稳定性的方向发展。高速光耦作为一种电气隔离与信号传输的核心器件,在现代电机控制中扮演着至关重要的角色。本文将详细介绍高速光耦在电机控制中的应用优势及其在实际工控系统中的重要性。高速光耦的基本原理及优势高速光耦是一种光电耦合器件,通过光信号传递电信号,实现输入输出端的电气隔离。这种隔离可以有效保护电路免受高压、电流浪涌等干扰。相比传统的光耦,高速光耦具备更快的响应速度,通常可以达到几百纳秒到几微秒级别的传输延迟。电气隔离:高速光
    晶台光耦 2024-12-20 10:18 155浏览
  • 光耦固态继电器(SSR)作为现代电子控制系统中不可或缺的关键组件,正逐步取代传统机械继电器。通过利用光耦合技术,SSR不仅能够提供更高的可靠性,还能适应更加复杂和严苛的应用环境。在本文中,我们将深入探讨光耦固态继电器的工作原理、优势、挑战以及未来发展趋势。光耦固态继电器:如何工作并打破传统继电器的局限?光耦固态继电器通过光电隔离技术,实现输入信号与负载之间的电气隔离。其工作原理包括三个关键步骤:光激活:LED接收输入电流并发出与其成比例的光信号。光传输:光电传感器(如光电二极管或光电晶体管)接收
    腾恩科技-彭工 2024-12-20 16:30 69浏览
  • 百佳泰特为您整理2024年12月各大Logo的最新规格信息。——————————USB▶ 百佳泰获授权进行 USB Active Cable 认证。▶ 所有符合 USB PD 3.2 标准的产品都有资格获得USB-IF 认证——————————Bluetooth®▶ Remote UPF Testing针对所有低功耗音频(LE Audio)和网格(Mesh)规范的远程互操作性测试已开放,蓝牙会员可使用该测试,这是随时测试产品的又一绝佳途径。——————————PCI Express▶ 2025年
    百佳泰测试实验室 2024-12-20 10:33 127浏览
  • 光耦合器,也称为光隔离器,是用于电气隔离和信号传输的多功能组件。其应用之一是测量电路中的电压。本文介绍了如何利用光耦合器进行电压测量,阐明了其操作和实际用途。使用光耦合器进行电压测量的工作原理使用光耦合器进行电压测量依赖于其在通过光传输信号的同时隔离输入和输出电路的能力。该过程包括:连接到电压源光耦合器连接在电压源上。输入电压施加到光耦合器的LED,LED发出的光与施加的电压成比例。光电二极管响应LED发出的光由输出侧的光电二极管或光电晶体管检测。随着LED亮度的变化,光电二极管的电阻相应减小,
    腾恩科技-彭工 2024-12-20 16:31 90浏览
  • Supernode与艾迈斯欧司朗携手,通过Belago红外LED实现精准扫地机器人避障;得益于Belago出色的红外补光功能,使扫地机器人能够大大提升其识别物体的能力,实现精准避障;Belago点阵照明器采用迷你封装,兼容标准无铅回流工艺,适用于各种3D传感平台,包括移动设备、物联网设备和机器人。全球领先的光学解决方案供应商艾迈斯欧司朗(瑞士证券交易所股票代码:AMS)近日宣布,与国内领先的多行业三维视觉方案提供商超节点创新科技(Supernode)双方联合推出采用艾迈斯欧司朗先进Belago红
    艾迈斯欧司朗 2024-12-20 18:55 97浏览
  • ALINX 正式发布 AMD Virtex UltraScale+ 系列 FPGA PCIe 3.0 综合开发平台 AXVU13P!这款搭载 AMD 16nm 工艺 XCVU13P 芯片的高性能开发验证平台,凭借卓越的计算能力和灵活的扩展性,专为应对复杂应用场景和高带宽需求而设计,助力技术开发者加速产品创新与部署。随着 5G、人工智能和高性能计算等领域的迅猛发展,各行业对计算能力、灵活性和高速数据传输的需求持续攀升。FPGA 凭借其高度可编程性和实时并行处理能力,已成为解决行业痛点的关
    ALINX 2024-12-20 17:44 98浏览
  • 耳机虽看似一个简单的设备,但不仅只是听音乐功能,它已经成为日常生活和专业领域中不可或缺的一部分。从个人娱乐到专业录音,再到公共和私人通讯,耳机的使用无处不在。使用高质量的耳机不仅可以提供优良的声音体验,还能在长时间使用中保护使用者听力健康。耳机产品的质量,除了验证产品是否符合法规标准,也能透过全面性的测试和认证过程,确保耳机在各方面:从音质到耐用性,再到用户舒适度,都能达到或超越行业标准。这不仅保护了消费者的投资,也提升了该公司在整个行业的产品质量和信誉!客户面临到的各种困难一家耳机制造商想要透
    百佳泰测试实验室 2024-12-20 10:37 175浏览
  • 国产数字隔离器已成为现代电子产品中的关键部件,以增强的性能和可靠性取代了传统的光耦合器。这些隔离器广泛应用于医疗设备、汽车电子、工业自动化和其他需要强大信号隔离的领域。准确测试这些设备是确保其质量和性能的基本步骤。如何测试数字隔离器测试数字隔离器需要精度和正确的工具集来评估其在各种条件下的功能和性能。以下设备对于这项任务至关重要:示波器:用于可视化信号波形并测量时序特性,如传播延迟、上升时间和下降时间。允许验证输入输出信号的完整性。频谱分析仪:测量电磁干扰(EMI)和其他频域特性。有助于识别信号
    克里雅半导体科技 2024-12-20 16:35 80浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦