电子产业一站式赋能平台

PCB联盟网

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

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

[复制链接]

840

主题

840

帖子

6491

积分

高级会员

Rank: 5Rank: 5

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

0rmtcxkrehg64015933843.gif

0rmtcxkrehg64015933843.gif

( h7 P# t5 i9 k* y* D& K点击上方蓝色字体,关注我们6 v. Z( M) T; J: _
8 Z: |5 G/ u+ q
1
# A- t$ ]# t' |0 Z- n4 L" _volatile关键字8 z; \7 y, ^$ }
volatile是一个特征修饰符,提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,告诉编译器对该变量不做优化,都会直接从变量内存地址中读取数据,从而可以提供对特殊地址的稳定访问。. y5 W+ |  w( L! R$ V5 e
7 v7 l1 R& Y" S: E. ]
常用场景:中断服务与主程序共享变量。示例代码如下:
3 @6 ]; e) e# l2 [1 A% d
) V9 E3 z- e! n* f# O' ^, H- m
  • //volatile uint8_t flag=1;uint8_t flag=1;) k' L7 M& q; D& D4 P- O
    void test(void){    while(flag)    {        //do something    }}
    # F+ c+ {: l: y6 {//interrupt service routinevoid isr_test(void){    flag=0;}
    " ^% K" w: V( K# c0 O如果没使用volatile定义flag,可能在优化后test陷入死循环,因为test里使用的flag并没修改它,开启优化后,编译器可能会固定从某个内存取值。0 S6 q- }( S. b9 ~

    4 w9 u2 B) G+ p' I! G% Z2
    5 Y( q2 b9 \. t- O* Jconst关键字
    ; e, s& f* f* Nconst 是 constant 的缩写,意思是“恒定不变的”,它是定义常变量的关键字。& s% S, m5 {. E; G
    通常有4种用法。
    ; q9 h& f7 r( d* X( v
    8 ~+ e7 |# P% i) }" o; N

    " l: z- ]# ~! T3 ?, p, F1、修饰变量
    & D8 ~! T( ^5 w6 |$ @1 k+ ~
    ' R9 ?+ k5 N, ~: n采用const修饰变量,即变量声明为只读,保护变量值以防被修改。
    - [8 j1 Q  Y2 Q! r" n% @
    8 ^5 ?4 Q5 N" x7 B# v7 M
  • const int i = 1;或者int const i=1;
    1 i, E+ G8 g+ ]9 w* j9 d变量i具有只读特性,不能够被更改;若想对i重新赋值,如i = 10,属于错误操作。
      ]/ k8 k/ Q( `2 C1 b- h* B% P' q% _* N/ L; H3 O/ j; Q1 x
    / |2 h2 _! a- H  ~$ o2 I& Q* Q
    2、修饰数组
    6 I! z" Z, |: E7 c
    5 k' _2 T4 F) g) D0 r数组元素与变量类似,具有只读属性,不能被更改,一旦更改,编译时就会报错。
    : s, k$ Q. s# L8 j# C- h* a. |$ i' h% Y) I. o" U
  • const int array[5] = {1,2,3,4,5};array[0] = array[0]+1; //错误,array是只读的,禁止修改
    . x4 ]1 G5 j5 a使用大数组存储固定的信息,例如查表(表驱动法的键值表),可以使用const节省ram。编译器并不给普通const只读变量分配空间,而是将它们保存到符号表中,无需读写内存操作,程序执行效率也会提高。
    4 _7 R$ g3 G1 c+ [3 v/ D9 Y, G: S# z- V& w" f2 S+ \! I

    $ B/ `, e$ {! F: O% t5 T& ^! m3、修饰指针( p4 N- k9 H! ?  [
    7 k" L* Q) ]8 l# t: l
    C语言中const修饰指针要特别注意,共有两种形式,一种是用来限定指向空间的值不能修改;另一种是限定指针不可更改。
    8 E4 b' U1 z8 @) V) k' j
    - q1 ^9 o; |% l+ P' A- T8 ^
  • int i = 1;int j = 2;% p! b2 S# D: x  e
    const int *p1 = &i;int* const p2 = &j;
    ) H& a2 L; d/ `" B' L0 j: [上面定义了两个指针p1和p2,区别是const后面是指针本身还是指向的内容。
    1 |' p  M/ Y9 j* A5 }$ o  }' {/ k0 L& V6 Q, g
    在定义1中const限定的是*p1,即其指向空间的值不可改变,若改变其指向空间的值如*p1=10,则程序会报错;但p1的值是可以改变的,对p1重新赋值如p1=&k是没有任何问题的。# l3 @5 g2 o3 t, f
    & r8 f  w, W) H  F, q) O% L0 H- i
    在定义2中const限定的是指针p2,若改变p2的值如p2=&k,程序将会报错;但*p2,即其所指向空间的值可以改变,如*p2=20是没有问题的,程序正常执行。
    8 y6 s/ ]6 a% n# k' T! D
    " }8 F+ L- g4 Q6 i) t( a
    $ R" K2 _3 B( `" [9 z
    4、 修饰函数参数
    " r1 U6 K7 S2 v- G4 b. _const关键字修饰函数参数,对参数起限定作用,防止其在函数内部被修改,所限定的函数参数可以是普通变量,也可以是指针变量。. e6 Z+ m; \2 f" B3 T' D& u; ^

    , N* p$ |, F9 `, C. @, e$ {$ \3 {
  • void fun(const int i){    ……    i++; //对i的值进行了修改,程序报错}
    $ b; j3 H; Y3 {+ Y' B常用的函数如strlen。4 O9 o  m, C7 h6 ?8 w. }0 }( [
    7 p. P  Q6 Z9 e8 I& V! a" I: Q- g
  • size_t strlen(const char *string);% P5 s1 |- [2 g9 k; V
    const在库函数中使用非常普遍,是一种自我保护的安全编码思维。
    : J: B7 t3 R8 p0 w0 y! O  _3
    + b2 o  V7 y9 h" N' Fstatic关键字
    ! i6 L* T3 T; @# t1、static修饰全局变量,该变量只在本文件内被访问,不能在其他文件被直接访问。+ p. s2 L  M9 e2 i, S
    4 o9 b9 E1 c% x: I
    2、static修饰函数,该函数只能在本文件内被访问,不能被其他文件访问。但是可以通过嵌套的方式调用,变相的封装的表现。5 r6 S  D. @# C' T% r- J

    , Z  e$ Q1 O1 w3、static修饰局部变量,更改该局部变量的生命周期。
    ' H2 u1 M" [" D: ]8 U
  • 生命周期:将临时变量的生命周期变成全局变量的生命周期。
  • 作用域不变:作用域仍然是在本代码块内。* U7 j# v, g1 Q/ c& Z# S
    5 |) Y# z0 e2 C+ h* L) l
    47 f+ R& {% W$ ^, D! D2 x
    struct与union! y( j2 P! d* W2 ^+ S
    可以使用struct结构体来存放一组不同类型的数据。4 C8 k5 ]; @; `

    0 \  F6 o. R) ?
  • struct 结构体名{    结构体所包含的变量或数组};
    5 J! R. p  \/ l& H8 G# f结构体是一种集合,它里面包含了多个变量或数组,它们的类型可以相同,也可以不同,每个这样的变量或数组都称为结构体的成员,通常我们使用结构体定义和解析协议,如下所示:' b3 p0 e0 z8 @) F3 [# ]- q0 r& I
    2 U) G* E7 g0 W7 q0 h
  • // 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()
    2 g$ V$ f5 b- K9 Y  T9 B2 hunion共用体关键字,定义union下面的成员变量共享一块内存,每一个成员在任一时刻有且只有一个成员使用此块内存。
    - {, v% |* U% N$ m0 z" j& _& V
    / W% [' p- L* N9 M  l+ Z% p
  • union 共用体名{    成员列表};" e0 R" s  e( w  |" h
    结构体和共用体的区别在于:结构体的各个成员会占用不同的内存,互相之间没有影响;而共用体的所有成员占用同一段内存,修改一个成员会影响其余所有成员。
    * Y8 j+ ]4 p) \& E1 N通常使用共用体做一些标志位操作,例如以下示例,可以非常灵活的访问Val中的bit位。- Z( A1 h. Z' U, y: S0 v! `+ W
    1 L) q+ k  L% B" R7 f
  • 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 V4 L0 Q7 h# K1 @0 x9 s! i( |
    或者使用共用体实现单字节与多字节的转化和拼接,如下所示:
    % }/ @# e0 F! B5 L% z6 t& p" O$ m" r8 E6 g
  • #include "stdio.h"! T7 P& D. _; W3 W
    typedef struct{    union    {        struct        {            unsigned char low;            unsigned char high;        };        unsigned short result;    };}test_t;# w; ]+ A& u( W. Q
    int main(int argc, char *argv[]){    test_t hello;. N5 Q: J  ?5 Z* B7 y4 h4 G: V
        hello.high=0x12;    hello.low=0x34;
    ! K8 B! K! C0 A    printf("result=%04X\r8 q5 Y' d, [% `. A1 ]5 G
    ",hello.result);//输出 result=1234
    4 v! y# Y. l/ `7 j    return 0;}
    : y$ Q. g: ]3 Y  }7 C
    7 k1 ~7 t0 _- a8 K5 o; w. W# l56 W8 J4 }8 W  P; E+ e6 X6 ?
    预定义标识符, r/ r5 C2 |- E6 V& G3 \
    一般编译器都支持预定义标识符,这些标识符结合printf等打印信息帮助程序员调试程序是非常有用的,一般编译器会自动根据用户指定完成替换和处理。
    - b4 M) b* _% R& i1 ~$ i8 @* v
    5 G3 ~% x, F7 F. {+ H- D/ n4 @常用的预定义标识符如下所示:$ c( d$ W+ [6 U( r( J. h5 T

    3 S& W+ p  w& h3 p
  • __FILE__    //表示编译的源文件名__LINE__   //表示当前文件的行号__FUNCTION__  //表示函数名__DATE__  //表示编译日期__TIME__   //表示编译时间
    1 v" B* S7 ~1 i在Debug打印日志时候经常会用到,如下所示:
    2 t8 ^6 F$ S- G: Z2 G9 N% D2 h: U5 _) ^* R- n  u* M4 `  w
  • printf("file:%s,line:%d,date:%s,time:%s",__FILE__,__LINE__,__DATE__,__TIME__);) |6 n5 B  l$ U  W, L7 f
      v5 t2 Z( f) J/ S* A
    6
    0 q9 {2 n, V- l- N! d: p  f: j0 O& U3 d+ |' `#与##- D1 E- d, A( R6 c2 i$ o& a
    #:是一种运算符,用于带参宏的文本替换,将跟在后面的参数转成一个字符串常量。
    * `) h. G  w. T7 f
    9 [9 w1 R- [- C4 Q; G9 E8 n' P7 s##:是一种运算符,是将两个运算对象连接在一起,也只能出现在带参宏定义的文本替换中。
    ' x" Z/ }1 h& A" h! V- ^
    # ~- S6 }# I8 z! l9 g
  • #include "stdio.h"4 W) s# j6 y, \" H/ }: D) r
    #define TO_STR(s) #s#define COMB(str1,str2) str1##str24 }3 t2 }/ E+ k% y* }0 b8 G4 R# c, x
    int main(int argc, char *argv[]){    int UART0= 115200;
    % w# y. ?. ~& t    printf("UART0=%d  {8 X# X. b$ L0 }3 |# s
    ", COMB(UART, 0));//字符串合并为变量UART0    printf("%s
    5 D  ~0 D/ c5 q; f, t", TO_STR(3.14));//将数字变成字符串3 \0 N$ |" q/ k$ \9 C" l7 q
        return 0;}
    % U. L& P' l2 O1 a) }2 o/ Y# ]# c% }. J1 N, i1 y) H  |
    7
    " R3 \: i' S5 Z, G6 m0 rvoid 与 void*关键字5 O% F4 l( x- E( y) }
    void表示的是无类型,不能声明变量或常量,但是可以把指针定义为void类型,如void* ptr。void* 指针可以指向任意类型的数据,在C语言指针操作中,任意类型的数据地址都可转为void* 指针。因为指针本质上都是unsigned int。
    2 [. H/ {* F. s1 w
    ( @; l! a9 p* {常用的内存块操作库函数:
    ) p& V- k! Y$ v  ~9 s) {: C8 {" u  q5 \) C+ \1 A& O0 w
  • void * memcpy( void *dest, const void *src, size_t len );void * memset( void *buffer, int c, size_t num);4 r+ X) f8 Q; ]& b
    数据指针为void* 类型,对传入任意类型数据的指针都可以操作。另外其中memcpy第二个参数,const现在也如前文所述,拷贝时对传入的原数据内容禁止修改。
    : h7 }. [% J# N7 p  U
    . r6 {2 b9 I  d& e: ]特殊说明,指针是不能使用sizeof求内容大小的,在ARM系统固定为int 4字节。对于函数无输入参数的,也尽量加上void,如下所示:$ Q, R( S! @! _
    : D4 M5 m4 [# n: V5 \; J
  • void fun(void);" S  Z# E" k' R( W
    4 E6 r: G; b" @. M1 H8 S
    8* W5 a* _! P/ M6 `! i" F
    weak关键字
    8 q/ F7 ^2 Q! Z, j- K' B% T一般简化定义如下所示:8 z9 E. ^, `$ V& u

    " X% Y' a6 A9 ^( P9 w5 u
  • #define _WEAK __attribute__((weak))
    + ^  I4 d6 [6 p! A% ]6 m函数名称前面加上__WEAK属性修饰符称为“弱函数”,类似C++的虚函数。链接时优先链接为非weak定义的函数,如果找不到则再链接带weak函数。: |6 _% t9 _5 R3 H6 b7 q

    $ {% o; t( V- j5 B- P' `
  • _WEAK void fun(void)  {      //do this}  
    7 M/ R) x% \+ x5 _- c' Q( m/ _//不在同一个.c,两同名函数不能在同一个文件void fun(void)  {      //do that}" q, G. _/ z* C
    这种自动选择的机制,在代码移植和多模块配合工作的场景下应用较多。例如前期移植代码,需要调用某个接口fun,但当前该接口不存在或者未移植完整使用,可以使用weak关键字定义为空函数先保证编译正常。# }$ U' i: J/ n* l- ^) E
    1 v* S/ j8 ]4 {9 G
    后续移植完成实现了fun,即软件中有2个fun函数没有任何错误,编译器自动会识别使用后者。当然也粗暴的#if 0屏蔽对fun的调用,但要确保后续记得放开。
    ' y" y. @/ z$ f& \! J: e0 O4 V4 r" r7 o, E- \) n/ z6 E+ C

    gsy3dti3fgp64015933943.png

    gsy3dti3fgp64015933943.png
    1 s) R" z% B2 X* _3 k5 _# Q0 v
    往期推荐什么是内存碎片?8 k( C3 s- Y  c
    详解UDS CAN诊断:什么是UDS(ISO 14229)诊断?
    8 l. u! O3 Y7 {0 D8 T8 U- B$ Z3 ~磁耦合共振无线供电装置
    ' Z1 u6 o1 E4 s" O+ a7 LC语言:十六进制(HEX)和浮点类型(float、double)转换. ^  R% ~' v0 y( H) `
    HarmonyOS 分布式多端应用一站式开发平台(DevEco Studio 安装)
    # {7 t1 C4 [0 J, L- Z: r2 V: Y) i# L& u3 g

    ! h" G  h3 e  o0 }3 A& x. d* }) o

    h1kyevj50zz64015934043.jpg

    h1kyevj50zz64015934043.jpg

    1 ~- ^" J) H- [- e( U

    dnweybl2r1c64015934143.gif

    dnweybl2r1c64015934143.gif
    # o6 q  Z: g6 b  V( k& z+ E
    点击阅读原文,更精彩~
  • 回复

    使用道具 举报

    发表回复

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

    本版积分规则


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