uml+oopc嵌入式c语言开发精讲_嵌入式开发中更接近底层的汇编与C语言

    分享本文,介绍下更接近硬件底层的C语言与汇编,解释 CPU 如何执行代码。

高级语言与低级语言

    学习编程其实就是学习与计算机交流的语言。因为计算机不理解人类语言,通过编译器把人类写的代码转成二进制代码,才能在机器上运行。掌握了高级语言,并不等于理解计算机实际的运行步骤,还需要对C语言甚至是汇编有所了解才行。编程语言从低级到高级,如下图所示。其中,Assembly Language也就是我们说的汇编,在机器语言Machine Language与高级语言之间。

2bd747393fa30c1a252066a05d09c2b9.png

    然而,计算机只能理解低级语言,它专门用来控制硬件。

    汇编语言就是一种低级语言,直接描述或者控制CPU的运行。通过学习汇编语言,可以了解CPU到底干了些什么。

    汇编语言不容易学习,大多数的嵌入式开发用C语言就能做得很好。用C语言开发效率更高,程序运行效率并不会大打折扣。为什么还要学习汇编呢?权当是为了更接近真相吧!

汇编语言怎么来的

    作为智能设备核心的CPU只负责计算,本身不具备智能,只会按照指令要求去执行相应动作。

    这些指令都是二进制的,称为操作码(opcode),比如加法指令就是00000011。编译器的作用,就是将高级语言写好的程序,翻译成一条条操作码。

    最早的时候,编写程序就是手写二进制指令,程序就是一串0或1。据说在上世纪,世界上只有为数不多的天才可以做到。写完一连串01程序之后,通过各种开关输入计算机,比如要做加法了,就按一下加法开关。后来,发明了纸带打孔机,通过在纸带上打孔,将二进制指令自动输入计算机。如下图,就可能是一段计算机指令。

a52a7527d644673ba1ba09a5c168d541.png

    但是,这种反人类的二进制程序难以理解,可读性极差,换人来维护基本上等于从头再来!根本看不出来机器干了什么。为了解决可读性的问题,以及偶尔的编辑需求,汇编语言应运而生。

    早期,为了解决二进制指令的可读性问题,工程师曾经将那些二进制指令写成了八进制,但是八进制的可读性也不行。很自然地,最后还是用文字表达。汇编语言是二进制指令的文本形式,与指令是一一对应的关系。比如,加法指令00000011写成汇编语言就是 ADD。内存地址也不再直接引用,而是用标签表示。

    把这些文字指令翻译成二进制,这个步骤就称为汇编assembling,完成这个步骤的程序就叫做汇编器assembler。它处理的文本,标准化以后称为汇编语言Assembly Language,缩写为asm,文件名后缀为s。 

寄存器与内存模型

寄存器

    每一种CPU 的机器指令都是不一样的,因此对应的汇编语言也不一样。本文介绍的是最常见的Intel 公司CPU使用的那种x86汇编语言。

    学习汇编语言要熟悉两个知识点:寄存器和内存模型。先来说一下寄存器。

    CPU本身只负责运算,不负责储存数据。数据一般都储存在内存之中,CPU要用的时候就去内存读写数据。但是,CPU的运算速度远高于内存的读写速度,为了避免被拖慢,CPU都自带一级缓存和二级缓存。基本上,CPU缓存可以看作是读写速度较快的内存。

    由于CPU缓存还是不够快,另外数据在缓存里面的地址是不固定的,CPU每次读写都要寻址也会拖慢速度。因此,除了缓存之外,CPU还自带了寄存器(register),用来储存最常用的数据。也就是说,像循环变量那种最频繁读写的数据都会放在寄存器里面,CPU优先读写寄存器,再由寄存器跟内存交换数据。如下图,按速度排序,从上到下依次降低。4110b7e542adac5c4d4ee77e1ee0f949.png

    寄存器不依靠地址区分数据,而依靠名称。每一个寄存器都有自己的名称,我们告诉CPU去具体的哪一个寄存器拿数据,这样的速度是最快的。有人比喻寄存器是CPU的零级缓存。

    早期的x86CPU只有8个寄存器,而且每个都有不同的用途。现在的寄存器已经有100多个了,都变成通用寄存器,不特别指定用途了,但是早期寄存器的名字都被保存了下来。

EAXEBXECXEDXEDIESIEBPESP

    上面这8个寄存器之中,前面七个都是通用的。ESP 寄存器有特定用途,保存当前 Stack 的地址。

59be3376345cfe8f5d6ec1a1fa4b9437.png

    常常看到的32位 CPU、64位 CPU 这样的名称,其实指的就是寄存器的大小。32 位 CPU 的寄存器大小就是4个字节。

内存模型:Heap

    寄存器只能存放很少量的数据,大多数时候,CPU 要指挥寄存器,直接跟内存交换数据。所以,除了寄存器,还必须了解内存怎么储存数据。程序运行的时候,操作系统会给它分配一段内存,用来储存程序和运行产生的数据。这段内存有起始地址和结束地址,比如从0x1000到0x8000,起始地址是较小的那个地址,结束地址是较大的那个地址。

cd923895bcf9fe97a7e7158d7b31cd2a.png

    程序运行过程中,对于动态的内存占用请求(比如新建对象,或者使用malloc命令),系统就会从预先分配好的那段内存之中,划出一部分给用户,具体规则是从起始地址开始划分(实际上,起始地址会有一段静态数据,这里忽略)。举例来说,用户要求得到10个字节内存,那么从起始地址0x1000开始给他分配,一直分配到地址0x100A,如果再要求得到22个字节,那么就分配到0x1020。

fa81a6f40105cb87cac77d5e71758b69.png

    这种因为用户主动请求而划分出来的内存区域,叫做 Heap(堆)。它由起始地址开始,从低位(地址)向高位(地址)增长。Heap 的一个重要特点就是不会自动消失,必须手动释放,或者由垃圾回收机制来回收。

内存模型:Stack

    除了 Heap 以外,其他的内存占用叫做 Stack(栈)。简单说,Stack 是由于函数运行而临时占用的内存区域。

ffdd013a9974720002557af4ada3d82e.png

    请看下面的例子。

int main() {   int a = 2;   int b = 3;}

    上面代码中,系统开始执行main函数时,会为它在内存里面建立一个帧(frame),所有main的内部变量(比如a和b)都保存在这个帧里面。main函数执行结束后,该帧就会被回收,释放所有的内部变量,不再占用空间。

cd994b5825775515bea34cda5e88e5f7.png

    如果函数内部调用了其他函数,会发生什么情况?

int main() {   int a = 2;   int b = 3;   return add_a_and_b(a, b);}

    上面代码中,main函数内部调用了add_a_and_b函数。执行到这一行的时候,系统也会为add_a_and_b新建一个帧,用来储存它的内部变量。也就是说,此时同时存在两个帧:main和add_a_and_b。一般来说,调用栈有多少层,就有多少帧。

2d3b43173d30d9b0a61062342795475f.png

    等到add_a_and_b运行结束,它的帧就会被回收,系统会回到函数main刚才中断执行的地方,继续往下执行。通过这种机制,就实现了函数的层层调用,并且每一层都能使用自己的本地变量。所有的帧都存放在 Stack,由于帧是一层层叠加的,所以 Stack 叫做栈。生成新的帧,叫做"入栈",英文是 push;栈的回收叫做"出栈",英文是 pop。Stack 的特点就是,最晚入栈的帧最早出栈(因为最内层的函数调用,最先结束运行),这就叫做"后进先出"的数据结构。每一次函数执行结束,就自动释放一个帧,所有函数执行结束,整个 Stack 就都释放了。

959a6c32793455de03f273f9e2f60a8e.png

df81907f3665ff609dd6db27e81adceb.png

    Stack 是由内存区域的结束地址开始,从高位(地址)向低位(地址)分配。比如,内存区域的结束地址是0x8000,第一帧假定是16字节,那么下一次分配的地址就会从0x7FF0开始;第二帧假定需要64字节,那么地址就会移动到0x7FB0。

7a3de5d31332fb56579bb0e224f2e965.png

CPU 指令

一个实例

    了解寄存器和内存模型以后,就可以来看汇编语言到底是什么了。下面是一个简单的程序example.c。

int add_a_and_b(int a, int b) {   return a + b;}int main() {   return add_a_and_b(2, 3);}

    gcc 将这个程序转成汇编语言。

$ gcc -S example.c

    上面的命令执行以后,会生成一个文本文件example.s,里面就是汇编语言,包含了几十行指令。这么说吧,一个高级语言的简单操作,底层可能由几个,甚至几十个 CPU 指令构成。CPU 依次执行这些指令,完成这一步操作。

    example.s经过简化以后,大概是下面的样子。

_add_a_and_b:   push   %ebx   mov    %eax, [%esp+8]    mov    %ebx, [%esp+12]   add    %eax, %ebx    pop    %ebx    ret  _main:   push   3   push   2   call   _add_a_and_b    add    %esp, 8   ret

    可以看到,原程序的两个函数add_a_and_b和main,对应两个标签_add_a_and_b和_main。每个标签里面是该函数所转成的 CPU 运行流程。

    每一行就是 CPU 执行的一次操作。它又分成两部分,就以其中一行为例。

push   %ebx

    这一行里面,push是 CPU 指令,%ebx是该指令要用到的运算子。一个 CPU 指令可以有零个到多个运算子。下面我就一行一行讲解这个汇编程序,建议读者最好把这个程序,在另一个窗口拷贝一份,省得阅读的时候再把页面滚动上来。

push指令

    程序从_main标签开始执行,这时会在 Stack 上为main建立一个帧,并将 Stack 所指向的地址,写入 ESP 寄存器。后面如果有数据要写入main这个帧,就会写在 ESP 寄存器所保存的地址。然后,开始执行第一行代码。

push 3

    push指令用于将运算子放入 Stack,这里就是将3写入main这个帧。

    虽然看上去很简单,push指令其实有一个前置操作。它会先取出 ESP 寄存器里面的地址,将其减去4个字节,然后将新地址写入 ESP 寄存器。使用减法是因为 Stack 从高位向低位发展,4个字节则是因为3的类型是int,占用4个字节。得到新地址以后, 3 就会写入这个地址开始的四个字节。

push 2

    第二行也是一样,push指令将2写入main这个帧,位置紧贴着前面写入的3。这时,ESP 寄存器会再减去 4个字节(累计减去8)。

e1e82a2c6955ef94634c38d7274d3dd1.png

call指令

    第三行的call指令用来调用函数。

call _add_a_and_b

    上面的代码表示调用add_a_and_b函数。这时,程序就会去找_add_a_and_b标签,并为该函数建立一个新的帧。下面就开始执行_add_a_and_b的代码。

push %ebx

    这一行表示将 EBX 寄存器里面的值,写入_add_a_and_b这个帧。这是因为后面要用到这个寄存器,就先把里面的值取出来,用完后再写回去。这时,push指令会再将 ESP 寄存器里面的地址减去4个字节(累计减去12)。

mov指令

    mov指令用于将一个值写入某个寄存器。

mov    %eax, [%esp+8]

    这一行代码表示,先将 ESP 寄存器里面的地址加上8个字节,得到一个新的地址,然后按照这个地址在 Stack 取出数据。根据前面的步骤,可以推算出这里取出的是2,再将2写入 EAX 寄存器。下一行代码也是干同样的事情。

mov    %ebx, [%esp+12]

    上面的代码将 ESP 寄存器的值加12个字节,再按照这个地址在 Stack 取出数据,这次取出的是3,将其写入 EBX 寄存器。

add指令

    add指令用于将两个运算子相加,并将结果写入第一个运算子。

add    %eax, %ebx

    上面的代码将 EAX 寄存器的值(即2)加上 EBX 寄存器的值(即3),得到结果5,再将这个结果写入第一个运算子 EAX 寄存器。

pop指令

    pop指令用于取出 Stack 最近一个写入的值(即最低位地址的值),并将这个值写入运算子指定的位置。

pop    %ebx

    上面的代码表示,取出 Stack 最近写入的值(即 EBX 寄存器的原始值),再将这个值写回 EBX 寄存器(因为加法已经做完了,EBX 寄存器用不到了)。

注意,pop指令还会将 ESP 寄存器里面的地址加4,即回收4个字节。

ret指令

    ret指令用于终止当前函数的执行,将运行权交还给上层函数。也就是,当前函数的帧将被回收。如下,可以看到,该指令没有运算子。

ret

    随着add_a_and_b函数终止执行,系统就回到刚才main函数中断的地方,继续往下执行。

add    %esp, 8 

    上面的代码表示,将 ESP 寄存器里面的地址,手动加上8个字节,再写回 ESP 寄存器。这是因为 ESP 寄存器的是 Stack 的写入开始地址,前面的pop操作已经回收了4个字节,这里再回收8个字节,等于全部回收。

ret

    最后,main函数运行结束,执行ret指令退出程序的执行。

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

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

相关文章

delphi xe http 收不到反馈消息_好消息!浦东这个小区3幢多层住宅都要加装电梯啦→...

原标题:好消息!浦东这个小区3幢多层住宅都要加装电梯啦→好消息!东明路街道这个小区要加装电梯,还是3个!规划设计方案公示中快和小布一起看看吧!▽环林东路879弄加装电梯项目建设单位:上海市浦东…

php调用美图接口,网易美图 API 接口调用与请求方法详细教程

原标题:网易美图 API 接口调用与请求方法详细教程网易美图 API 接口在网上已经很多且大都封装成了 API 供别人调用。支持前台跨域请求,以POST方式提交图片即可。网易美图 API 接口会随机更新一张美图,你可以选择调用本站的接口,也…

nlp 财务提取_RPA,智慧财务时代的“珍妮纺织机”来了?

智慧财务020社群与知识分享|上方蓝字星标置顶订阅|转载商务合作请添加首财君微信:cfo_tian昨日重磅《百亿存款谜局2》新戏码下的资管拷问一文秒懂财务机器人(RPA)的拐点和跳点。文 | 首财君【原创】出品 | 首席财务官(ID:cfoworld…

安装oracle配置监听出错,安装失败,无法建立监听?

本帖最后由 tomfish88 于 2013-12-5 13:23 编辑原来的监听如何删除呢,我把注册表的也删除了,还是安装不成功,QQ截图20131203114715.png (70.13 KB, 下载次数: 2)2013-12-5 13:21 上传QQ截图20131205132007.png (8.28 KB, 下载次数: 1)2013-12…

数据量大的sql怎么做优化_搜索引擎关键词优化怎么做

搜索引擎关键词优化怎么做新媒体营销的兴起,而且是方兴未艾,因此与客户建立起良好关系,保持良好的口碑关系,让客户信任你,公司适合增值服务:或者利润多,同时能在业务管理、销售管理、人事管理等…

vba显示正在加载_简单、易用的VBA过程积木代码

分享成果,随喜真能量(这是我视频教程的开场白)。大家好,今天继续和大家分享VBA编程中常用的简单“积木”过程代码第76-80讲,内容是:保存当前工作簿的副本到其它位置备份;不显示警告框&#xff1…

linux关机_Linux中shutdown,halt,poweroff,init 0区别

1)shutdown命令我们较常使用的是shutdown这个命令,这个命令可以安全地关闭或重启Linux系统,它在系统关闭之前给系统上的所有登录用户提示一条警告信息。该命令还允许用户指定一个时间参数,可以是一个精确的时间,也可以…

php static_castunsigned int,static_cast揭密

本文讨论static_cast<> 和 reinterpret_cast<>。介绍大多程序员在学C前都学过C&#xff0c;并且习惯于C风格(类型)转换。当写C(程序)时&#xff0c;有时候我们在使用static_cast<>和reinterpret_cast<>时可能会有点模糊。在本文中&#xff0c;我将说明…

群晖 root_最新群晖DSM7.0降级教程

小广告:隔壁网1212活动开始,群晖DS920+和DS220+满300减40,购买酷狼8T赠送隔壁网邀请码和1年VIP,大刀价格微信咨询momo。 很多童靴,升级到DSM7.0后第三方套件无法用了,比如transmission,玩PT下载的童靴,需要谨慎升级。 这个方法wjq_xp分享的,非常感谢,前面部分和旧的降…

update关联一个视图的时候特别慢_实现一个简单的Vue.js

原文转自 https://const_white.gitee.io/gitee-blog/blog/vue/mini-vue/Vue响应式原理图片引自 孟思行 - 图解 Vue 响应式原理乞丐版 mini-vue实现mini-vue之前&#xff0c;先看看官网的描述。在Vue官网&#xff0c;深入响应式原理中&#xff0c;是这样说明的&#xff1a;每个组…

接口500什么原因_80%小餐饮店几乎都“活“不过500天,为什么?都在这5个原因里...

餐饮创业分享达人 原创作者&#xff1a;秦.关中我是餐饮创业实战经验分享达人&#xff0c;想要了解和学习更多餐饮实战经验&#xff0c;记得关注我哦&#xff01;希望我的分享能帮助大家在创业的道路上少走弯路&#xff0c;不踩坑&#xff0c;为你创业成功助上一臂之力。无论是…

padding-left在ie8中不生效_陆国应 律师从民法典第209条来看房屋未过户买卖合同是否生效?...

基本案例(案例来源于大律师教你怎样打官司)&#xff1a;张某与某公司签订了房屋买卖合同。双方在合同中约定&#xff1a;某公司将三居室一套售于张某&#xff0c;价款30万元&#xff0c;并约定该房屋于2018年2月1日前交付使用。此后&#xff0c;张某先后向某公司支付购房款人民…

电脑反应慢卡怎么解决_电脑键盘失灵怎么解决

键盘是电脑中重要的输入设备之一&#xff0c;但在使用的过程中有时可能会碰到键盘失灵的问题&#xff0c;该怎么办呢&#xff1f;别着急&#xff0c;接下来&#xff0c;小编我就来给大家详细的介绍一下电脑键盘出现怎么办&#xff0c;并将解决键盘失灵的操作方法来分享给你们。…

金山手机控usb调试模式开启工具_话筒坏了无法连麦?一招手机秒变电脑麦克风...

前段时间孩子上网课&#xff0c;沉睡多年的摄像头和麦克风被从箱子底请了出来。当连接妥当后&#xff0c;突然发现麦克风失灵了&#xff0c;几经调试宣布报废&#xff01;当时正处于疫情高发期&#xff0c;电脑城肯定是关了&#xff0c;网购是来不及了&#xff0c;怎么办&#…

idea的setting界面怎么进_电脑怎么备份系统

电脑怎么备份系统&#xff1f;提前备份系统可以帮助下次系统出现问题后&#xff0c;随时还原备份的系统。下面小白提供两种备份系统的方法。一、系统自带备份一般现在的电脑系统都自带备份还原的功能&#xff0c;不管是win10还是win7都需要手动去备份。优点&#xff1a;可以增加…

快手用旺旺瓶子做机器人_用罐头瓶子做醪糟容易做好保存

醪糟是湖北四川一带的著名风味小吃&#xff0c;醪糟汤圆、醪糟鸡蛋酸甜可口&#xff0c;非常美味。这些年&#xff0c;北方人喜欢它的人也逐渐多了起来&#xff0c;我们单位食堂每周三都做醪糟汤圆大枣汤&#xff0c;是同事们最喜爱、期待的一道美食。我告诉她们可以自己做醪糟…

你不出去卖我拿什么养你_玉树枝干“胳膊粗”,至少养了20年,给钱也不卖!...

玉树是很多花友家里常见的栽培花卉&#xff0c;很多地方叫厚脸皮&#xff0c;是很早进入到国内的多肉植物&#xff0c;经常能够几十年的玉树老桩&#xff0c;长得霸气。玉树叶子四季浓绿油亮&#xff0c;叶子犹如一片片碧玉&#xff0c;枝干古朴苍劲&#xff0c;犹如一棵大树&a…

c++rpg黑框游戏_NO总本色出演断智大师兄,电竞魔音主C人《超级猎杀》一战成名...

谁说没有智力不能玩RPG&#xff1f;近日&#xff0c;一款名为《超级猎杀》的生存RPG地图在诸多暴雪主播的相继挑战下进入了玩家们的眼帘&#xff0c;要说其中哪位的操作堪称"迷幻"之最&#xff0c;或许No总排第二没人敢称第一。这位菜的同时又自诩无敌的"断智法…

centos7 修改为任意网卡名_centos7首次启动需要配置的内容

前言用户需安装centos图形化界面或命令行界面安装centos7命令行界面&#xff0c;我为大家整理好了&#xff0c;请点击以下链接&#xff0c;亲测N次没问题&#xff01;&#xff01;[https://blog.csdn.net/zkzbhh/article/details/78145708](https://blog.csdn.net/zkzbhh/artic…

vue实现一个带搜索功能的列表_(Vue起步)2.模板指令:v-for / v-on / v-model

①公众号&#xff1a;王酱酱记②记录跟着文档学习Vue的一些关键点&#xff0c;持续更新。感兴趣的小白建议关注一下③Vue当中有几个常见的指令&#xff0c;看看是怎么用的&#xff0c;强烈建议你自己在编辑器里打一遍&#xff0c;你就更明白Vue为什么是数据驱动型的一款框架啦&…