|
vr5tulpg5ee640330348.gif
r. ]( {# b* d% I点击上方蓝色字体,关注我们2 \ a i2 H6 z9 i9 c1 ?* }2 b
1 R7 q, O9 [ M
19 E" c1 h! E! u* \
volatile关键字4 w$ c' ?5 m- t5 {; }+ @# V* F
volatile是一个特征修饰符,提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,告诉编译器对该变量不做优化,都会直接从变量内存地址中读取数据,从而可以提供对特殊地址的稳定访问。; N: b0 i. s) P6 p$ ? a
7 H( {6 k. L9 l/ ~0 q常用场景:中断服务与主程序共享变量。示例代码如下:
, p5 n( q! t- K: ^8 L7 I7 d# X; p! d0 W" [+ T
//volatile uint8_t flag=1;uint8_t flag=1;9 Y5 O* K# [4 N1 o
void test(void){ while(flag) { //do something }}
8 }5 ~! {4 }/ A//interrupt service routinevoid isr_test(void){ flag=0;}
& E r; [0 u" E* T v如果没使用volatile定义flag,可能在优化后test陷入死循环,因为test里使用的flag并没修改它,开启优化后,编译器可能会固定从某个内存取值。
3 k6 e& I! _' ]% D+ o7 Q
1 P) y3 x* y# |1 P2
* n6 m0 k2 j7 jconst关键字
/ M1 p& h; r7 ]3 j- rconst 是 constant 的缩写,意思是“恒定不变的”,它是定义常变量的关键字。' T* R9 }" Y: C/ _3 `/ N
通常有4种用法。
3 |+ J" \9 @6 z; s
) y8 d* V! G% F, ~
& I" r O2 Z F% k! c1、修饰变量
% `9 J. S- P- m) J* Z6 v. ^9 F/ I" u2 t, E/ E5 j4 u( }2 b
采用const修饰变量,即变量声明为只读,保护变量值以防被修改。
0 U/ w2 _- f* g9 s6 E0 T' l# o2 [
4 a" @% o* L+ Kconst int i = 1;或者int const i=1;4 V3 ~; x" Q" d( J1 a0 b' [! n* J1 F8 Q
变量i具有只读特性,不能够被更改;若想对i重新赋值,如i = 10,属于错误操作。7 W) w( s6 d1 ?/ d1 h. @: M. @
9 T! h0 u6 g" I
2 L1 U' v6 f& h; ?3 |7 C
2、修饰数组5 d8 x: }, g* s5 I8 B( M
# p1 R# W1 l2 B1 f' c" `( E \; [数组元素与变量类似,具有只读属性,不能被更改,一旦更改,编译时就会报错。
O$ i' Y: Q+ U" N% {7 v: E1 |& H, A
const int array[5] = {1,2,3,4,5};array[0] = array[0]+1; //错误,array是只读的,禁止修改; {+ v( ~( | m: `
使用大数组存储固定的信息,例如查表(表驱动法的键值表),可以使用const节省ram。编译器并不给普通const只读变量分配空间,而是将它们保存到符号表中,无需读写内存操作,程序执行效率也会提高。. J% b! t% ]9 H$ z4 y6 J K
. C0 Y; r6 @4 Y; ]( L3 ?
$ ?/ V6 b" g8 T7 `
3、修饰指针3 u0 A- V3 ^9 j' c" c S
3 B% c" F# q4 T0 dC语言中const修饰指针要特别注意,共有两种形式,一种是用来限定指向空间的值不能修改;另一种是限定指针不可更改。
* u, Q0 |1 q6 @2 i% o
$ }2 f/ Q( J) U& A- z8 |7 j0 |5 {int i = 1;int j = 2;
- r+ F1 i. Y6 Xconst int *p1 = &i;int* const p2 = &j;9 ]1 j# U/ A, o: |/ v
上面定义了两个指针p1和p2,区别是const后面是指针本身还是指向的内容。
7 A w% L! O! X% S% {6 u
- N. P/ F# g7 O+ i; U9 S1 M在定义1中const限定的是*p1,即其指向空间的值不可改变,若改变其指向空间的值如*p1=10,则程序会报错;但p1的值是可以改变的,对p1重新赋值如p1=&k是没有任何问题的。
) L! x% ^9 v: V# }6 H' h
% f, x7 ^0 l% k6 g0 F在定义2中const限定的是指针p2,若改变p2的值如p2=&k,程序将会报错;但*p2,即其所指向空间的值可以改变,如*p2=20是没有问题的,程序正常执行。5 x+ H4 [- @& H
9 c* D3 i% z4 m5 I! n) W
! t, ^8 S" `. u; P. e
4、 修饰函数参数( U. w2 C4 S8 [% q; }6 G
const关键字修饰函数参数,对参数起限定作用,防止其在函数内部被修改,所限定的函数参数可以是普通变量,也可以是指针变量。
# J2 ^: y. J6 E. h( o- h: @$ F3 g+ E- ?6 p" X
void fun(const int i){ …… i++; //对i的值进行了修改,程序报错}/ k8 @" v, P3 N3 q3 @+ ~% M
常用的函数如strlen。1 ~. F- m, K8 j2 X8 v6 ~
5 y) x- u4 @" tsize_t strlen(const char *string);3 v. i" e W# D( G C
const在库函数中使用非常普遍,是一种自我保护的安全编码思维。
1 A' L4 H- X* i k, t/ }3
# M1 E& Y" U# `8 Istatic关键字4 J: c' H x3 ~' |6 q. k3 z: ^, Z
1、static修饰全局变量,该变量只在本文件内被访问,不能在其他文件被直接访问。& d! A# X+ } U" v* n. y
, E1 ?5 G9 E) q
2、static修饰函数,该函数只能在本文件内被访问,不能被其他文件访问。但是可以通过嵌套的方式调用,变相的封装的表现。( ~7 T6 v; C# n7 D1 [
7 ^% n" w( ]- f/ A' p3、static修饰局部变量,更改该局部变量的生命周期。
& Q% b9 W9 h& K1 W$ i, Q6 O生命周期:将临时变量的生命周期变成全局变量的生命周期。作用域不变:作用域仍然是在本代码块内。
/ U4 q5 @. ?( Z1 R+ o4 n$ s' ]. Q9 t
" a/ K; `7 s% L k4
% J* r1 Q& s! b9 S* c$ g$ ^struct与union
2 P+ q+ |9 |7 T+ B" ~& T0 H- f可以使用struct结构体来存放一组不同类型的数据。
3 s2 _" D4 P2 ?9 B1 `# ]
$ i" { m& p6 q- U! ^) [2 astruct 结构体名{ 结构体所包含的变量或数组};
- i8 C0 c E. _. e5 h& ^& L) u结构体是一种集合,它里面包含了多个变量或数组,它们的类型可以相同,也可以不同,每个这样的变量或数组都称为结构体的成员,通常我们使用结构体定义和解析协议,如下所示:. \, w7 y* W/ c) z4 v
0 O2 s, a3 D+ d2 j- f// 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()5 n# B- Q# c3 P1 \5 F P8 u( n
union共用体关键字,定义union下面的成员变量共享一块内存,每一个成员在任一时刻有且只有一个成员使用此块内存。" X# ?; y3 e" X; m
* y) j" ^' f/ R5 T. y8 A
union 共用体名{ 成员列表};$ c% ]; H& D$ o8 x( c
结构体和共用体的区别在于:结构体的各个成员会占用不同的内存,互相之间没有影响;而共用体的所有成员占用同一段内存,修改一个成员会影响其余所有成员。1 x' T4 `' Q! s, ^/ y- i
通常使用共用体做一些标志位操作,例如以下示例,可以非常灵活的访问Val中的bit位。
% [8 p8 P# {5 f% G: j2 w& z4 Q6 v
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;
1 a' M: f( S5 w) i; b( \* P或者使用共用体实现单字节与多字节的转化和拼接,如下所示:
" X" l5 c) r! W
* _. `/ G8 _3 ]' [6 P# M* h#include "stdio.h"4 h6 t) P+ x9 r4 D; j4 h5 ?" s
typedef struct{ union { struct { unsigned char low; unsigned char high; }; unsigned short result; };}test_t;
2 j V+ v, x2 [. Vint main(int argc, char *argv[]){ test_t hello;% _/ E6 {6 j+ q# d/ ]' a
hello.high=0x12; hello.low=0x34;: ~' _! i; {3 K$ {% Y |
printf("result=%04X\r, B( Y4 f. C$ i3 P- \* p
",hello.result);//输出 result=1234 - ?6 d) J4 G$ B4 h
return 0;}
: S8 D/ d" c: V' ~) M6 ^0 A, v$ D
# T" M4 A" ?) E# V1 L8 c5" q1 ]' y# S" @/ C" w3 ]. ?% N
预定义标识符* u5 J5 q; |, S/ B- H( m& _
一般编译器都支持预定义标识符,这些标识符结合printf等打印信息帮助程序员调试程序是非常有用的,一般编译器会自动根据用户指定完成替换和处理。
9 t8 m- m3 Z- x. o- n9 p
" P5 [0 ~% w) P! r: J4 G常用的预定义标识符如下所示:
5 z3 _- a+ S1 E2 h, a. f
& w4 v9 K9 }4 I% o# ~& W__FILE__ //表示编译的源文件名__LINE__ //表示当前文件的行号__FUNCTION__ //表示函数名__DATE__ //表示编译日期__TIME__ //表示编译时间( m3 G- ]4 m' t; s8 Q S. U
在Debug打印日志时候经常会用到,如下所示:
0 L0 u) T3 V" U0 T8 D3 B% [' I5 B/ t+ G4 m& ?& _
printf("file:%s,line:%d,date:%s,time:%s",__FILE__,__LINE__,__DATE__,__TIME__);4 W2 B5 j- S i& U/ p |% ]) V% N
' I8 n; R* l2 U& W, Q; h1 Y, _9 _6
8 L g8 M# z( @1 k#与##
o* @: _2 v( O8 e. E#:是一种运算符,用于带参宏的文本替换,将跟在后面的参数转成一个字符串常量。
: I) I3 R5 k! _+ H! f
* o# T1 v9 s! b/ P R##:是一种运算符,是将两个运算对象连接在一起,也只能出现在带参宏定义的文本替换中。. O+ a- s' |' O" R
) c( {) v$ `9 v' E2 {
#include "stdio.h"; X$ i/ G% M! J+ w" R5 n/ A4 n
#define TO_STR(s) #s#define COMB(str1,str2) str1##str20 A. r0 ]5 U7 h( P f, f
int main(int argc, char *argv[]){ int UART0= 115200;: X$ \5 K5 L0 v$ y# V: b1 z
printf("UART0=%d
4 T* u/ i( H! |& ]6 A/ `", COMB(UART, 0));//字符串合并为变量UART0 printf("%s
$ }, p% n# X5 `! {+ a5 Y/ P8 @9 B", TO_STR(3.14));//将数字变成字符串 }; a* ?5 w7 o
return 0;}, S) w$ G- A- W$ z% l/ n( E3 U
. }* E* o5 G* Y7
0 w8 O4 d5 d, Z' \3 Hvoid 与 void*关键字8 }- {8 v! E0 e
void表示的是无类型,不能声明变量或常量,但是可以把指针定义为void类型,如void* ptr。void* 指针可以指向任意类型的数据,在C语言指针操作中,任意类型的数据地址都可转为void* 指针。因为指针本质上都是unsigned int。
4 ^' n6 k+ ^1 B
$ P# Q# ^1 R- ?% b( K; k2 g常用的内存块操作库函数:
$ o! U1 u7 q# M+ |( p4 }
, ^: u6 r j! m* x: w' a& a' h0 g3 K& H! cvoid * memcpy( void *dest, const void *src, size_t len );void * memset( void *buffer, int c, size_t num);
* c: U9 u& ]; s% }8 H' A/ v数据指针为void* 类型,对传入任意类型数据的指针都可以操作。另外其中memcpy第二个参数,const现在也如前文所述,拷贝时对传入的原数据内容禁止修改。8 E! t& `* S; h% x# {
# }8 b: i- a4 S4 G8 z3 o
特殊说明,指针是不能使用sizeof求内容大小的,在ARM系统固定为int 4字节。对于函数无输入参数的,也尽量加上void,如下所示:
) G5 k7 |) {$ k* P2 b- o# R
: J! \& l S; k- c$ Cvoid fun(void);
: \1 f' A- m/ \
4 E0 k; B4 ]. I7 O" z8
; R; Z! ]5 I+ ^; X" Yweak关键字' n8 d" P( G2 l# A9 _3 h; }
一般简化定义如下所示:& {1 j! q5 v6 w {
6 {* N. w/ i! H! ], O#define _WEAK __attribute__((weak))$ ~, w- {; [4 P% y0 Q
函数名称前面加上__WEAK属性修饰符称为“弱函数”,类似C++的虚函数。链接时优先链接为非weak定义的函数,如果找不到则再链接带weak函数。
. I9 n' t4 s* S5 P4 x* x4 L. h, f9 W7 }: j
_WEAK void fun(void) { //do this} 0 N7 ]' b6 o% G! ?1 a* _& _
//不在同一个.c,两同名函数不能在同一个文件void fun(void) { //do that}( N8 j4 [* A8 p
这种自动选择的机制,在代码移植和多模块配合工作的场景下应用较多。例如前期移植代码,需要调用某个接口fun,但当前该接口不存在或者未移植完整使用,可以使用weak关键字定义为空函数先保证编译正常。: v, A/ U/ @" w2 _& ]$ X. `
$ x2 y% U7 \ s+ h
后续移植完成实现了fun,即软件中有2个fun函数没有任何错误,编译器自动会识别使用后者。当然也粗暴的#if 0屏蔽对fun的调用,但要确保后续记得放开。
( c# H9 C o6 |: _6 d5 @6 t8 [
; p' ^9 w9 f& h5 r5 P- i1 ^
wq3y0u5nfmz640330449.png
; E3 Z {- b3 j! b4 B往期推荐什么是内存碎片?
2 c. D; O3 @ M9 F( N详解UDS CAN诊断:什么是UDS(ISO 14229)诊断?8 p* V! \. l0 P' j
磁耦合共振无线供电装置4 c x' j* c; Q# f4 f
C语言:十六进制(HEX)和浮点类型(float、double)转换6 m% N! E& \0 R8 H. _8 o
HarmonyOS 分布式多端应用一站式开发平台(DevEco Studio 安装)4 c9 p0 p; h( F6 ?' O
( Y+ y& F1 t* x5 J$ c
3 ]+ c' R2 T4 e
er05lue2zg4640330549.jpg
( R+ |3 x% E S5 g( K* C
cnz4mopc0wc640330649.gif
2 p% X: y; U/ x
点击阅读原文,更精彩~ |
|