嵌入式开发系列教程(三) 单片机的C语言

这里我们主要介C语言运行环境在单片机(MCU)中建立的过程。单片机运行的核心构件是CPU,我们通过讨论CPU怎么运行程序员怎么告诉CPU正确运行(汇编),程序员怎么更容易的告诉CPU正确运行(C语言),来进一步明晰C语言运行环境的建立。值得注意的是,计算机是现代工业的产品,C语言也是。C语言和计算机这两大产品,为了适应彼此都做了相当的调整。

计算机系统的基本概念

《嵌入式开发系列教程(三) 单片机的C语言》

  • 冯诺依曼结构CPU取得存储单元中的程序指令,译码执行。
  • 程序指令被当成了一种特别的静态数据。
  • 指令当成数据的概念使得汇编语言,编译器与其他编程工具得以实现。
  • 哈佛结构是对冯诺依曼结构的一种改进。

汇编

汇编语言使用助记符来代替和表示特定低级机器语言的操作。我们以8051为例,看一个汇编指令

MOV R0,#30H

《嵌入式开发系列教程(三) 单片机的C语言》

  • 图中 r r r的位置应填0 0 0代表R0,immediate data30H由此我们也看到,mov只能用到R0-R7(111)这几个寄存器。
  • 当汇编器看到 MOV Rn,#data类似的命令时,会按照图示将其翻译为一个两字节指令。
MOV 40H,R1

《嵌入式开发系列教程(三) 单片机的C语言》

  • 图中 r r r的位置应填0 0 1代表R1,direct addres40H
  • 虽然同为MOV指令,但是二进制上来看,和上一条指令相比,确是完全不同的两条指令。也就是说通过MOV这个助记符,可以提取同一类动作的指令,方便程序员书写,查看。最后利用汇编器把指令翻译成二进制机器代码。

51单片机的指令宽度较窄,不是一个很好的例子,大家可以熟悉查看cortexM3的指令集,会有更多收获
汇编语言非常直接,他就是一些确定的机器代码(CPU指令),对地址(存储器)进行直接操作。我们来看一张汇编代码的内存布局示意图。

《嵌入式开发系列教程(三) 单片机的C语言》

  • 代码CODE A、CODE B、CODE C和变量variable x variable y是混在一块的,对于单片机来讲,一般将代码放置在flash(ROM)里,variable x变量分配的地址如果为flash空间,这个变量便不能被写。这一般不是我们想看到的。也就是说,写汇编代码必须清楚某一段地址空间被映射到了什么存储单元上。
  • 假设我们把CODE A CODE B CODE C看成是三个函数。我们知道对于汇编来讲只要label,也就是只要函数地址,那么我们怎么传参数
    • 利用寄存器 R0 R1等。这样的话我们需要确定这几个寄存器是否空闲,不空闲的话,要先将其内容保存,然后再用,函数调用完成后恢复。
    • 指定一个地址空间,也就是说,我们可以假设variable x便是需要传的参数,需要传参数时,便把变量值写到variable x空间上。
    • 可参数是不固定的,数量不固定,类型也不固定。刚刚那几个办法只是解决了参数才内存中放置的问题。可我们把参数放置在了variable x variable y内存空间上,用来传递。可去取得时候,我们又需要考虑,这个参数是什么类型的,到底占用了多少内存空间。

我们知道C语言良好的解决了上述问题,可他是怎么解决的呢?

C语言

《嵌入式开发系列教程(三) 单片机的C语言》

  • 这是unix环境下的c语言内存布局图,不过和单片机的差不太多,图就不另外再画了。
  • text是代码段,我们可以看到,已经初始化的数据和代码指令都被放到了代码段
  • 未初始化的数据,被放置在了bss段
  • 有一个stack空间和一个heap空间,单片机上,我们可以只考虑stack空间
  • 图中的high address单片机上完全不用考虑。

C语言中,我们只要申请变量(编译器会根据变量类型分配对应大小的内存空间),写代码就可以了,至于变量和代码会被编译器合理的安排内存空间。如果你想要了解各个段的内存分配情况,可以自行查看工程对应的链接脚本。

我们再来看看栈的活动过程。

int sum()
{
    int a=10,b=11,c=12;
    int s = a+b+c;
    return s
}

int main()
{
    int x=1,y=2,z=3;
    sum();
    return 0;
}

当代码执行到main函数中 int x=1,y=2,z=3这一行时

《嵌入式开发系列教程(三) 单片机的C语言》

栈帧向下生长到 z=3,栈空间上保存了x,y,z三个变量,代码执行到pc指针处。
代码执行到sum函数中int a=10,b=11,c=12这一行时

《嵌入式开发系列教程(三) 单片机的C语言》

栈帧向下生长到c=12,栈空间保存了x,y,z a,b,c六个变量。
当代码执行到main函数中return 0这一行时

《嵌入式开发系列教程(三) 单片机的C语言》

栈帧回退,这样存储a,b,c三个变量的空间可以被重复利用

也就是说,栈帧其实是编译器帮我们维护的一段内存空间。当我们需要的时候便会分配给我们一段,不需要的时候(函数执行完),便会将内存回收,以便其他地方使用,而如果我们用汇编编程,需要手动做这些操作,非常麻烦,得不偿失。

图示中,故意避开了参数传递动作,对于RISC的CPU来说,参数传递一般依赖Rn寄存器,我们可以看编译器手册确定。也就是说,C语言编译器自动生成一些代码保存 Rn –> 使用Rn传递参数 –>跳转到指定函数 –> 执行完毕 –>取Rn原值,返回。类似这种代码。你可以仔细思考下当用栈传递参数时,栈帧结构是怎么变化的

单片机C环境的建立

其实这个问题回答起来很简单,单片机C运行环境,便是将栈,代码段,bss段,各个段内存布局做好,这个代码一般在对应的start.S中,然后,跳转到main函数执行。但是其中有很多细节,你如果有兴趣的话,可查看相应代码。

这是一个免费,开源的教程,如果你喜欢可以转发,也可以打赏奖励。 欢迎关注微信公众号小站练兵

《嵌入式开发系列教程(三) 单片机的C语言》

    原文作者:qianlihu
    原文地址: https://www.jianshu.com/p/709f04a530d0
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞