第一范文网 - 专业文章范例文档资料分享平台

松翰C语言编程指导C+Program+Guide (7)

来源:用户分享 时间:2020-06-28 本文由一世年华 分享 下载这篇文档 手机版
说明:文章内容仅供预览,部分内容可能不全,需要完整文档或者需要复制内容,请下载word后使用。下载word有问题请添加微信号:xxxxxx或QQ:xxxxxx 处理(尽可能给您提供完整文档),感谢您的支持与谅解。

TDxxxV1.0

从上面的程序中我们可以看出,汇编是通过用户规定的一个或几个寄存器来将参数传递进去!又用同样的方法,将结果传递出来。可以想到,所需的结果在函数返回后可以在A中得到,其应用方式如下:

mov cmprs

clock_min,a a,#60h

5.2 函数参数传递

在汇编程序中,用户必须自己定义寄存器用来存放往函数内传递的参数数据和返回的结果。在C程序中函数参数的传递都是通过形参和实参的对应来传递的,我们通过函数调用来将参数传递给函数,在完成函数功能得到结果后,又通过返回值将结果传回给调用的地方。如图:

clock_min = bcd(clock_min); unsigned int bcd(unsigned int input) {

图5-1 函数参数传递与返回

但是,我们都知道C是可以通过转成汇编来实现的,那么SN8 C是如何来实现C的函数传递的呢?虽然这是一个编译器内部的问题,但是面对单片机硬件的编程,了解其原理会有助于用户写出更为精简合理的程序。

我们来看看C函数调用时,编译器生成的中间汇编代码。假设于caller函数内调用callee函式. callee 的参数名称为

_callee_arg ? ; ? 为参数个数。

例如:

int foo(int a, int b, long c) 会产生 _foo_data SEGMENT DATA INBANK ;OVERLAYABLE _foo_arg0 DS 1 ; int a _foo_arg1 DS 1 ; int b _foo_arg2 DS 2 ; long c

前面所示的bcd函数调用的转换如下:

31

… … … return(result_buf); }

TDxxxV1.0

MOV

A, (_clock_min)

__SelectBANK _bcd_arg0 MOV _bcd_arg0, A ;End push arg.... call _bcd

__SelectBANK (_clock_min) MOV _clock_min, A

通过C与汇编程序的对应,我们很容易知道参数的传递是这样的:实际参数clock_min被直接赋值给函数的形参_bcd_arg0,然后在调用函数中参加运算。再来看看返回值,从程序中我们发现最后返回值是在A中得到的,那么在这里是由系统生成的,是不是有一定的规律呢?还是随意的?当然不可能是随意的!那么系统是如何规定的呢?

若返回值是基本的数据类型,一般都是放到固定的寄存器或虚拟寄存器里面,如下表所示:

返回值类型 unsigned/signed char unsigned/signed short unsigned/signed int unsigned/signed long float 寄存器 A A A A,R A,R,Y,Z 表5-1 不同返回值的存放列表

从上边的列表可以知道,对于不同返回值类型,编译器都会有固定的返回值存放点,当程序从函数中返回的时候,这些对应的寄存器里面就存放着返回值,这就从规则上印证了上述bcd函数的转换结果。

而对于那些返回值是数据结构的,系统则会在调用函数的时候增加一个隐含参数(地址)传递给被调函数,被调函数在完成功能运算后将结果放到指定的位置上,程序从函数里返回后就可以从该地址读取返回值。

尽管从原理上看起来,函数的参数传递与返回值的C与汇编的转换的形式是一样的,但是编译器产生代码时依然可能产生一些冗余代码。所以,在面对单片机这样的资源的硬件编程,过多的参数传递及过于复杂的返回值往往造成代码转换得低效率,这是用户需要注意的。

5.3 函数参数与全局变量

在通用计算机上编写C程序的时候,为了尽量使程序模块化,尽量减小模块之间的耦合度,往往会将函数的所有与外界的联系都通过参数传递来进行,这样就使得函数完全被功能化,同时具有很强的复用性。这些正是C所具有的优势所在,正是由于C具有这样的特征才使得C 具有很好的封装性,让这些函数很容易被封装成函数库,方便地提供任意的程序中调用。也正是有这么多的优势,才让我们选择了C。

但是,SN8 C是面对运算能力并不是很强的8-bit单片机处理核心,我们在关注程序的模

32

TDxxxV1.0

块化,可维护性,封装性的优势的同时又不得不考虑程序的转换效率和运行效率。

由于C 的封装特性,使得C程序非常简洁易懂,一个简单的加法赋值运算在汇编中会有很多条语句,而在C中则用一条非常简洁易懂的表达式就可以准确表达。从而使得C也显得更加简单易用。但是,忽略单片机的特征的C 程序就不会是一个好的程序。

面对C 的结构特征,用户必须定义适当数量的函数用于完成相应功能,这是符合C 的编程思想,也符合整体程序编程思想的。而如何来兼顾单片机的硬件特性呢?正因为如此,就要求SN8 C的用户对硬件和对SN8 ASM应该有一定的了解。

我们在前面已经看到过汇编是如何来传递参数的,事实上它是借助了汇编当中的变量的全局特性来完成的。因为全局变量的值不会因为程序的跳转而发生改变,所以在跳转到子程序内部执行的时候,依然可以得到所需变量的值。同样,当在子程序中对确定的全局变量进行改变,当返回到主调程序时依然可以从相应地址得到变量的值。

SN8 C 的参数传递也采取类似的方法,编译器会为相应的函数参数定义相应个数的全局变量(可覆盖的)用于传递参数,尽管这样做已经是一种很优化的处理方案,但相比于汇编的全局变量来看,始终存在数据传递的环节(将调用函数时的实际参数传递给定义的全局变量),不如汇编高效。而当有多个参数需要传递时更增加了转换的代码量!

再从返回值方面来考虑,我们先来看看得到返回值的转换代码。

如:

keyPress = keyConvert(keyPress);

这条keyFinishCHK中的语句调用keyConvert()函数,并将返回值存放到keyPress中。转换得代码如下:

;push arg....

__SelectBANK _keyFinishCHK_arg0 MOV A, (_keyFinishCHK_arg0) __SelectBANK _keyConvert_arg0 MOV _keyConvert_arg0, A ;End push arg.... call _keyConvert

__SelectBANK (_keyFinishCHK_arg0) MOV _keyFinishCHK_arg0, A ;end of function call

从中我们也可以看到返回的时候,都会有一个中间寄存器的转换(上例中的A) ,当返回值只有一个Byte时,这个中间寄存器是A,我们看不出差距。当用到其它寄存器时,就会有明显的代码消耗。

C有了全局变量和局部变量的区分,从而可以将有限的RAM进行复用,从而达到节省资源的目的。同时由于与底层地址的剥离,使变量的命名更趋向人性化,这些都为用户带来了很大的方便。但在考虑使用局部变量的时候我们又不得不考虑代码空间的消耗。这样,通用计算机编程时的“尽量少用全局变量”的准则在单片机编程中就受到了挑战。在函数定义上,我们就需要权衡,在全局变量和参数之间进行取舍。对于面对硬件的单片机编程,我们还是建议多用全局变量来传递数值,而不是用参数。这样在产生代码时就会越贴近汇编的形式,产生代码的效率会越高。

当然,必要时例外!

33

TDxxxV1.0

六、结构体、联合在SN8 C程序中的应用

结构体、联合都是属于C的数据封装形式,它们在通用C程序中起着举足轻重的作用。同样,在编写C程序时,用好这些数据封装形式可以非常方便用户实现面对硬件的编程。

6.1 结构体

结构体可以将几种数据类型组合在一起进行操作。

首先我们来看看结构体的定义,结构体用关键字struct进行定义,如:

Struct 结构体类型名{

成员1数据类型 成员2数据类型 ??

};

其中可以嵌套struct。

结构体在存储形式上是按定义形式的先后,将成员逐个存储摆放形成一个数据块。这样就为程序的数据块操作提供了一个方便的途径。例如在显示屏上要依次序显示[0,5],[1,10],[2,25],[3,50]这样格式的数,定义一个数据结构如下:

Struct disvalue{ Unsigned int number; Unsigned int Weight1;

Unsigned int weight2; };

成员1名称;

成员2名称;

Disvalue disarray[] = {{0,0,5},{1,1,0},{2,2,5},{3,5,0}};

在程序中我们就可以根据从程序操作过程中获得的num来进行显示。

Void diplayFun(unsigned int num) { }

Disvalue display;

Unsigned int disbuff[5]; Display = disarray[num]; disbuff[0] = display.number; disbuff[4] = display.weight1; disbuff[5] = display.weight2;

结构体在硬件编程中还有一个常用的功能就是定义位域,其定义的方法如下:

Struct 结构体名称{

Unsigned bit0:1; Unsigned bit1:2;

34

TDxxxV1.0

Unsigned bit2:1; Unsigned bit3:1; Unsigned num:4;

};

这个结构体中的成员数据类型都是Unsigned,这是必须的。成员名称同样要符合变量命名规则,它与其他结构体不同的是成员名称后面有一个冒号,后面跟一个数据。冒号后面的数据是指占用位的数量,如成员bit1会占用2个bit,而num会占用3个bit,其他分别占用1个bit。这样这个结构体就占用1个Byte的空间。在后面的应用我们会更详细地分析这一数据类型的应用。

同样,与通用C语言的编程相比,在SN8 C中有一些限制,对于struct,我们只能对其实例安排存放的空间,加__RAM和__ROM限制字加于限制。而对于struct的成员我们就无法对其进行限制,这个原因是非常明显的,若是都允许对存放空间进行限制的话,就会造成冲突。如下定义是系统不允许的:

struct StuType { int __ROM data; // 错误 };

对结构体内部的成员变量,其存储的位置由整个结构体来决定。

6.2 联合体

“联合”与“结构”有一些相似之处。但两者有本质上的不同。在结构中各成员有各自的内存空间, 一个结构变量的总长度是各成员长度之和。而在“联合”中,各成员共享一段内存空间, 一个联合变量的长度等于各成员中最长的长度。联合体的特点在于其成员的存储空间相同,即联合体内部成员指向同一个空间,事实上是同一个空间通过不同的名称调用。如同时定义几种数据类型char、int、long组成一个联合体。那么,这就意味着修改三个成员中的任意一个都会影响到另外两个的值。

一、联合的定义

定义一个联合类型的一般形式为:

union 联合名 {

成员表

};

成员表中含有若干成员,成员的一般形式为: 类型说明符 成员名 成员名的命名应符合标识符的规定。

例如:

union perdata { int class;

35

搜索“diyifanwen.net”或“第一范文网”即可找到本站免费阅读全部范文。收藏本站方便下次阅读,第一范文网,提供最新初中教育松翰C语言编程指导C+Program+Guide (7)全文阅读和word下载服务。

松翰C语言编程指导C+Program+Guide (7).doc 将本文的Word文档下载到电脑,方便复制、编辑、收藏和打印
本文链接:https://www.diyifanwen.net/wenku/1105808.html(转载请注明文章来源)
热门推荐
Copyright © 2018-2022 第一范文网 版权所有 免责声明 | 联系我们
声明 :本网站尊重并保护知识产权,根据《信息网络传播权保护条例》,如果我们转载的作品侵犯了您的权利,请在一个月内通知我们,我们会及时删除。
客服QQ:xxxxxx 邮箱:xxxxxx@qq.com
渝ICP备2023013149号
Top