电子产业一站式赋能平台

PCB联盟网

搜索
查看: 177|回复: 0
收起左侧

嵌入式软件开发常用的关键字和运算符

[复制链接]

1002

主题

1002

帖子

8854

积分

高级会员

Rank: 5Rank: 5

积分
8854
发表于 2023-12-4 12:00:00 | 显示全部楼层 |阅读模式

0rmtcxkrehg64015933843.gif

0rmtcxkrehg64015933843.gif
8 m: A4 I. L7 M) k& P; y) L
点击上方蓝色字体,关注我们' p4 ]; b6 l4 G5 {% _! S) m! u
# X/ m8 C' C$ I! _
1( V# p0 B4 a( A; g. K% v) R- [' s
volatile关键字
/ f: L9 o3 b9 B- P& Tvolatile是一个特征修饰符,提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,告诉编译器对该变量不做优化,都会直接从变量内存地址中读取数据,从而可以提供对特殊地址的稳定访问。+ m. D7 U4 h! L. M2 o& @7 X4 {; v
2 U0 T- {9 x1 G/ H- f6 V$ N
常用场景:中断服务与主程序共享变量。示例代码如下:9 Z  @& N3 _+ V+ ^6 w# G) m: N2 v' W
: U, e( Z4 R& {$ k
  • //volatile uint8_t flag=1;uint8_t flag=1;( i# \+ V8 a' J* K$ g( J& Y0 C
    void test(void){    while(flag)    {        //do something    }}
    , k! h# T  \7 N//interrupt service routinevoid isr_test(void){    flag=0;}3 i* K& s4 l6 ^! d1 x4 g: _* ]
    如果没使用volatile定义flag,可能在优化后test陷入死循环,因为test里使用的flag并没修改它,开启优化后,编译器可能会固定从某个内存取值。
    8 \$ a1 A( `) X. u, c3 J' O" R0 I' L: q+ l# J: c  i# ^  J! H; u$ C
    2
    * H7 I( t$ q; p; U5 p# H% mconst关键字
    9 I$ j" u3 h  @3 b, Q+ e  Z/ iconst 是 constant 的缩写,意思是“恒定不变的”,它是定义常变量的关键字。
    + R. v( c2 R) p1 R5 [3 J6 J通常有4种用法。
    & ~+ o5 m& a! o/ f4 F/ [4 `5 i  G" b) ]" b( m' E9 B5 A1 V; M# Z0 s
    ( Y' U) A- [: M& h7 l6 ?$ g; o
    1、修饰变量$ Z8 I' Z+ R# z" q" w. s. h  K

    4 {: h' D( G- }- p采用const修饰变量,即变量声明为只读,保护变量值以防被修改。
    , }5 e5 H4 H( W7 R" E' T. i  Z2 c6 S2 F1 H  e
  • const int i = 1;或者int const i=1;" i1 c0 ]8 _" u  T
    变量i具有只读特性,不能够被更改;若想对i重新赋值,如i = 10,属于错误操作。
    3 F1 t$ M. @. z: {1 t: j
    ! ?) \# b/ D. c

    - m2 X( v+ m9 t, s8 q2、修饰数组
    & ]& |5 c% _! B& ^) |) n3 j) C0 z0 o' u0 l/ @
    数组元素与变量类似,具有只读属性,不能被更改,一旦更改,编译时就会报错。# H& g$ a4 [, Y7 B3 k
    " I+ V) X  b8 Q2 I5 z" M& o- ~
  • const int array[5] = {1,2,3,4,5};array[0] = array[0]+1; //错误,array是只读的,禁止修改
    9 }0 r- a2 t9 K0 Y, u使用大数组存储固定的信息,例如查表(表驱动法的键值表),可以使用const节省ram。编译器并不给普通const只读变量分配空间,而是将它们保存到符号表中,无需读写内存操作,程序执行效率也会提高。3 X4 o( Z( F1 e* H, R! d& ~
    6 \. g6 ]: ^: y2 T* k! v8 n; g
    ! q& M( Q; \5 C
    3、修饰指针1 y) `# O  ]4 Z# B/ i; {8 z
    + V2 G4 G, B0 i5 ]
    C语言中const修饰指针要特别注意,共有两种形式,一种是用来限定指向空间的值不能修改;另一种是限定指针不可更改。
    2 q# U3 K0 ?1 c6 a
    7 \0 z2 K; _* w9 d; i
  • int i = 1;int j = 2;6 C0 s+ D/ I$ s' V  o9 w+ n
    const int *p1 = &i;int* const p2 = &j;+ Q2 c" K* p1 d/ G
    上面定义了两个指针p1和p2,区别是const后面是指针本身还是指向的内容。  ~" p! y; ~/ o) D" [

    , Q3 A# i& K& ?在定义1中const限定的是*p1,即其指向空间的值不可改变,若改变其指向空间的值如*p1=10,则程序会报错;但p1的值是可以改变的,对p1重新赋值如p1=&k是没有任何问题的。* ?3 b# _/ h8 x6 p% q

    % k* Y' i2 {7 w( l% P% u- y0 @在定义2中const限定的是指针p2,若改变p2的值如p2=&k,程序将会报错;但*p2,即其所指向空间的值可以改变,如*p2=20是没有问题的,程序正常执行。* |- c; a* F; m2 d  e
    1 E% ]! F8 K5 f) Z* W- s

    / G& b  V' p: y* K4、 修饰函数参数
    * J! I6 m; a/ {8 {  Yconst关键字修饰函数参数,对参数起限定作用,防止其在函数内部被修改,所限定的函数参数可以是普通变量,也可以是指针变量。
    8 F) U3 P# o9 k( ~. g' z# ]. ]/ L0 x8 y( [% m- V' |5 Y
  • void fun(const int i){    ……    i++; //对i的值进行了修改,程序报错}( {% n, T  b9 S8 E4 Y
    常用的函数如strlen。. D- X. Q% I; Z; Z, R# s) g7 F! g

    1 t, p: n+ o9 n5 j
  • size_t strlen(const char *string);
    2 x4 d- o# z6 J: D2 }const在库函数中使用非常普遍,是一种自我保护的安全编码思维。
    : ~' M) Z9 _$ ~$ m3 H3. S" C4 z  n* z0 ^1 g( h
    static关键字! I" c* p" j! A% Q- C/ Z* P. g- V
    1、static修饰全局变量,该变量只在本文件内被访问,不能在其他文件被直接访问。
    - p; U# d1 b& U( \
    8 \# V2 b# ^- g- n" E6 {2、static修饰函数,该函数只能在本文件内被访问,不能被其他文件访问。但是可以通过嵌套的方式调用,变相的封装的表现。: T/ i5 }+ g: R
    ( R% _3 v5 v) @0 X1 ]3 g1 P
    3、static修饰局部变量,更改该局部变量的生命周期。; W+ c/ O* Z0 P; D  R2 E4 [2 `2 H
  • 生命周期:将临时变量的生命周期变成全局变量的生命周期。
  • 作用域不变:作用域仍然是在本代码块内。
    * I. \+ X& n) t! o* M$ o

    # L3 J( M7 n0 V1 d+ H) F: d49 C& M" y0 g1 B6 u' ?* |
    struct与union& b# A: {. f/ p' K# d
    可以使用struct结构体来存放一组不同类型的数据。
    : W, A8 |( A7 i$ f/ T0 L: F+ \& [" o6 z& G7 ^  Z
  • struct 结构体名{    结构体所包含的变量或数组};
    ! R0 |6 m! F" b, P1 `: {结构体是一种集合,它里面包含了多个变量或数组,它们的类型可以相同,也可以不同,每个这样的变量或数组都称为结构体的成员,通常我们使用结构体定义和解析协议,如下所示:
    5 i- A1 X" @, S2 q. @+ V/ l; N+ E" C
    : @- D+ V5 l; B  |' {/ E0 i  T
  • // 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()
    ) I' f8 ?. F6 V' P( n/ Gunion共用体关键字,定义union下面的成员变量共享一块内存,每一个成员在任一时刻有且只有一个成员使用此块内存。
    ! e5 ], l4 b/ F6 p& S  Y9 L8 m6 \  `, y! a
  • union 共用体名{    成员列表};! I. j+ j4 w$ {" u
    结构体和共用体的区别在于:结构体的各个成员会占用不同的内存,互相之间没有影响;而共用体的所有成员占用同一段内存,修改一个成员会影响其余所有成员。
    4 ^* r# `( M1 k3 t7 F$ J0 m" W通常使用共用体做一些标志位操作,例如以下示例,可以非常灵活的访问Val中的bit位。
    " ~7 g6 @- U" S# n6 X. s
      R% H0 L; d4 ]( F# h% f8 }
  • 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;
    5 i0 j+ Y/ Q1 j  K2 x- x或者使用共用体实现单字节与多字节的转化和拼接,如下所示:6 M: D* a# u2 M; U  i
    2 S, d9 p# C: r. e. b- |4 s
  • #include "stdio.h"
    ( `8 l# `8 P9 [3 \# B# U+ k) j; gtypedef struct{    union    {        struct        {            unsigned char low;            unsigned char high;        };        unsigned short result;    };}test_t;
    # u- [. ^6 n" sint main(int argc, char *argv[]){    test_t hello;
    ) ?4 f+ V7 Y( m9 H% W, i    hello.high=0x12;    hello.low=0x34;% o( U4 C  c) O+ o, h- F- V
        printf("result=%04X\r
    1 S6 n/ i  N- X. s9 t$ q% ~2 M1 m",hello.result);//输出 result=1234 0 U, \/ F/ j. ~& s8 q, A1 {
        return 0;}$ c; c4 i+ @; T; U  n; e  q; e0 w' w

    3 e: j: o' R1 M2 d* a0 O7 R: Z50 y! w. W3 _" g- z! O
    预定义标识符) ]( a: _0 {2 `/ T1 W6 {: S
    一般编译器都支持预定义标识符,这些标识符结合printf等打印信息帮助程序员调试程序是非常有用的,一般编译器会自动根据用户指定完成替换和处理。
    ' f0 Y9 o) b: p$ ]7 o2 j* @( D0 i  ]* W; C8 F
    常用的预定义标识符如下所示:
    ( b. B! i& }4 x2 ?" G9 F, C8 \" J6 ^( y" D- t
  • __FILE__    //表示编译的源文件名__LINE__   //表示当前文件的行号__FUNCTION__  //表示函数名__DATE__  //表示编译日期__TIME__   //表示编译时间- X/ C! u/ _3 v- A5 @8 m" N3 V& G
    在Debug打印日志时候经常会用到,如下所示:
    8 F( ]. B( t! ^  Z/ ~4 M* E8 }0 e2 X0 r  f1 y
  • printf("file:%s,line:%d,date:%s,time:%s",__FILE__,__LINE__,__DATE__,__TIME__);5 _1 u/ ?" V* Z
    ! _2 Y% h0 d" E: w" b: O6 B! y
    6
    & l7 ^' j# l, l! C#与##) v; l, z2 K6 F& V8 k5 m
    #:是一种运算符,用于带参宏的文本替换,将跟在后面的参数转成一个字符串常量。
    $ f' `- o. J4 |
    5 e* R! x8 o+ f1 }) ?% Z' i; m##:是一种运算符,是将两个运算对象连接在一起,也只能出现在带参宏定义的文本替换中。) g% k2 t' o0 g  _6 X" h6 V. t

    5 w" w7 |, `3 z) k+ y* Z; u
  • #include "stdio.h"
    6 W' G  ^6 K4 ]7 Q6 m3 v#define TO_STR(s) #s#define COMB(str1,str2) str1##str2
    # j6 d7 L) n& I$ \$ {int main(int argc, char *argv[]){    int UART0= 115200;3 u7 S  M$ ~7 k: e
        printf("UART0=%d" ~( X7 |- l/ }; W- y8 |  ^, M
    ", COMB(UART, 0));//字符串合并为变量UART0    printf("%s) y# F+ L) K) v3 H; x( w- ~; h$ b: R
    ", TO_STR(3.14));//将数字变成字符串+ f% J. w, d" Q
        return 0;}* _9 x) _- u& z8 c* k& r/ v$ G

    % \3 N# A( G2 Z/ y3 \7
    0 W; i6 p5 q6 w4 N+ k9 o) ?void 与 void*关键字
    ! a3 ^6 ?; r7 O- tvoid表示的是无类型,不能声明变量或常量,但是可以把指针定义为void类型,如void* ptr。void* 指针可以指向任意类型的数据,在C语言指针操作中,任意类型的数据地址都可转为void* 指针。因为指针本质上都是unsigned int。
    ) `& [' ?5 L' q2 X. ~5 M
    8 G. R2 q7 b! r( N, O2 T常用的内存块操作库函数:
    2 R# e+ t" Q0 }) n4 B
      _7 f1 z2 k, i8 L- [
  • void * memcpy( void *dest, const void *src, size_t len );void * memset( void *buffer, int c, size_t num);9 n, ?1 @  [9 ^$ I  C
    数据指针为void* 类型,对传入任意类型数据的指针都可以操作。另外其中memcpy第二个参数,const现在也如前文所述,拷贝时对传入的原数据内容禁止修改。4 J  \  e# y1 X: h5 E+ I

    ; L& N( E- w2 M1 p特殊说明,指针是不能使用sizeof求内容大小的,在ARM系统固定为int 4字节。对于函数无输入参数的,也尽量加上void,如下所示:( C/ Y# b  b8 k9 c9 I" N: [

    3 n) z2 ]5 h( k: D, j- `
  • void fun(void);
    ! D* s1 e3 r% a& A4 W1 D: |: L: G* m0 r, V  e" @( s! i, M2 F
    8# g; B; ]- Y! B+ v2 U
    weak关键字
    & W5 n& F2 ~, ^6 S1 S- e一般简化定义如下所示:
    * y4 W& q+ X) J* g+ y4 {, N4 F
  • #define _WEAK __attribute__((weak))
    9 Z9 _% r. u9 v3 D函数名称前面加上__WEAK属性修饰符称为“弱函数”,类似C++的虚函数。链接时优先链接为非weak定义的函数,如果找不到则再链接带weak函数。
    # W8 X3 j' k7 a
    6 Y0 X4 e! V6 G# G
  • _WEAK void fun(void)  {      //do this}  
    + H  c$ @9 s9 O9 o: n. r) v2 E//不在同一个.c,两同名函数不能在同一个文件void fun(void)  {      //do that}. c% F2 m2 _- D; y
    这种自动选择的机制,在代码移植和多模块配合工作的场景下应用较多。例如前期移植代码,需要调用某个接口fun,但当前该接口不存在或者未移植完整使用,可以使用weak关键字定义为空函数先保证编译正常。; J6 r# j, N' W: B. z

    3 D$ E) Y' L  {( b4 n+ H后续移植完成实现了fun,即软件中有2个fun函数没有任何错误,编译器自动会识别使用后者。当然也粗暴的#if 0屏蔽对fun的调用,但要确保后续记得放开。2 ~! U) a  d, R3 b# b# e* o7 G

    ) o1 z( w1 d* H% h8 M8 |- f# |

    gsy3dti3fgp64015933943.png

    gsy3dti3fgp64015933943.png

    : W; W" e8 X* L( c0 W0 m往期推荐什么是内存碎片?! n( X# K  V4 m$ X2 t6 _
    详解UDS CAN诊断:什么是UDS(ISO 14229)诊断?
    * r1 R8 D; O: B& X  I磁耦合共振无线供电装置7 b, X1 F1 B2 g
    C语言:十六进制(HEX)和浮点类型(float、double)转换# t! ?/ T" S, y. f
    HarmonyOS 分布式多端应用一站式开发平台(DevEco Studio 安装)& e" {. o: `, z. W* |. m* ?" _

    9 X% J$ Z. `9 V. c  |

    4 U; u* T/ ~& W1 h2 e% s( n, D2 [

    h1kyevj50zz64015934043.jpg

    h1kyevj50zz64015934043.jpg

    6 w. d6 @7 c6 g; h! @6 I, |

    dnweybl2r1c64015934143.gif

    dnweybl2r1c64015934143.gif

    . ~/ {  ^5 a, w6 E  k: K$ W4 X点击阅读原文,更精彩~
  • 回复

    使用道具 举报

    发表回复

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则


    联系客服 关注微信 下载APP 返回顶部 返回列表