|

skk2rppqhah64024997041.gif
0 e4 j! M C: U: m9 ^$ w8 o
点击上方蓝色字体,关注我们# r. M5 c7 P1 C1 w9 ?" w
1 b; ~1 w5 E b: _6 l) P1 k8 p
1
. I; d, r+ m5 k. m: {9 y, gvolatile关键字4 e3 ^0 i- P" a, l
volatile是一个特征修饰符,提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,告诉编译器对该变量不做优化,都会直接从变量内存地址中读取数据,从而可以提供对特殊地址的稳定访问。" O1 L2 @* s8 N" r
: {/ i2 O4 [: H% k8 Y常用场景:中断服务与主程序共享变量。示例代码如下:$ n, A& j, K4 P) v: k% ~8 ]
( ?* {* Y8 b$ V( J4 d* X
//volatile uint8_t flag=1;uint8_t flag=1;+ f1 ]: P2 n+ p" ?! C* j8 A! |+ K
void test(void){ while(flag) { //do something }}
2 N2 p/ p. V' c2 I* e) S4 ^//interrupt service routinevoid isr_test(void){ flag=0;}# W9 g- k7 O) ]5 x$ U. @: y
如果没使用volatile定义flag,可能在优化后test陷入死循环,因为test里使用的flag并没修改它,开启优化后,编译器可能会固定从某个内存取值。- B/ w! y8 j6 E* U1 ]
1 ~4 o V1 z8 f4 p- t2 H2
6 X6 L. h: {. b5 w! m6 ?const关键字0 R2 e0 w$ p9 h2 v* _
const 是 constant 的缩写,意思是“恒定不变的”,它是定义常变量的关键字。
4 g6 m/ M: r. J+ K6 x/ L通常有4种用法。
3 @$ w& @7 e2 w( n& S e5 Y* F6 ^' B( W2 S, ^
( S/ ?6 C+ k4 g* M# x3 _1、修饰变量
% b2 d7 s _! @( }, U/ J
! ^4 L7 I6 D+ m" z" z9 W7 P2 x采用const修饰变量,即变量声明为只读,保护变量值以防被修改。$ l8 f' j$ {1 |' R, ^; B4 s
7 G& p5 l- q, C" f2 l
const int i = 1;或者int const i=1;& T2 `4 ~5 H9 m, [8 M
变量i具有只读特性,不能够被更改;若想对i重新赋值,如i = 10,属于错误操作。 i) ~& Q2 |& c! @# o/ D4 M
: q6 {* Z1 O; `* b W1 ^2 A8 c# W# H. B! M7 ~ Q
2、修饰数组
$ r G8 Y$ h1 U9 [% C' O3 R5 d' O. F' D- D& N
数组元素与变量类似,具有只读属性,不能被更改,一旦更改,编译时就会报错。
) {, u- S# w" ?/ B, T; ~8 s4 k1 @1 i) F0 N0 p3 D
const int array[5] = {1,2,3,4,5};array[0] = array[0]+1; //错误,array是只读的,禁止修改+ D' M0 B1 B" N$ w
使用大数组存储固定的信息,例如查表(表驱动法的键值表),可以使用const节省ram。编译器并不给普通const只读变量分配空间,而是将它们保存到符号表中,无需读写内存操作,程序执行效率也会提高。
1 A& j/ w! ?: ?- m6 e% t4 Q9 I2 I) b( ]1 r6 C
! j2 B3 A7 r5 l! L& K: m3 n, J
3、修饰指针
- A4 R/ L; L( _( F* Z- w0 m3 t; S3 e
$ Y9 q' L4 ?, B7 ZC语言中const修饰指针要特别注意,共有两种形式,一种是用来限定指向空间的值不能修改;另一种是限定指针不可更改。
: B' u# B8 c8 _8 i& |0 A. @
$ |' r5 q. p$ w2 T) ~1 w0 ?$ `- wint i = 1;int j = 2;+ A. l2 V+ n0 y
const int *p1 = &i;int* const p2 = &j;
" ~, _. U0 Y0 R9 A" L上面定义了两个指针p1和p2,区别是const后面是指针本身还是指向的内容。0 {+ e/ v4 S! C# a" H6 v
9 I1 x% G" S+ y在定义1中const限定的是*p1,即其指向空间的值不可改变,若改变其指向空间的值如*p1=10,则程序会报错;但p1的值是可以改变的,对p1重新赋值如p1=&k是没有任何问题的。
f+ S/ V3 H/ y$ o4 Z+ L) U4 j! _, n- V7 E+ C- j4 h) y) D" w0 N: N/ f
在定义2中const限定的是指针p2,若改变p2的值如p2=&k,程序将会报错;但*p2,即其所指向空间的值可以改变,如*p2=20是没有问题的,程序正常执行。6 U% G2 w( J1 e
. T; b2 I1 X! M8 {% P7 }0 g
j) n9 x3 n! `# c$ R0 @3 `
4、 修饰函数参数5 P6 L+ T: }7 x
const关键字修饰函数参数,对参数起限定作用,防止其在函数内部被修改,所限定的函数参数可以是普通变量,也可以是指针变量。( R4 o5 |' i5 x! [7 |) g' h# k
* L9 @$ e6 E1 [- [, K
void fun(const int i){ …… i++; //对i的值进行了修改,程序报错}
. i% d+ f& \" s' ^常用的函数如strlen。3 `" r2 G L* Y. V
8 C v3 B$ s& a# ]& V- B3 vsize_t strlen(const char *string);
8 W3 \ {% [) a2 i7 rconst在库函数中使用非常普遍,是一种自我保护的安全编码思维。
0 q9 d* f: Z6 ^& _$ @3
: N3 N6 d$ n5 Lstatic关键字
7 R' P" @9 o0 j6 o1、static修饰全局变量,该变量只在本文件内被访问,不能在其他文件被直接访问。& H/ G6 M3 {$ A/ i5 ] k
. G" o# x: l& U' a2、static修饰函数,该函数只能在本文件内被访问,不能被其他文件访问。但是可以通过嵌套的方式调用,变相的封装的表现。5 g9 H! j5 v- R
9 j* O4 K) ~" D; r W3、static修饰局部变量,更改该局部变量的生命周期。
+ H3 n' g$ {- N0 h, ]4 C7 ^( G生命周期:将临时变量的生命周期变成全局变量的生命周期。作用域不变:作用域仍然是在本代码块内。+ _* a/ t7 x2 k; m
" R4 t8 v* ^; c* m+ S v
48 g( k' L) ^& U
struct与union5 G4 ?: M K: V. g4 x
可以使用struct结构体来存放一组不同类型的数据。% l6 D- C0 w6 ?: V# |
% `7 Y; n% }5 G) M% E4 r% X$ W: Vstruct 结构体名{ 结构体所包含的变量或数组};
( ]' w' t7 e: r$ K: [( q结构体是一种集合,它里面包含了多个变量或数组,它们的类型可以相同,也可以不同,每个这样的变量或数组都称为结构体的成员,通常我们使用结构体定义和解析协议,如下所示:
2 t# ?0 Y" S% q8 M" l: g4 v
( V( E! t; e" P3 v- {3 K% T1 n// 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()7 k/ G W" g Y. u( J
union共用体关键字,定义union下面的成员变量共享一块内存,每一个成员在任一时刻有且只有一个成员使用此块内存。
* [# U1 g& v/ M8 I
6 G. t, }7 \. Y1 S& Dunion 共用体名{ 成员列表};
+ [) D( { l0 J& m& J+ v2 h4 V- D结构体和共用体的区别在于:结构体的各个成员会占用不同的内存,互相之间没有影响;而共用体的所有成员占用同一段内存,修改一个成员会影响其余所有成员。3 y7 Q$ f( n1 D) C3 G1 c7 {
通常使用共用体做一些标志位操作,例如以下示例,可以非常灵活的访问Val中的bit位。8 V; T( }" k1 J
6 T3 i4 G7 }! r+ L3 e: J& ntypedef 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;
. Y( M7 h! w& m6 ^- f, r或者使用共用体实现单字节与多字节的转化和拼接,如下所示:
2 O* G# N$ I: v" z- |+ t9 I' W; V) G9 i: ?
#include "stdio.h"4 f" H: S6 ^# X( B
typedef struct{ union { struct { unsigned char low; unsigned char high; }; unsigned short result; };}test_t;$ w3 x1 s* ?* p# W9 G J
int main(int argc, char *argv[]){ test_t hello;* _/ F5 U1 I4 z& e; X
hello.high=0x12; hello.low=0x34;
) c. o* F7 y/ m. C1 k ?- ^2 d6 K printf("result=%04X\r
$ m& Z" H- Z0 a",hello.result);//输出 result=1234
& X3 C8 P# Z1 y8 D3 v- e1 \ return 0;}
* A+ }7 ]3 O. W7 V7 W# [1 Y) q- e6 |" V" d) L( b& e
5
, l# ^+ H) d, S0 r1 l$ `6 H预定义标识符
/ q" y: v" S% A8 a E- I1 G一般编译器都支持预定义标识符,这些标识符结合printf等打印信息帮助程序员调试程序是非常有用的,一般编译器会自动根据用户指定完成替换和处理。
6 t+ z- @/ P& z0 C5 t+ ?8 e3 e# r6 A: {& @- c7 N
常用的预定义标识符如下所示:
! z0 A0 ~& R1 }7 P6 C' R+ f; a/ O+ Z) t t6 H$ m9 w
__FILE__ //表示编译的源文件名__LINE__ //表示当前文件的行号__FUNCTION__ //表示函数名__DATE__ //表示编译日期__TIME__ //表示编译时间* [$ E6 }* A- x5 R7 R' s
在Debug打印日志时候经常会用到,如下所示:
6 K4 x4 P8 N3 l9 p/ }3 j7 y6 k4 _7 G# _
printf("file:%s,line:%d,date:%s,time:%s",__FILE__,__LINE__,__DATE__,__TIME__);
/ n* U$ V2 T3 Q5 K1 X a1 K+ j8 ?
; ? S# o) |7 T6
) D" C0 m, V- N- j#与##
" {) u# t( }* l+ Z; m/ Q#:是一种运算符,用于带参宏的文本替换,将跟在后面的参数转成一个字符串常量。
& d0 m& U5 Z/ p0 |! ]
( D& P% K2 J% t##:是一种运算符,是将两个运算对象连接在一起,也只能出现在带参宏定义的文本替换中。. x6 |" U: S4 L- O6 C
' {( T0 J. Y4 Q% B% l6 _( F
#include "stdio.h"9 n) d% [+ D& Y1 v, U
#define TO_STR(s) #s#define COMB(str1,str2) str1##str2
2 h/ Q$ I2 V! z1 eint main(int argc, char *argv[]){ int UART0= 115200;
l& K7 Y q9 g6 F' P; l printf("UART0=%d
6 Y8 _3 B( Q/ W", COMB(UART, 0));//字符串合并为变量UART0 printf("%s
( T5 J2 A2 N& M ^5 Q; C", TO_STR(3.14));//将数字变成字符串, D3 `$ X% ]" \& f. w& j
return 0;}. P# d, [+ J& f% k, n! O( g0 S! C
7 R, ?4 E2 i3 V! R+ f7 u
7
2 B! `) O- V; }: avoid 与 void*关键字
, \6 b6 ]5 ? k2 B. N* m5 Q% |void表示的是无类型,不能声明变量或常量,但是可以把指针定义为void类型,如void* ptr。void* 指针可以指向任意类型的数据,在C语言指针操作中,任意类型的数据地址都可转为void* 指针。因为指针本质上都是unsigned int。
+ c3 K& b' l# K/ ?6 x: z, N
! D; {6 v2 O9 `常用的内存块操作库函数:
" n+ j$ s! j- E) a! ^0 h! w
8 L' G6 W0 P8 }- q) ?void * memcpy( void *dest, const void *src, size_t len );void * memset( void *buffer, int c, size_t num);2 D9 o, l- c) S: Y8 h- S4 \
数据指针为void* 类型,对传入任意类型数据的指针都可以操作。另外其中memcpy第二个参数,const现在也如前文所述,拷贝时对传入的原数据内容禁止修改。* P/ A- b* p7 [5 l# I/ E; }0 I
" O8 Z: F( I; i/ {% K) w& X- [
特殊说明,指针是不能使用sizeof求内容大小的,在ARM系统固定为int 4字节。对于函数无输入参数的,也尽量加上void,如下所示:
6 p5 z( f; }8 f, [( c- j' D. S
* s8 Y: g0 n4 Z, O- Tvoid fun(void);
9 q" P7 p, G7 {; F- I) g3 P7 s) Y) G& I8 i: u( o1 o2 ?6 c) ~
8
6 W7 `/ x4 [ T/ K0 z6 A8 \weak关键字
& z4 K3 o8 i/ I2 `5 F" h一般简化定义如下所示:
) t! q3 i \# L: X% V1 Y( X) Y% u- V/ Q; H0 o ?5 Y3 V, n
#define _WEAK __attribute__((weak))
* R6 Z7 U3 j; A8 N& ^; W- p函数名称前面加上__WEAK属性修饰符称为“弱函数”,类似C++的虚函数。链接时优先链接为非weak定义的函数,如果找不到则再链接带weak函数。
" z: O% b: X. x8 Y2 a* c# _/ t' T, W9 s7 I6 C
_WEAK void fun(void) { //do this} ( S, J; [; B6 Q- n( Z% M
//不在同一个.c,两同名函数不能在同一个文件void fun(void) { //do that}
2 L7 Q5 H K0 g i4 e& L这种自动选择的机制,在代码移植和多模块配合工作的场景下应用较多。例如前期移植代码,需要调用某个接口fun,但当前该接口不存在或者未移植完整使用,可以使用weak关键字定义为空函数先保证编译正常。- y0 G7 j2 }6 E
) _" v# I4 f M, {4 J8 E后续移植完成实现了fun,即软件中有2个fun函数没有任何错误,编译器自动会识别使用后者。当然也粗暴的#if 0屏蔽对fun的调用,但要确保后续记得放开。# o& ^1 m- s7 k: J5 l
5 p$ f% z/ M# n4 o6 Y& t
z3iwwlzuh1q64024997141.png
3 `4 `7 b1 a$ u
往期推荐什么是内存碎片?
1 H' z0 C! l0 E X2 [* ^详解UDS CAN诊断:什么是UDS(ISO 14229)诊断?
0 _ |+ q$ g; _磁耦合共振无线供电装置
& p% g! o) ]6 Y! g MC语言:十六进制(HEX)和浮点类型(float、double)转换
4 N& I4 l" y& W5 L8 y- Q, a: B" zHarmonyOS 分布式多端应用一站式开发平台(DevEco Studio 安装)
2 ?! H' u+ A8 ^$ O9 K# V$ f- ~
( k. ^$ K; E" y5 X. R$ L
1 t3 i$ o& Q$ _3 q, H2 p l/ B
fuk2mic3ad164024997241.jpg
z& f! T. e0 `( \! T
rx0bwuyifqw64024997341.gif
3 Z5 N* n5 v# p: T3 e9 n点击阅读原文,更精彩~ |
|