当前位置: 首页 > 科技观察

C语言关键字应用技巧

时间:2023-03-14 18:44:23 科技观察

1、volatilevolatile修饰表示变量是volatile的,编译器中的优化器每次使用时都要仔细从内存中重新读取变量的值,而不是使用备份存放在寄存器中,有效防止编译器自动优化,以符合软件设计。中断服务和主程序共享变量://volatileuint8_tflag=1;uint8_tflag=1;voidtest(void){while(flag){//dosomething}}//interruptserviceroutinevoidisr_test(void){flag=0;}如果没有定义volatileflag,优化后测试可能会陷入死循环,因为测试中使用的flag并没有对其进行修改。开启优化后,编译器可能会固定某个内存中的值。例如:for(inti=0;i<100000;i++);//对比for(volatileinti=0;i<100000;i++);前者可能会被优化,虽然代码的目的是延迟执行操作,但编译器认为代码没有意义。总的来说,volatile就是告诉编译器不管是什么代码,都必须保留,使用的时候需要从内存中重新读取更新,之前读取的缓存不能使用。一般在驱动代码中用的比较多。2、constconst表示常量,其修饰的各种数据类似于只读效果。1)被修饰变量用const修饰,即声明变量为只读,保护变量的值不被修改。例如constinti=1;上面的例子表明变量i是只读的,不能改变;如果要重新赋值i,比如i=10;这是一个错误的操作。特别是,将变量定义为intconsti=1时初始化变量是正确的。2)C语言中修饰数组const也可以修饰数组,例如:constintarray[5]={1,2,3,4,5};array[0]=array[0]+1;//错误,数组只读,禁止修改数组元素。与变量类似,具有只读属性,不可更改;一旦更改,编译时会报错。使用大数组存储固定信息,如查找表(表驱动方法的key-value表),可以使用const保存ram。编译器不会为普通的const只读变量分配空间,而是将它们保存在符号表中,这样就不需要读写内存操作,程序执行效率也会提高。3)修饰指针C语言中的const修饰指针要特别注意。有两种形式,一种用于限制指向空间的值,不能修改;另一种是限制指针不能改变。例子如下:inti=1;intj=2;constint*p1=&i;int*constp2=&j;上面定义了两个指针p1和p2,区别在于const后面是指针本身还是指向的内容。定义1中,const仅限于*p1,即它指向的值不能改变。如果改变*p1=10指向的值,程序会报错;但是p1的值可以改变。p1=&k这样的赋值是没有问题的。在定义2中,const定义了指针p2。如果改变p2的值如p2=&k,程序会报错;但是*p2,也就是它指向的空间的值是可以改变的,比如*p2=20,是没有问题的。程序正常执行。4)修饰函数参数const关键字用于修饰函数参数,对参数进行限制,防止在函数内部被修改。定义的函数参数可以是普通变量,也可以是指针变量。例如:voidfun(constinti){...i++;//修改了i的值,程序报错}常用函数如strlensize_tstrlen(constchar*string);const在库函数中非常常用,是一种自我保护的安全编码思想。3.struct和union在嵌入式领域使用频率非常高,用于struct结构和unionunion。一些可编程芯片提供的寄存器组,通过结构体和联合体的组合提供给软件人员。开发,在平时的编码过程中灵活应用这两种数据类型,也可以实现代码更好的封装和简化。如下面的简单示例所示,您可以非常灵活地访问Val中的位。typedefunion{BYTEVal;struct__packed{BYTEb0:1;BYTEb1:1;BYTEb2:1;BYTEb3:1;BYTEb4:1;BYTEb5:1;BYTEb6:1;BYTEb7:1;}位;}BYTE_VAL,BYTE_BITS;其中:1表示按位运算。不仅仅是bits-bytes,单字节和多字节也可以简化拼接。#include"stdio.h"typedefstruct{union{struct{unsignedcharlow;unsignedcharhigh;};unsignedshortresult;};}test_t;intmain(intargc,char*argv[]){test_thello;hello.high=0x12;hello.low=0x34;printf("result=%04X\r\n",hello.result);//输出result=1234return0;}运行输出result=1234(win7系统下QT开发环境),本来要求(high<<8)|low操作可以简化为union类型自动完成,但是一定要注意平台的字节序,是属于big-endian还是little-endian模式。在应用层面,如果明确某个数据可能有两种可能,并且这两种结果不会同时存在,也可以使用structure和union的组合来保证统一的对外接口模块。例如,移动通信模块使用数据结构来保存其基站信息。由于标准不同,模块可能工作在2G-GSM或4G-Cat1。为了保证上层有唯一的读取基站信息的接口,使用union是非常合适的,否则需要定义两套接口。如果觉得文章不错,可以关注微信公众号【嵌入式系统】获取更多资讯。4.预定义标识符通常,编译器都支持预定义标识符。这些标识符与printf和其他打印信息相结合,对于帮助程序员调试程序非常有用。一般情况下,编译器会根据用户的要求自动完成替换和处理。部分标识:__FILE__//表示编译后的源文件名__LINE__//表示当前文件的行号__FUNCTION__//表示函数名__DATE__//表示编译日期__TIME__//表示编译时间例:printf("file:%s,行:%d,日期:%s,时间:%s",__FILE__,__LINE__,__DATE__,__TIME__);这些比较常用,主要用于日志分析,版本记录,便于调试。5、#和###:是用于参数宏文本替换的运算符,将后面的参数转换为字符串常量。##:是连接两个操作数的运算符,只能出现在有参数宏定义的文本替换中。#include"stdio.h"#defineTO_STR(s)#s#defineCOMB(str1,str2)str1##str2intmain(intargc,char*argv[]){intUART0=115200;printf("UART0=%d\n",COMB(UART,0));//将字符串合并为变量UART0printf("%s\n",TO_STR(3.14));//将数字转为字符串return0;}6、用void和void*void表示是untyped,不能声明变量或常量,但可以将指针定义为void类型,如void*ptr。void*指针可以指向任何类型的数据。C语言指针操作中,任何类型的数据地址都可以转换为void*指针。因为指针本质上是unsignedint。常用的内存块操作库函数:void*memcpy(void*dest,constvoid*src,size_tlen);void*memset(void*buffer,intc,size_tnum);数据指针是void*类型,对于任何类型的数据指针都可以操作。另外,现在memcpy的第二个参数const就是上面说的,复制时禁止修改传入的原始数据的内容。特别是指针不能用sizeof来求内容大小,在ARM系统中固定为int4字节。对于没有入参的函数,尽量加上void,比如voidfun(void);7、weak一般简化定义#define_WEAK__attribute__((weak))在函数名前加上__WEAK属性修饰符称为“弱函数”,类似于C++虚函数。链接时先链接定义为非弱的函数,如果找不到则链接弱的函数。_WEAKvoidfun(void){//dothis}//不在同一个.c中,两个同名函数不能在同一个文件中。场景中有很多应用。比如前期移植代码的时候,需要调用某个接口fun,但是移植后这个接口不存在或者没有完全使用。可以使用weak关键字将其定义为空函数,以保证正常编译。后续移植完成实现fun,即软件中有两个fun函数没有报错,编译器会自动识别并使用后者。当然,粗鲁的#if0会阻塞对fun的调用,但一定要记得稍后放手。8.摘要的存在是合理的。C语言中的关键字各有其特殊的含义,但一般使用频率较低。比如作文用常用的汉字就可以;等级。后面会详细介绍这部分,涉及到动态加载(OTA)和界面自启动。本文转载自微信公众号“嵌入式系统”,可通过以下二维码关注。转载本文请联系EmbeddedSystems公众号。