本节说明ANSIC和MPLAB C30在关键字方面的差别。新关键字是基本GCC实现的一部分,本节的讨论基于标准的GCC文档,选择GCCMPLAB C30部分的特定语法和语义来讲述。
1.指定变量的属性
MPLAB C30的关键字__attribute__用来指定变量或结构位域的特殊属性。关键字后的双括号中的内容是属性说明。目前支持的变量属性如下:address(addr)、aligned(alignment)、deprecated、far、mode(mode)、near、noload、packed、persistent、reverse(alignment)、section("section-name")、sfr(address)、space(space)、transparent_union、unordered、unused、weak。
也可以通过在关键字前后使用__(双下画线)来指定属性。例如,用__aligned__代替aligned,以避免在头文件中使用这些关键字时出现与宏同名的情况。要指定多个属性,可在双括号内用逗号将属性分隔开。例如:__attribute__((aligned(16),packed))。注意,一个项目中对变量属性的使用要一致。例如,如果在文件A中用far属性定义了一个变量,在文件B中将其声明为extern而不带far,就可能导致链接错误。
(1)address(addr)
address属性为变量指定绝对地址。这个属性不能与section属性同时使用。
address属性优先。带address属性的变量不能存放到auto_psv空间,这样做会产生警告,且编译器将此变量存放到psv空间。
如果要将变量存放到psv段,地址应为程序存储器地址,例如:
(2)aligned(alignment)
该属性为变量指定最小的对齐方式,用字节表示。对齐方式必须是2的次幂。例如,下面的声明:
使编译器按照16字节分配全局变量x。对于dsPICDSC器件,这可以与访问需要对齐的操作数的DSP指令和寻址模式的asm语句配合使用。在前面的例子中,可以显式地指定希望编译器对给定变量使用的对齐方式(用字节表示)。或者可以省略对齐方式,而要求编译器为变量使用dsPICDSC器件的最大有用对齐。例如,可以这样写:
当省略了对齐属性说明中的对齐方式时,编译器会自动地将已声明变量的对齐方式设置为目标单片机任何数据类型所使用的最大对齐方式。在dsPICDSC器件中,为双字节(1个字)。
aligned属性只能增大对齐,但可以通过指定packed属性来减小对齐。
aligned属性与reverse属性冲突,同时指定两者会产生错误。
(3)deprecated
deprecated属性使得包含这一属性的声明能被编译器特别识别到。当使用deprecated函数或变量时,编译器会发出警告。deprecated定义仍将被编译器执行,并被反映到目标文件中。例如,编译以下程序:
将产生下面的警告:
在生成的目标文件中,仍以通常的方式定义了i。
(4)far
far属性告知编译器不必将变量分配到near(前8KB)数据空间中(即变量可以分配到数据存储器中的任何地址)。
(5)mode(mode)
在变量声明中使用mode属性来指定与模式mode对应的数据类型。实际上就是允许根据变量的宽度指定整数或浮点数类型。mode的有效值见表2-1。这一属性对于编写可在所有MPLAB C30支持的目标单片机之间移植的代码很有用。
表2-1 mode的有效值
例如,如下函数将两个32位有符号整数相加,并返回一个32位有符号整数结果:
可以指定byte或__byte__模式对应于单字节整数,word或__word__模式对应于单字整数,pointer或__pointer__模式用于表示指针。
(6)near
near属性告知编译器将变量分配到near数据空间(数据存储器的前8KB)。对这种变量的访问有时比访问未分配(或不知已分配)到near数据空间的变量效率高。例如:
(7)noload
noload属性指明应该为变量分配空间,但不应为变量装入初值。这一属性对于设计在运行时将变量装入存储器(如从串行EEPROM)的应用程序可能有用。例如:
(8)packed
packed属性指定变量或结构位域采用最小的可能对齐方式:变量占①字节,位域占①位,除非用aligned属性指定了一个更大的值。下面的结构中位域x被压缩,所以它紧接在a之后:
注:dsPIC器件要求字按偶数字节对齐,因此在使用packed属性时要特别小心,避免运行时寻址错误。
(9)persistent
persistent属性指定在启动时变量不应被初始化或清零。具有persistent属性的变量可用于存储器件复位后仍保持有效的状态信息。例如:
(10)reverse(alignment)
reverse属性为变量的结束地址指定最小对齐方式。对齐以字节指定,必须是2的次幂。反向对齐的变量可用于递减dsPICDSC汇编语言中的模缓冲区。如果应用程序需要使在C中定义的变量,可从汇编语言访问,这一属性可能有用。例如:
reverse属性与aligned和section属性冲突。如试图为反向对齐的变量指定一个段,系统将忽略,并发出警告。为同一个变量同时指定reverse和aligned会产生错误。带有reverse属性的变量不能存放到auto_psv空间(参见space属性或-mconst-in-code选项);试图这样做将导致警告,且编译器会将变量存放到psv空间。
(11)section("section-name")
默认情况下,编译器将其生成的目标代码存放在.data段和.bss段中。section属性允许指定变量(或函数)存放到特定的段中。例如:
section属性与address属性和reverse属性冲突。在这两种冲突情形下,段名将被忽略,并发出警告。这一属性还可能与space属性冲突。
(12)sfr(address)
sfr属性告知编译器变量是一个特殊功能寄存器(SFR),同时使用address参数指定变量的运行时地址。例如:
为避免产生错误,需要使用extern说明符。
注:按照约定,仅在处理器头文件中使用sfr属性。为将一个普通变量定义到指定的地址,要使用address属性,且用near或far来指定正确的寻址模式。
(13)space(space)
一般说来,编译器在一般数据空间内分配变量。可使用space属性来指示编译器将变量分配到特定存储空间。space属性接受如下参数:
●data:将变量分配到一般数据空间。可使用一般C语句访问一般数据空间中的变量。这是默认的分配。
●xmemory:仅适用于dsPIC30F/33FDSC。将变量分配到X数据空间。可使用一般C语句访问X数据空间中的变量。例如:
●ymemory:仅适用于dsPIC30F/33FDSC。将变量分配到Y数据空间。可使用一般C语句访问Y数据空间中的变量。例如:
●prog:将变量分配到程序空间中为可执行代码指定的段。程序空间中的变量不能使用一般C语句访问,这些变量必须由编程人员显式访问,通常通过表访问行内汇编指令,或使用程序空间可视性窗口访问。
●auto_psv:将变量分配到程序空间中为自动程序空间可视性窗口访问指定的编译器管理段。auto_psv空间中的变量可使用一般C语句来读(但不能写),且变量的分配空间最大为32KB。当指定space(auto_psv)时,不能使用section属性指定段名,任何段名将被忽略并产生警告。auto_psv空间中的变量不能存放到特定地址或反向对齐。在启动时分配到auto_psv段中的变量不装入数据存储器。这一属性对于减少RAM的使用可能有用。
●dma:仅适用于PIC24HMCU和dsPIC33FDSC。将变量分配到DMA存储区。可以通过一般C语句和DMA外设访问DMA存储区中的变量。可使用__builtin_dmaoffset()来得到用于配置DMA外设的正确偏移量。
●psv:将变量分配到程序空间中为程序空间可视性窗口访问指定的段。链接器将定位段,因此可以通过PSVPAG寄存器的设置来访问整个变量。PSV空间中的变量不是由编译器管理的,不能使用一般C语句访问。这些变量必须由编程人员显式访问,通常使用表访问行内汇编指令,或使用程序空间可视性窗口访问。
●eedata:仅适用于dsPIC30FDSC。将变量分配到EEData空间。EEData空间中的变量不能使用一般C语句访问。这些变量必须由编程人员显式访问,通常使用表访问行内汇编指令,或使用程序空间可视性窗口访问。
(14)transparent_union
这是属于union型函数参数的属性,即相应的参数可以是任何联合成员的类型,但以第一个联合成员的类型传递参数。使用transparent联合的第一个成员的调用约定将参数传递给函数,而不是使用联合本身的调用约定。联合的所有成员必须具有相同的机器码表示,以保证参数传递正常进行。
(15)unordered
unordered属性表明变量存放的地址可以相对于所在C源文件中其他变量的位置而改变。这不符合ANSIC,但可使链接器更好地利用存储空隙。例如:
(16)unused
这一变量属性表明变量可能不被使用。MPLAB C30不会为这种变量产生未使用变量警告。(17)weak
weak属性声明weak符号。weak符号可能被全局定义取代。如果对外部符号的引用使用weak,则链接时不需要该符号。例如:
在上面的程序中,如果s没有被其他模块定义,程序仍会链接,但不会给s分配地址。若条件验证s已被定义,就返回它的值(如果它有值的话)。否则将返回“0”值。这个特征很有用,主要用于提供与任意库链接的通用代码。
weak属性可以应用于函数和变量,例如:
在上述代码中,函数compress_data只有在与其他模块链接时才使用。是否使用该特性是在链接时决定的,而不是在编译时决定的。
weak属性对定义的影响更为复杂,需要多个文件加以说明,例如:
以上程序在weak2.c中对i的定义使符号成为强定义。链接时不会出现错误,两个i指向同一个存储位置。为weak1.c中的i分配存储空间,但这个空间不可访问。
不能保证两个程序里的i具有相同的类型,如果将weak2.c中的i改为float型,仍然允许链接,但是函数foo的操作将无法预料。foo将向32位浮点值的最低有效部分写入一个值。相反,在weak1.c中把i的weak定义改为float型,将导致灾难性结果。这样会把一个32位的浮点值写到16位的整型地址中,覆盖掉紧接在i之后存储的任何变量。
在只存在weak定义的情况下,链接器才选择为定义是不可访问的。无论符号属于什么类型,操作是相同的,函数和变量具有相同的操作。
2.指定函数的属性
在MPLAB C30中,可以对程序中调用的函数进行某些声明,帮助编译器优化函数调用,且更准确地检查代码。关键字_attribute_允许在声明时指定特殊的属性。关键字后面的双括号中的是属性说明。目前支持函数的下列属性:address(addr)、alias("target")、const、far、format(archetype,string-index,first-to-check)、format_arg(string-index)、interrupt[([save(list)][,irq(irqid)][,altirq(altir-qid)][,preprologue(asm)])]、near、no_instrument_function、noload、noreturn、section("section-name")、shadow、unused、weak。
也可以通过在关键字前后使用__(双下画线)来指定属性(例如,用__shadow__代替shadow)。这样使得在头文件中使用它们时不必考虑会出现与宏同名的情况。要想在声明中指定多个属性,可以在双括号内使用逗号将属性分隔开,或者在一个属性声明后紧跟另一个属性声明。
(1)address(addr)(www.xing528.com)
address属性为函数指定绝对地址。这个属性不能与section属性同时使用,address属性优先。例如:
(2)alias("target")
alias属性为另一个符号声明一个别名,必须指定这个符号。
使用这一属性会产生对对象的外部引用,必须在链接时解析该引用。
(3)const
许多函数除了检查自身的参数外不会检查任何其他值,只会影响其返回值。可像算术运算符一样,对这种函数进行公共子表达式删除和循环优化。这些函数应该用属性const来声明。例如:
也就是说,上述假设的square函数的实际被调用次数即使比程序指定的次数少一些也是安全的。应该注意,如果函数有指针参数,且检查指针指向的数据,那么这种函数一定不能用const声明。同样,调用非const函数的函数通常也不能声明为const。具有void返回值类型的const函数没有什么意义。
(4)far
far属性告知编译器不应该用更有效的调用指令形式来调用该函数。
(5)format(archetype,string-index,first-to-check)
format属性指定一个函数具有printf、scanf或strftime类型参数,要根据格式字符串检查这些参数的类型。例如,考虑以下声明:
以上语句使编译器检查对my_printf调用中的参数,确定是否与printf类型的格式字符串参数my_format一致。
参数archetype确定如何解释格式字符串,应该为printf、scanf或strftime之一。参数string-index指定哪个参数是格式字符串参数(参数从左至右编号,从1开始),first-to-check指定根据格式字符串检查的第一个参数的编号。对于不能检查参数的函数(如vprintf),指定第三个参数为0。这种情况下,编译器仅检查格式字符串的一致性。
在上面的例子中,格式字符串(my_format)是函数my_print的第二个参数,从第三个参数开始检查,所以format属性的正确参数是2和3。
format属性允许识别以格式字符串作为参数的用户自定义函数,所以MPLAB C30可以检查对这些函数的调用有无错误。每当要求这种警告(使用-Wformat)时,编译器总会检查ANSI库函数printf、fprintf、sprintf、scanf、fscanf、sscanf、strftime、vprintf、vfprintf和vsprintf的格式,所以不必修改头文件stdio.h。
(6)format_arg(string-index)
format_arg属性指定一个函数具有printf或者scanf类型的参数,修改这个函数(如将它翻译为另外一种语言),并把函数的结果传递给printf或scanf类型的函数。例如,考虑以下声明:
上述语句使编译器检查对函数my_dgettext的调用中的参数,该函数的结果传递给printf、scanf或strftime类型的函数,以确定是否与printf类型的格式字符串参数my_format一致。
参数string-index指定哪个参数是格式字符串参数(从1开始)。
format-arg属性允许识别修改格式字符串的用户定义函数,所以MPLAB C30可以检查对printf、scanf或strftime函数的调用,这些函数的操作数是对用户定义函数的调用。
(7)interrupt[([save(list)][,irq(irqid)][,altirq(altirqid)][,preprologue(asm)])]
使用这个选项来指明指定的函数是中断服务程序。当指定这个属性时,编译器将生成适用于中断服务程序的函数prologue和epilogue序列。可选的参数save指定函数prologue和epilogue中分别保存和恢复的变量列表。可选参数irq和altirq指定要使用的中断向量表ID。可选参数preprologue指定要在编译器生成的prologue代码前生成的汇编代码。
(8)near
near属性告知编译器可以使用call指令的更有效形式调用函数。
(9)no_instrument_function
如果指定命令行选项-finstrument-functions,那么几乎所有用户函数的入口和出口处在编译时都会被插入profiling函数,而函数被指定此选项时将不执行上述操作。
(10)noload
noload属性指明应该为函数分配空间,但不应把实际代码装入存储器。如果应用程序设计为在运行时将函数装入存储器(如EEPROM),那么这一属性就会很有用。
(11)noreturn
一些标准库函数是不能返回的,例如abort和exit,MPLAB C30自动清楚这种情况。有些程序自定义了不会返回的函数,可以将这些函数声明为noreturn来告知编译器这种情况。
noreturn关键字告知编译器fatal不会返回而不必考虑如果fatal返回会怎样。这可以在某种程度上优化代码,而且这样有助于避免未初始化变量的假警告。
对于noreturn函数,非void的返回值类型并没有什么意义。
(12)section("section-name")
通常,编译器将生成的代码存放在.text段中。但有时可能需要其他的段,或者需要将某些函数存放在特殊的段中。section属性指定将一个函数存放在特定的段中。例如下面的声明:
将函数foobar存放在.libtext段中。
section属性与address属性有冲突。忽略段名会导致警告。
(13)shadow
shadow属性使编译器使用影子寄存器而不是软件堆栈来保存寄存器。该属性通常与interrupt属性同时使用。例如:
(14)unused
unused属性表明函数可能不会被使用。MPLAB C30不会为这种函数发出未使用函数的警告。
(15)weak
参见1.1.1节相关内容。
3.内联函数
通过声明一个函数为inline,可以指示MPLAB C30将这个函数的代码集成到调用函数的代码中。通常这样可避免函数调用的开销,使代码执行速度更快。另外,若任何实际的参数值为常数,它们的已知值可允许在编译时进行简化,这样不用包含所有的内联函数代码。对代码量的影响是不容易预估的。使用内联函数,机器代码量视具体情况可能更大,也有可能更小。
为将函数声明为内联,在其声明中使用inline关键字,例如:
函数定义中的某些用法可能使函数不适合于内联替代。这些用法包括:varargs的使用、alloca的使用、长度可变数据的使用,以及相对goto和非局部goto的使用。如果使用了命令行选项-winline,当标识为inline的函数不能被替代时,会发出警告,并给出失败原因。
在MPLAB C30语法中,关键字inline不会影响函数的链接。
当一个函数同时为inline和static时,如果对该函数的所有调用都集成到调用函数中,且从不使用该函数的地址,那么该函数自身的汇编程序代码就不会被引用。这种情况下,MPLAB C30实际上并不输出该函数的汇编代码,除非指定命令行选项-fkeep-inline-functions。有些调用由于各种原因不能被集成(特别是在函数定义之前的调用不能被集成,定义内的递归调用也不能被集成)。如果存在非集成的调用,那么会以通常方式将函数编译成汇编代码。如果程序引用函数的地址,也必须以通常的方式编译函数,因为它不能被内联。仅在内联函数被声明为static,且函数定义在函数使用之前的情况下,编译器才会删除内联函数。
当inline函数不是static时,编译器必须假定其他源文件可能调用这个函数。因为全局符号只能在所有程序中定义一次,不能在其他源文件中定义该函数,所以其他源文件中的调用不能被集成。因此,非static的内联函数总是以通常的方式编译。
如果在函数定义中同时指定inline和extern,那么定义的函数就只能用来内联。不能以通常的方式编译函数,即使显式地引用其地址,因为这种地址变成了一个外部引用,如同只是声明了函数却没有定义它一样。
4.指定寄存器中的变量
MPLAB C30允许把几个全局变量存放到指定的硬件寄存器中。
也可以指定在其中存放普通寄存器变量的寄存器。
全局寄存器变量在整个程序执行过程中保留寄存器的值。这在程序中可能很有用,如编程语言解释程序,带有几个经常被访问的全局变量。
特定寄存器中的局部寄存器变量并不保留寄存器的值。编译器的数据流分析可以确定何时指定寄存器包含有效的值,何时可将指定寄存器用于其他用途。局部寄存器变量不使用时其中存储的值可被删除。对局部寄存器变量的引用可以被删除、移动或简化。
如果要将汇编指令的一个输出直接写到某个特定的寄存器,那么这些局部变量有时便于扩展行内汇编的使用范围。
(1)定义全局寄存器变量
在MPLAB C30中,可通过以下语句来定义一个全局寄存器变量:
其中,w8是要使用的寄存器名。选择一个可被函数调用正常保存和恢复的寄存器(W8~W13),这样库函数就不会破坏它的值。
一个函数若可能改变一个全局寄存器变量的值,它就不能安全地被保存和恢复该变量编译的函数调用,因为这可能破坏调用函数返回时期望找到的值。因此,若一个程序片段使用了全局寄存器变量,作为该程序片段入口的函数必须显式地保存和恢复属于其调用函数的值。
库函数longjmp将恢复每个全局寄存器变量在setjmp时的值。
所有全局寄存器变量的声明必须在所有函数定义之前。如果这种声明在函数定义之后,寄存器可能被声明之前的函数用于其他用途。
全局寄存器变量不能有初值,因为可执行文件不能为一个寄存器提供初值。
(2)为局部变量指定寄存器
可以通过以下语句用一个指定的寄存器定义局部寄存器变量:
其中,w8是使用的寄存器名。应该注意这与定义全局寄存器变量的语法相同,但是对于局部变量,这种定义应该出现在一个函数中。
定义这种寄存器不保留寄存器的值,控制确定变量的值无效时,其他用途仍可使用这种寄存器。使用这一功能,可能使编译某些函数时可用寄存器太少。
该选项并不能保证MPLAB C30生成的代码始终将这一变量存放在指定的寄存器中。不可以在asm语句中,编写对该寄存器的显式引用,并假定它总是引用这个变量。
局部寄存器变量不使用时其分配可被删除。对局部寄存器变量的引用可以被删除、移动或简化。
5.复数
MPLAB C30支持复数数据类型。我们可以用关键字__complex__来声明整型复数和浮点型复数。例如:__complex__floatx;定义x为实部和虚部都是浮点型的变量。
__complex__shortinty;定义y的实部和虚部都是shortint型的变量。
要写一个复数数据类型的常量,使用后缀“i”或“j”(两者之一,两者是等同的)。例如,2.5fi是__complex__float型的,3i是__complex__int型的。这种常量只有虚部值,但是可以通过将其与实常数相加来形成任何复数值。
要提取复数值符号exp的实部,可写做__real__exp。类似地,用__imag__来提取虚部。例如:
当对复数型值使用算子“~”时,执行复数的共扼。MPLAB C30可以采用非邻近的方式分配复数自动变量,甚至可以将实部分配到寄存器中,而将虚部分配到堆栈中,反之亦然。调试信息格式无法表示这种非邻近的分配,所以MPLAB C30把非邻近的复数变量描述为两个独立的非复数类型变量。如果实际变量名是foo,那么两个假设变量命名为foo$real和foo$imag。
6.双字整型
MPLAB C30支持长度为longint两倍的整型数据类型。对于有符号整型,使用long longint,而对于无符号整型,则使用unsignedlonglongint。可以通过在整型上添加后缀LL得到longlongint类型的整型常量,在整型上添加后缀ULL得到unsigned long long int类型的整型常量。
可以在算术运算中像使用其他整型一样使用这些类型。这些数据类型的加、减和位逻辑布尔运算是开放源代码的,但是,这些数据类型的除法与移位运算不是开放源代码的。这些不开放源代码的运算要使用MPLAB C30自带的特殊库函数。
免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。