电子说
1volatile关键字
volatile是一个特征修饰符,提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,告诉编译器对该变量不做优化,都会直接从变量内存地址中读取数据,从而可以提供对特殊地址的稳定访问。
常用场景:中断服务与主程序共享变量。示例代码如下:
//volatile uint8_t flag=1; uint8_t flag=1; void test(void) { while(flag) { //do something } } //interrupt service routine void isr_test(void) { flag=0; }
如果没使用volatile定义flag,可能在优化后test陷入死循环,因为test里使用的flag并没修改它,开启优化后,编译器可能会固定从某个内存取值。
2const关键字
const 是 constant 的缩写,意思是“恒定不变的”,它是定义常变量的关键字。
通常有4种用法。
1、修饰变量
采用const修饰变量,即变量声明为只读,保护变量值以防被修改。
const int i = 1; 或者 int const i=1;
变量i具有只读特性,不能够被更改;若想对i重新赋值,如i = 10,属于错误操作。
2、修饰数组
数组元素与变量类似,具有只读属性,不能被更改,一旦更改,编译时就会报错。
const int array[5] = {1,2,3,4,5}; array[0] = array[0]+1; //错误,array是只读的,禁止修改
使用大数组存储固定的信息,例如查表(表驱动法的键值表),可以使用const节省ram。编译器并不给普通const只读变量分配空间,而是将它们保存到符号表中,无需读写内存操作,程序执行效率也会提高。
3、修饰指针
C语言中const修饰指针要特别注意,共有两种形式,一种是用来限定指向空间的值不能修改;另一种是限定指针不可更改。
int i = 1; int j = 2; const int *p1 = &i; int* const p2 = &j;
上面定义了两个指针p1和p2,区别是const后面是指针本身还是指向的内容。
在定义1中const限定的是*p1,即其指向空间的值不可改变,若改变其指向空间的值如*p1=10,则程序会报错;但p1的值是可以改变的,对p1重新赋值如p1=&k是没有任何问题的。
在定义2中const限定的是指针p2,若改变p2的值如p2=&k,程序将会报错;但*p2,即其所指向空间的值可以改变,如*p2=20是没有问题的,程序正常执行。
4、 修饰函数参数
const关键字修饰函数参数,对参数起限定作用,防止其在函数内部被修改,所限定的函数参数可以是普通变量,也可以是指针变量。
void fun(const int i) { …… i++; //对i的值进行了修改,程序报错 }
常用的函数如strlen。
size_t strlen(const char *string);
const在库函数中使用非常普遍,是一种自我保护的安全编码思维。
3static关键字
1、static修饰全局变量,该变量只在本文件内被访问,不能在其他文件被直接访问。
2、static修饰函数,该函数只能在本文件内被访问,不能被其他文件访问。但是可以通过嵌套的方式调用,变相的封装的表现。
3、static修饰局部变量,更改该局部变量的生命周期。
生命周期:将临时变量的生命周期变成全局变量的生命周期。
作用域不变:作用域仍然是在本代码块内。
4struct与union
可以使用struct结构体来存放一组不同类型的数据。
struct 结构体名{ 结构体所包含的变量或数组 };
结构体是一种集合,它里面包含了多个变量或数组,它们的类型可以相同,也可以不同,每个这样的变量或数组都称为结构体的成员,通常我们使用结构体定义和解析协议,如下所示:
// WiFi接收数据帧,控制切换模式 #pragma pack(1) typedef struct receive_data_mode_t { uint8_t device_head; // 数据帧头:0XA0+功能码(FUNCTION_ID3),A款产品智能插座 uint16_t device_len; // 数据包总长度 uint16_t device_id; // 节点ID 0X0001~0XFFFE char software_version[15]; // 软件版本 SMART_SW_A1_1.0 A款产品软件1.0版本 char hardware_version[15]; // 硬件版本 SMART_HW_A1_1.0 A款产品硬件1.0版本 uint8_t switch_mode; // 切换模式 0:运行模式,1:配置模式,2:节点升级,3:节点重启 uint16_t crc; // 校验位 }ReceiveData_Mode_t; #pragma pack()
union共用体关键字,定义union下面的成员变量共享一块内存,每一个成员在任一时刻有且只有一个成员使用此块内存。
union 共用体名{ 成员列表 };
结构体和共用体的区别在于:结构体的各个成员会占用不同的内存,互相之间没有影响;而共用体的所有成员占用同一段内存,修改一个成员会影响其余所有成员。
通常使用共用体做一些标志位操作,例如以下示例,可以非常灵活的访问Val中的bit位。
typedef union { BYTE Val; struct __packed { BYTE b0:1; BYTE b1:1; BYTE b2:1; BYTE b3:1; BYTE b4:1; BYTE b5:1; BYTE b6:1; BYTE b7:1; } bits; }BYTE_VAL, BYTE_BITS;
或者使用共用体实现单字节与多字节的转化和拼接,如下所示:
#include "stdio.h" typedef struct { union { struct { unsigned char low; unsigned char high; }; unsigned short result; }; }test_t; int main(int argc, char *argv[]) { test_t hello; hello.high=0x12; hello.low=0x34; printf("result=%04X ",hello.result);//输出 result=1234 return 0; }
5预定义标识符
一般编译器都支持预定义标识符,这些标识符结合printf等打印信息帮助程序员调试程序是非常有用的,一般编译器会自动根据用户指定完成替换和处理。
常用的预定义标识符如下所示:
__FILE__ //表示编译的源文件名 __LINE__ //表示当前文件的行号 __FUNCTION__ //表示函数名 __DATE__ //表示编译日期 __TIME__ //表示编译时间
在Debug打印日志时候经常会用到,如下所示:
printf("file:%s,line:%d,date:%s,time:%s",__FILE__,__LINE__,__DATE__,__TIME__);
6#与##
#:是一种运算符,用于带参宏的文本替换,将跟在后面的参数转成一个字符串常量。
##:是一种运算符,是将两个运算对象连接在一起,也只能出现在带参宏定义的文本替换中。
#include "stdio.h" #define TO_STR(s) #s #define COMB(str1,str2) str1##str2 int main(int argc, char *argv[]) { int UART0= 115200; printf("UART0=%d ", COMB(UART, 0));//字符串合并为变量UART0 printf("%s ", TO_STR(3.14));//将数字变成字符串 return 0; }
7void 与 void*关键字
void表示的是无类型,不能声明变量或常量,但是可以把指针定义为void类型,如void* ptr。void* 指针可以指向任意类型的数据,在C语言指针操作中,任意类型的数据地址都可转为void* 指针。因为指针本质上都是unsigned int。
常用的内存块操作库函数:
void * memcpy( void *dest, const void *src, size_t len ); void * memset( void *buffer, int c, size_t num);
数据指针为void* 类型,对传入任意类型数据的指针都可以操作。另外其中memcpy第二个参数,const现在也如前文所述,拷贝时对传入的原数据内容禁止修改。
特殊说明,指针是不能使用sizeof求内容大小的,在ARM系统固定为int 4字节。对于函数无输入参数的,也尽量加上void,如下所示:
void fun(void);
8weak关键字
一般简化定义如下所示:
#define _WEAK __attribute__((weak))
函数名称前面加上__WEAK属性修饰符称为“弱函数”,类似C++的虚函数。链接时优先链接为非weak定义的函数,如果找不到则再链接带weak函数。
_WEAK void fun(void) { //do this } //不在同一个.c,两同名函数不能在同一个文件 void fun(void) { //do that }
这种自动选择的机制,在代码移植和多模块配合工作的场景下应用较多。例如前期移植代码,需要调用某个接口fun,但当前该接口不存在或者未移植完整使用,可以使用weak关键字定义为空函数先保证编译正常。
后续移植完成实现了fun,即软件中有2个fun函数没有任何错误,编译器自动会识别使用后者。当然也粗暴的#if 0屏蔽对fun的调用,但要确保后续记得放开。
全部0条评论
快来发表一下你的评论吧 !