电子产业一站式赋能平台

PCB联盟网

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

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

[复制链接]

1002

主题

1002

帖子

8854

积分

高级会员

Rank: 5Rank: 5

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

skk2rppqhah64024997041.gif

skk2rppqhah64024997041.gif

: J5 d+ [1 u0 j) \: r& G点击上方蓝色字体,关注我们* ^6 S% c- S, s0 m
9 B% v& _) j& v2 B1 U2 g
15 f6 B, ^: K: i5 Y
volatile关键字
% g& h. i! ~0 s: xvolatile是一个特征修饰符,提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,告诉编译器对该变量不做优化,都会直接从变量内存地址中读取数据,从而可以提供对特殊地址的稳定访问。6 W7 @* s  `( |* B  x* w
4 T0 G& Y7 O9 [! i! O% x; w/ c
常用场景:中断服务与主程序共享变量。示例代码如下:/ c* R5 i4 n7 K, v  @0 S
( x0 r  u8 K% f* \
  • //volatile uint8_t flag=1;uint8_t flag=1;
    , J3 @) ?  O& r9 b# \5 N/ Uvoid test(void){    while(flag)    {        //do something    }}
    0 n* a: z. b# t) R//interrupt service routinevoid isr_test(void){    flag=0;}& T  Y( P' r8 a* r# I- B* C
    如果没使用volatile定义flag,可能在优化后test陷入死循环,因为test里使用的flag并没修改它,开启优化后,编译器可能会固定从某个内存取值。7 z/ I9 R  x+ u/ N( \* _

    * T. T) m& U8 @0 }, k0 u8 n# T2$ L) u2 N% ?' \9 D# Y
    const关键字
    % P; U1 L9 @3 C9 `const 是 constant 的缩写,意思是“恒定不变的”,它是定义常变量的关键字。3 k3 n, j4 S! Z, f9 c, B3 i# ~" ^0 r
    通常有4种用法。
    4 ]$ c" D1 m/ O% D& E0 K) a- t8 V! z" a! J7 r
    5 p! B; C$ j# a- {* E
    1、修饰变量
    3 T" S: v' D' D$ \' O
    , f8 r4 N# W, i1 x, N采用const修饰变量,即变量声明为只读,保护变量值以防被修改。
      X8 t, Y+ j  ?( }  ]% Z9 ?  f- a/ t: E8 j" L4 R- U
  • const int i = 1;或者int const i=1;2 H1 d+ Y3 _  a1 ?
    变量i具有只读特性,不能够被更改;若想对i重新赋值,如i = 10,属于错误操作。* z* F; x. S% ?0 V. P
    ( B: Z8 A1 t, ]$ m' A
    0 }! G( `5 @9 b4 ^
    2、修饰数组
    + C' f: |* S0 L. P( J+ F5 T8 z% A
    2 A) _: e1 Q0 x3 O# [数组元素与变量类似,具有只读属性,不能被更改,一旦更改,编译时就会报错。2 F6 N; @1 r3 H) V3 K+ T8 j! _% w0 F

    " Z: K2 |/ V7 M* _5 ]$ ]' `
  • const int array[5] = {1,2,3,4,5};array[0] = array[0]+1; //错误,array是只读的,禁止修改5 W8 C9 ~6 y' S% F3 W  f
    使用大数组存储固定的信息,例如查表(表驱动法的键值表),可以使用const节省ram。编译器并不给普通const只读变量分配空间,而是将它们保存到符号表中,无需读写内存操作,程序执行效率也会提高。
    - j! l, C' j! l! G
    + c- K1 m3 r3 i: z# P

    : ^5 u+ ~9 m7 U; Y) [% f3、修饰指针
      c; u0 W4 H0 c6 V: O! T8 ?  S6 n) G' L5 f: v
    C语言中const修饰指针要特别注意,共有两种形式,一种是用来限定指向空间的值不能修改;另一种是限定指针不可更改。
    + M& e+ `, ~4 |4 i
    4 a) M6 C% K6 u2 f, r! ?0 z
  • int i = 1;int j = 2;
    # e# m3 v& B  k6 \const int *p1 = &i;int* const p2 = &j;1 h) V, p0 F, E) @: S- [+ J
    上面定义了两个指针p1和p2,区别是const后面是指针本身还是指向的内容。7 M" ~1 \# z! v$ U8 V

    6 O& i$ T/ Q/ S0 G4 i  z在定义1中const限定的是*p1,即其指向空间的值不可改变,若改变其指向空间的值如*p1=10,则程序会报错;但p1的值是可以改变的,对p1重新赋值如p1=&k是没有任何问题的。$ e$ B9 [8 e2 d& t. p  c0 ~9 w1 I' D5 Q
      q' D4 p: B. m% t
    在定义2中const限定的是指针p2,若改变p2的值如p2=&k,程序将会报错;但*p2,即其所指向空间的值可以改变,如*p2=20是没有问题的,程序正常执行。% m4 y! I% u$ c
    % O- Z: W( l3 }7 n- Z4 ~

    ! @1 L( U# u# q6 f" D) y4、 修饰函数参数/ n2 {& G' x3 v% W$ W) X
    const关键字修饰函数参数,对参数起限定作用,防止其在函数内部被修改,所限定的函数参数可以是普通变量,也可以是指针变量。- ^, U- b, _" N0 E% m/ z$ j: B
    ! q) x6 F9 E; `2 k: {2 z9 N1 b
  • void fun(const int i){    ……    i++; //对i的值进行了修改,程序报错}
    + Z" v# j7 h6 K! C常用的函数如strlen。
    & c1 v, Y/ C" b4 q% @
    6 g: p, C+ e/ _0 N. e
  • size_t strlen(const char *string);' h. n8 O9 }/ F7 x
    const在库函数中使用非常普遍,是一种自我保护的安全编码思维。
    " ]2 P( H3 a1 ]: H& @3 G3 K& m8 Z3! O4 ^; A% D2 K; N+ k: ^  X
    static关键字. S. B: s6 G9 S! q" |" U" r
    1、static修饰全局变量,该变量只在本文件内被访问,不能在其他文件被直接访问。2 P# G' N) g8 K4 E. `3 U  {

    ' M1 C' B! i9 x) ~2、static修饰函数,该函数只能在本文件内被访问,不能被其他文件访问。但是可以通过嵌套的方式调用,变相的封装的表现。! Q1 j6 S  g6 @3 z; Z

    ( u& Q7 E+ l" B( S, X8 b# t3、static修饰局部变量,更改该局部变量的生命周期。  c9 e. U0 l4 Q6 i* R
  • 生命周期:将临时变量的生命周期变成全局变量的生命周期。
  • 作用域不变:作用域仍然是在本代码块内。
    + i5 v% R6 C* T4 B4 ?
    . [! ]5 I, H/ }& J  U: I' P0 B& O. p6 X* p
    48 W, R  k7 s; z7 g* t% _! a
    struct与union5 m7 ?& [" I0 E: o- \4 r- T
    可以使用struct结构体来存放一组不同类型的数据。5 X7 M3 g0 ^" P+ Z
    0 T2 H- ?8 B" H8 \" X# L
  • struct 结构体名{    结构体所包含的变量或数组};
    " y/ ]; V# s. \& O结构体是一种集合,它里面包含了多个变量或数组,它们的类型可以相同,也可以不同,每个这样的变量或数组都称为结构体的成员,通常我们使用结构体定义和解析协议,如下所示:( l( S1 b' T$ o, ~

    3 [* e, H; V: s( [/ V7 @
  • // 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()! h$ P5 P% n0 G; r0 f, ?; ]* ]
    union共用体关键字,定义union下面的成员变量共享一块内存,每一个成员在任一时刻有且只有一个成员使用此块内存。
    1 z; j, Y1 N" I1 c0 o! [& P  g- r
    6 {& [2 T& E* W: N
  • union 共用体名{    成员列表};
    - ?2 Q0 [$ s1 I% R# R1 |结构体和共用体的区别在于:结构体的各个成员会占用不同的内存,互相之间没有影响;而共用体的所有成员占用同一段内存,修改一个成员会影响其余所有成员。
    - d7 P0 Y7 R( G0 B% b% _# a1 `, u5 W通常使用共用体做一些标志位操作,例如以下示例,可以非常灵活的访问Val中的bit位。
    * p2 f0 T7 Q& Z; ?4 P+ T1 e
    ( N9 }0 {9 I" @/ 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;% M: b" m6 R  M; w' K) O1 A
    或者使用共用体实现单字节与多字节的转化和拼接,如下所示:
    6 k* P/ l) V2 k- X1 @% W
    & ^/ N; T4 m; j# S! k3 H
  • #include "stdio.h"8 w+ U7 R8 L: x. h% G, O
    typedef struct{    union    {        struct        {            unsigned char low;            unsigned char high;        };        unsigned short result;    };}test_t;
    # W3 R# x8 a* z7 p8 Xint main(int argc, char *argv[]){    test_t hello;, Z5 A- j5 L' K3 G1 T- d  p
        hello.high=0x12;    hello.low=0x34;
    , m& V" F5 M# {' B/ d# g    printf("result=%04X\r
    : k: {8 T. U1 a( _6 T& B",hello.result);//输出 result=1234
    . R3 ?& U  X1 T# H; N    return 0;}
    ! e! W, Q1 x& g% Q
    # w0 B$ l, v& |- j5 \56 H% s9 T/ m! |* O# I+ |3 }
    预定义标识符! m. E; u- l& l) _7 p. V
    一般编译器都支持预定义标识符,这些标识符结合printf等打印信息帮助程序员调试程序是非常有用的,一般编译器会自动根据用户指定完成替换和处理。& R* ^. E" M+ ^4 H
    : c7 Y! E' z& t
    常用的预定义标识符如下所示:! p& t; Z) c+ t5 G4 `2 [0 r

    ! E7 W2 G& f: ^; I. {5 w3 T
  • __FILE__    //表示编译的源文件名__LINE__   //表示当前文件的行号__FUNCTION__  //表示函数名__DATE__  //表示编译日期__TIME__   //表示编译时间
    ; Y: `, ^( @6 B: U% w在Debug打印日志时候经常会用到,如下所示:
    4 \6 x( N- }; d9 c, K; b7 o, f) [" n, x  C6 U  |
  • printf("file:%s,line:%d,date:%s,time:%s",__FILE__,__LINE__,__DATE__,__TIME__);
    6 _! B! c' _+ T& Y! i
    " q- o% }* f/ B3 M6" h& K" o. t5 Q! c. t
    #与##
      [1 m: N: m; b3 h6 |#:是一种运算符,用于带参宏的文本替换,将跟在后面的参数转成一个字符串常量。
    ) j; _. r9 q$ p' [" t3 N$ y! e' _0 d- f
    * U* O9 |. Z5 a5 M) W" ]##:是一种运算符,是将两个运算对象连接在一起,也只能出现在带参宏定义的文本替换中。
    8 c. H7 r- W- A; R
    # U5 ^0 Y1 B: X, H( L
  • #include "stdio.h"; k0 B$ I) |& z, N4 G
    #define TO_STR(s) #s#define COMB(str1,str2) str1##str24 g. C6 ~( S9 g' N( U" ^. e
    int main(int argc, char *argv[]){    int UART0= 115200;* o; v0 T- d; ~4 |  g: C
        printf("UART0=%d
    , M4 C9 C! {1 B2 S", COMB(UART, 0));//字符串合并为变量UART0    printf("%s
    ( J9 u. Q: O3 q  F; p! C% H6 r", TO_STR(3.14));//将数字变成字符串
    & t+ p* j) [8 ]6 ?4 J+ g% e    return 0;}
    ' O0 B: V+ O8 n- N6 {
      p; T) ]" Q! E: a* ]7
    6 @3 I: H+ {: Rvoid 与 void*关键字
    4 h7 ~$ X: P3 Y$ u0 L% uvoid表示的是无类型,不能声明变量或常量,但是可以把指针定义为void类型,如void* ptr。void* 指针可以指向任意类型的数据,在C语言指针操作中,任意类型的数据地址都可转为void* 指针。因为指针本质上都是unsigned int。
      |1 e: W  o0 c/ F& G4 f
    + R& M( |  V- ?1 f, O' T常用的内存块操作库函数:
    ! a) V9 Q( Q$ T' Q& y; b9 f6 N7 E: d2 r9 D
  • void * memcpy( void *dest, const void *src, size_t len );void * memset( void *buffer, int c, size_t num);2 U5 p* {7 Q, Z4 X" Q: x
    数据指针为void* 类型,对传入任意类型数据的指针都可以操作。另外其中memcpy第二个参数,const现在也如前文所述,拷贝时对传入的原数据内容禁止修改。
    ) }$ S) M  l, n
    & Y3 b; u; \% X9 p特殊说明,指针是不能使用sizeof求内容大小的,在ARM系统固定为int 4字节。对于函数无输入参数的,也尽量加上void,如下所示:1 |) C7 C5 ^- n: W$ b+ I
    : ?# M- [$ T2 H0 C
  • void fun(void);
    / F5 ~+ z/ E9 L$ U1 y5 j' F. o: I1 T7 f4 \& V
    8* n  U, E+ v! u% ]3 j4 G
    weak关键字
    - b3 ^  N9 C4 p7 L一般简化定义如下所示:# x4 ]. q7 j* H9 Y. \7 Y

    ' x5 a& {! o# R& v6 }( W
  • #define _WEAK __attribute__((weak))
    & \% H: ?- q$ D6 W! V( L' S函数名称前面加上__WEAK属性修饰符称为“弱函数”,类似C++的虚函数。链接时优先链接为非weak定义的函数,如果找不到则再链接带weak函数。( X0 V) T" d# q- p

    + |& T9 U0 t4 i* s1 I0 S
  • _WEAK void fun(void)  {      //do this}  
    0 P, R- c. G/ c//不在同一个.c,两同名函数不能在同一个文件void fun(void)  {      //do that}. H2 x# M9 j- N+ y3 {
    这种自动选择的机制,在代码移植和多模块配合工作的场景下应用较多。例如前期移植代码,需要调用某个接口fun,但当前该接口不存在或者未移植完整使用,可以使用weak关键字定义为空函数先保证编译正常。9 w: y6 V0 n3 Z: C4 i* p$ g3 a9 P9 g

    4 n* X* k) g% o后续移植完成实现了fun,即软件中有2个fun函数没有任何错误,编译器自动会识别使用后者。当然也粗暴的#if 0屏蔽对fun的调用,但要确保后续记得放开。* r4 [8 A4 o) L0 S9 c
    + }! |6 o  n9 \

    z3iwwlzuh1q64024997141.png

    z3iwwlzuh1q64024997141.png
      y( }1 V& M- k! Y0 l' `6 r
    往期推荐什么是内存碎片?
    # i3 S3 v3 v  b0 P, W! z详解UDS CAN诊断:什么是UDS(ISO 14229)诊断?4 T- W/ C9 J  r' Z9 z% J
    磁耦合共振无线供电装置
    + y: [5 Q( Q* H& Q- w" ]C语言:十六进制(HEX)和浮点类型(float、double)转换
      J( h) S" b6 W/ F' |+ G: y- }( ]7 [HarmonyOS 分布式多端应用一站式开发平台(DevEco Studio 安装)& ]$ \6 k" M8 m2 d2 L# C& C) C0 G0 j

    3 D6 r. m; ]; I. v
    5 s7 H# B1 s6 Q  R

    fuk2mic3ad164024997241.jpg

    fuk2mic3ad164024997241.jpg

    6 r/ G- H: }, u' w- ], n) V4 a

    rx0bwuyifqw64024997341.gif

    rx0bwuyifqw64024997341.gif
    " u3 }% F) Z8 D0 u$ {8 M( l. N
    点击阅读原文,更精彩~
  • 回复

    使用道具 举报

    发表回复

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

    本版积分规则


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