首页 理论教育 ARM体系结构嵌入式C语言编程技术:存储类型关键字

ARM体系结构嵌入式C语言编程技术:存储类型关键字

时间:2023-10-19 理论教育 版权反馈
【摘要】:变量的存储类型首先取决于它的声明位置。这个关键字提示编译器将register关键字修饰的自动变量存储在CPU内部的通用寄存器中而不是存储器中,这些变量被称为寄存器变量。

ARM体系结构嵌入式C语言编程技术:存储类型关键字

变量的存储类型是指变量存储位置的存储器类型。变量的存储类型决定了变量何时创建、何时销毁以及其值保持多久(即生命周期)。在C语言中变量可以存放在3个地方:内存全局数据区、内存堆栈、CPU内部的通用寄存器。在这3个地方存储的变量具有不同的特性。

变量的存储类型首先取决于它的声明位置。凡是在函数外声明的变量都是全局变量(默认情况下全局变量的作用域仅限于声明该变量的C文件中,如果希望在该文件之外能够访问这个变量,程序员就需要在引用该变量的C文件中使用extern关键字对这个变量进行重新声明),编译器在编译过程中将全局变量映射在内存全局数据区中,在程序的整个执行期间该变量始终占用编译器为它分配的内存空间,它始终保持原来的值,直到对这个变量进行赋值操作或是程序结束,所以有时也称全局变量是静态的。对于ARM编译器而言,在编译过程中编译器会生成两个全局变量的“段”:有初值全局变量RW段(Read and Write)和无初值全局变量ZI段(Zero Initialized,以零初始化);链接器则将所有C文件的RW段和ZI段进行拼接并对其中的全局变量进行重新定位。注意:程序员不能修改全局变量的存储类型,它只能是静态的。

1.auto关键字

在一个C函数内部声明的变量是局部变量,局部变量的作用域仅限于声明该变量的函数内部,对函数外面的代码是不可见的。默认情况下局部变量的存储类型是自动的(Automatic),也就是说要么这个变量被编译器安排存储在栈中,要么被存储在CPU内部的寄存器中(存放到寄存器以加快访问速度)。在程序执行到声明自动变量的代码时,自动变量才被创建,当程序的执行流离开该代码段时,这些变量便自动销毁,所以可以说自动变量是动态的。

2.register关键字

register关键字可以用于自动变量的声明,声明后称为寄存器变量。这个关键字提示编译器将register关键字修饰的自动变量存储在CPU内部的通用寄存器中而不是存储器中,这些变量被称为寄存器变量。由于CPU内部硬件寄存器的速度要远远高于外部存储器,因此将这些变量存放在寄存器中将获得更高的访问效率(对于嵌入式系统而言,由于访问CPU内部寄存器的功耗要远远小于访问外部存储器的功耗,因此寄存器变量的使用对于降低功耗也会有额外的好处)。但是,由于寄存器资源有限,编译器并不一定遵循程序员的这个建议,如果有太多的自动变量被声明为寄存器变量,则编译器有可能只选取前面的几个存放在寄存器中,其余的将保存在栈中;另外,如果一个编译器拥有自己的一套寄存器优化方法,它可能也会忽略register关键字,因为编译器决定哪些变量存放在寄存器中可能比程序员决定更合理。现在的商用编译器往往采用后一种策略,因此程序员在编写程序时完全可以不再使用register关键字,将这个优化工作交给编译器完成。

3.static关键字

static关键字可能是C语言中比较多义的一个关键字。该关键字的含义取决于使用这个关键字的不同上下文。static关键字一共有3个不同的用途:

(1)如果用于函数内部的局部变量声明,static关键字的作用是改变局部变量的存储类型,从自动变量变为静态变量,也就是说这个局部变量不再存储在栈或寄存器中,而是在编译的时候由编译器分配一个静态的地址空间,但是这个变量的作用域不受影响,依然仅局限在声明它的函数内部。这对于一些想将变量的作用域局限在函数内部,而又期望再次调用该函数时能获取上次运算结果的应用是很有用的。示例程序如下:

程序执行结果如下:

(2)在用于全局变量的声明时,这个全局变量的作用域将局限在声明该变量的C文件内部,这个C文件之外的代码将无法访问这个变量。在分工协作编写一个工程代码时,一来可以用这个方法保护全局变量不被外部访问,减少代码的耦合性,二来可以避免不同程序员定义和命名变量时的同名冲突。例如,甲程序员在其实现的A.c文件中定义了“static char deskNum;”,乙 程 序 员 在 其 实 现 的B.c文 件 中 也 定 义 了“static char deskNum;”,他们所定义的deskNum只能在自己的文件中可用,且编译时编译器将为这两个deskNum分配不同的存储空间。

(3)如果static关键字被用于函数的定义,static关键字的作用类似于全局变量的情况,这个函数就只能在定义该函数的C文件中引用,该C文件外的代码将无法调用这个函数。函数跟全局变量一样,可以通过extern关键字声明为外部文件所用,因此其作用域都是“全局”的,函数是“全局”指令,而全局变量是“全局”数据。因此,可以通过static来约束它们的作用域。

4.extern关键字

在默认情况下,C语言中的全局变量和函数的作用域仅限于定义或声明这个函数或变量的C文件内部,如果需要从这个C文件之外访问这些函数或者全局变量就需要使用extern关键字。这是因为C语言编译器是以C文件为单位进行编译的,如果这个C文件中引用了其他文件中定义的函数或变量,编译器将无法找到这个函数或变量的定义,从而给出该函数或变量未定义的错误信息。为了解决这个问题,C语言中采用了extern关键字。

一般而言,使用extern关键字有两种方式:第一种方式是在C文件中直接声明某个其他文件中定义的函数或全局变量为extern,从而告诉编译器这个函数或变量是在其他C文件中定义的;第二种方式是在头文件中声明某个函数或变量为extern,然后在需要引用该函数或变量的C文件中包含这个头文件。

5.struct关键字

面对一个大型C语言程序时,只看其中struct关键字的使用情况就可以对其编写者的编程经验进行评估。因为一个大型的C语言程序势必要涉及一些(甚至大量)进行数据组合的结构体,这些结构体可以将原本意义属于一个整体的数据组合在一起。从某种程度上来说,会不会用struct关键字、怎样用struct关键字是鉴别一个开发人员是否具备丰富开发经历的标志。关于结构体的基础知识,读者可参考C语言书籍,在此强调两个嵌入式C语言编程时要注意的概念,即位域和结构体内部成员的对齐。

1)位域(www.xing528.com)

有些信息在存储时并不需要占用一个完整的字节,而只需要占一个或几个二进制位。例如在存放一个开关量时,只有0和1两种状态,用一个二进制位即可。为了节省存储空间并使处理简便(为了节约成本,嵌入式硬件系统往往是资源有限的),C语言又提供了一种数据结构,称为“位域”或“位段”。所谓“位域”,是把一个字节中的二进制位划分为几个不同的区域,并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。这样就可以把几个不同的对象用一个字节的二进制位域来表示。位域的定义和位域变量的说明与结构体定义相仿,其形式如下:

比如:

说明:结构体变量pk1或者pk2的3个成员将总共占用16位存储,其中a占用2位,b占用8位,c占用6位。

注意:一个位域必须存储在同一个字节中,不能跨2个字节。当1个字节所剩空间不够存放另一位域时,应从下一单元起存放该位域。也可以有意使某位域从下一个单元开始,例如:

在这个位域定义中,a占第一字节的4位,后4位填0表示不使用;b从第二字节开始,占用4位;c占用4位。另外,由于位域不允许跨2个字节,因此位域的长度不能大于1个字节的长度,也就是说不能超过8位二进制位。

2)结构体内部成员的对齐

在计算结构体长度(尤其是用sizeof关键字)时,需要注意根据不同的编译器和处理器,结构体内部成员有不同的对齐方式,这会引起结构体长度的不确定性。示例代码如下:

以上代码在Tubeo C 2.0中结果是:1,2,3,3。

以上代码在VC6.0中是:1,2,3,4。

字节对齐的细节和编译器实现相关,但一般而言应满足以下3个准则

(1)结构体变量的首地址能够被其最宽基本类型成员的大小所整除;

(2)结构体每个成员相对于结构首地址的偏移量(offset)都是成员大小的整数倍,如有需要,编译器会在成员之间加上填充字节(internal adding);

(3)结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要,编译器会在最末一个成员之后加上填充字节(trailing padding)。

对于上面的准则,有两点需要说明:

(1)结构体某个成员相对于结构体首地址的偏移量可以通过宏offsetof()来获得,这个宏在“stddef.h”中定义如下:

(2)本类型是指前面提到的char、short、int、float、double这样的内置数据类型,这里所说的“数据宽度”就是指其sizeof的大小。由于结构体的成员可以是复合类型,比如另外一个结构体,所以在寻找最宽基本类型成员时,应当包括复合类型成员的子成员,而不是把复合成员看成一个整体。在确定复合类型成员的偏移位置时则将复合类型作为整体看待。

免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。

我要反馈