电子产业一站式赋能平台

PCB联盟网

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

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

[复制链接]

752

主题

752

帖子

6398

积分

高级会员

Rank: 5Rank: 5

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

3dh4jxqwyqg64023061236.gif

3dh4jxqwyqg64023061236.gif
( m2 N; g$ A$ P5 T  X& ]
点击上方蓝色字体,关注我们0 A4 j0 D1 l  I0 t2 Y7 h
. s- U9 b2 K0 y$ u
1
" ?6 l. \, Y$ g1 a7 c. q% {: tvolatile关键字
5 J. n0 i) L* t4 E6 O  }5 G5 Cvolatile是一个特征修饰符,提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,告诉编译器对该变量不做优化,都会直接从变量内存地址中读取数据,从而可以提供对特殊地址的稳定访问。
) i/ ]; D; h) [* E. u
6 t# @9 Z6 E- q- M常用场景:中断服务与主程序共享变量。示例代码如下:1 k) F9 K' x6 f# M4 L% n

9 Q: p0 G" ]4 V4 C
  • //volatile uint8_t flag=1;uint8_t flag=1;
    & x& O8 K( r7 M  Y( o# ]& p" N1 h$ Fvoid test(void){    while(flag)    {        //do something    }}; H% F; M9 |0 r9 S
    //interrupt service routinevoid isr_test(void){    flag=0;}. E. _, K8 p" Y9 T* y9 Z0 y4 `
    如果没使用volatile定义flag,可能在优化后test陷入死循环,因为test里使用的flag并没修改它,开启优化后,编译器可能会固定从某个内存取值。+ _# l3 J. V  ^6 q/ z! I( w

    / t' P5 g( J: S0 d) f3 g26 n' v& D: P; s1 L) o  d
    const关键字. F$ J% o) A& [& d) s
    const 是 constant 的缩写,意思是“恒定不变的”,它是定义常变量的关键字。
    0 \+ s2 g% \9 u通常有4种用法。
    # Q$ b' y: c0 d2 j# |& `* Y) h& w
    " b& t! k/ b. K" D' L3 ^  N. I+ d
    2 B* W8 F3 |; H$ Q' }
    1、修饰变量
    : q! M& l( P: k9 N1 D" Y" s& M* p( y, v5 ^* x
    采用const修饰变量,即变量声明为只读,保护变量值以防被修改。9 d: g+ ?: Z1 e) k+ X$ w

    $ ?+ M8 B' ]3 g/ |" u3 E8 M
  • const int i = 1;或者int const i=1;1 h! @+ M+ g- A% W, x
    变量i具有只读特性,不能够被更改;若想对i重新赋值,如i = 10,属于错误操作。
    8 k. g+ [% ]8 q- }% @2 e3 h0 ~, l9 ~. A, X
    - ^# O7 u  N5 U- L
    2、修饰数组
    . \; J6 o0 M8 P" Z# x# X
    3 p; v3 e3 L7 b0 Z5 y数组元素与变量类似,具有只读属性,不能被更改,一旦更改,编译时就会报错。
    " p9 O1 u* W' J1 W+ o1 h8 J$ d8 e9 L* l4 w  A: Q: @) p
  • const int array[5] = {1,2,3,4,5};array[0] = array[0]+1; //错误,array是只读的,禁止修改
    & D. Y; c; t/ e% r" T2 r% S) c2 |使用大数组存储固定的信息,例如查表(表驱动法的键值表),可以使用const节省ram。编译器并不给普通const只读变量分配空间,而是将它们保存到符号表中,无需读写内存操作,程序执行效率也会提高。9 o, H: W, y3 z

    ! k: i3 m0 \1 s5 `8 [6 K

    ' T6 l  v7 f" ~  o3、修饰指针
    + {) @$ F9 U% ]
    " {8 \1 {! g! JC语言中const修饰指针要特别注意,共有两种形式,一种是用来限定指向空间的值不能修改;另一种是限定指针不可更改。2 u: ]* m8 P: N7 V  }3 Z* ^( A4 \
    9 D3 h: [4 \7 r& Z& e
  • int i = 1;int j = 2;
    5 l/ A3 G' x8 @8 P7 m$ q/ H% Mconst int *p1 = &i;int* const p2 = &j;. I( G: E0 m  i9 }) T: _4 ?( X9 T! }" W
    上面定义了两个指针p1和p2,区别是const后面是指针本身还是指向的内容。
      g, x- F2 y4 i/ U8 v$ N
    - t% H. `- N; R7 U, f在定义1中const限定的是*p1,即其指向空间的值不可改变,若改变其指向空间的值如*p1=10,则程序会报错;但p1的值是可以改变的,对p1重新赋值如p1=&k是没有任何问题的。
    9 D# k- ^/ _" A  |( S0 G( c
    0 l. [8 a$ [% B6 S6 C8 o) T3 t& ~在定义2中const限定的是指针p2,若改变p2的值如p2=&k,程序将会报错;但*p2,即其所指向空间的值可以改变,如*p2=20是没有问题的,程序正常执行。" S* g# C9 Q, E6 @6 t! ~
    3 q' A8 S" O) J% T) `3 |
    % f% ^, f( q1 L6 A( X0 w6 L
    4、 修饰函数参数+ S# C' J: u0 @) J
    const关键字修饰函数参数,对参数起限定作用,防止其在函数内部被修改,所限定的函数参数可以是普通变量,也可以是指针变量。
    % A9 i; ~* j& Q1 e2 R: a9 b# v3 j, S! u" c6 q3 o
  • void fun(const int i){    ……    i++; //对i的值进行了修改,程序报错}
    , J5 r% i2 @* }. E9 o6 _常用的函数如strlen。0 c0 l* W/ m+ b+ X0 w* j
    2 d# `1 \+ K: ^9 Q; M8 x4 ?1 Z3 U5 m' l( I
  • size_t strlen(const char *string);9 o7 _; g6 S7 a6 t  j
    const在库函数中使用非常普遍,是一种自我保护的安全编码思维。) |( }' M9 C/ J/ y$ N+ e4 T
    33 n/ |: s. _; V( S' @/ Y4 Z5 ]
    static关键字% x( A, k. X6 W( {
    1、static修饰全局变量,该变量只在本文件内被访问,不能在其他文件被直接访问。5 ~- z; x: d$ O' I/ M
    0 t; X& C" o2 P7 W% a
    2、static修饰函数,该函数只能在本文件内被访问,不能被其他文件访问。但是可以通过嵌套的方式调用,变相的封装的表现。/ P# N  c! {) o0 @$ I

    . V2 w: I/ a. P% v/ z* ]' W3、static修饰局部变量,更改该局部变量的生命周期。9 q1 D2 r( e5 a) X) ]8 ?+ ~
  • 生命周期:将临时变量的生命周期变成全局变量的生命周期。
  • 作用域不变:作用域仍然是在本代码块内。- q$ ~) G% m3 T6 Y7 D8 G, R
    ) B8 |& T% w, a. l" p' E
    4
    ' A; M6 [! a* Fstruct与union
    / w. G2 p6 n/ n; Z  r1 r( t- X可以使用struct结构体来存放一组不同类型的数据。" `  n8 \; f% E9 d6 Y
      r2 t* V' J8 X" T1 t8 i
  • struct 结构体名{    结构体所包含的变量或数组};
    ! A' O: l& Y1 U& @' k# l& p结构体是一种集合,它里面包含了多个变量或数组,它们的类型可以相同,也可以不同,每个这样的变量或数组都称为结构体的成员,通常我们使用结构体定义和解析协议,如下所示:
    0 m7 K* S* [3 S0 i# [; g  n( T
    7 m# X( F. S- I' Y
  • // 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()
    ' \! ?: v7 P, U. \8 Wunion共用体关键字,定义union下面的成员变量共享一块内存,每一个成员在任一时刻有且只有一个成员使用此块内存。
    : [& k& U2 @" y5 `( s
    : A0 S& L9 ]- M4 p8 I8 M; s
  • union 共用体名{    成员列表};2 B9 F" P9 X3 @4 D3 `$ ^
    结构体和共用体的区别在于:结构体的各个成员会占用不同的内存,互相之间没有影响;而共用体的所有成员占用同一段内存,修改一个成员会影响其余所有成员。
    . c$ |; d* g/ d+ w  i5 Q8 {通常使用共用体做一些标志位操作,例如以下示例,可以非常灵活的访问Val中的bit位。
    0 `, Q9 {+ M: J! m7 x* b) {  _1 D- p& q
  • 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 g( G  K% O0 {6 V7 @1 N或者使用共用体实现单字节与多字节的转化和拼接,如下所示:$ w# ?) C3 ?6 K3 C+ X% O0 ?

    - y, s& F8 J  d2 Q8 G1 f" |
  • #include "stdio.h"% C# F0 x. Y* y5 i
    typedef struct{    union    {        struct        {            unsigned char low;            unsigned char high;        };        unsigned short result;    };}test_t;% p- ~. A1 ~8 B, s1 x
    int main(int argc, char *argv[]){    test_t hello;" X% @, ^- t$ L
        hello.high=0x12;    hello.low=0x34;
    4 T! I/ G3 e3 M) n- @    printf("result=%04X\r* d4 l- C3 j+ `( U% k5 E; N
    ",hello.result);//输出 result=1234
      ?7 I; ~- t/ ]1 m  W* d    return 0;}. B8 P- d! @) m3 ~/ f

    " U" P" H9 {2 P2 \8 R. b5
    & k/ e% J* [! @2 F1 s  s. U- V, x预定义标识符
    % Z: ]! r7 a5 a一般编译器都支持预定义标识符,这些标识符结合printf等打印信息帮助程序员调试程序是非常有用的,一般编译器会自动根据用户指定完成替换和处理。
    " r' E5 u# m7 P8 G9 s5 u7 \" [) l8 K/ \% V1 W8 m
    常用的预定义标识符如下所示:/ k. _3 M: k/ C4 B
    : G1 J; L, d# _" v$ J
  • __FILE__    //表示编译的源文件名__LINE__   //表示当前文件的行号__FUNCTION__  //表示函数名__DATE__  //表示编译日期__TIME__   //表示编译时间
    6 m6 I, J) d! y% b/ i# X在Debug打印日志时候经常会用到,如下所示:
    # ?; m) a& e: Y0 ]( z" A# Z3 z
    * t" d3 a! x8 C" ?8 B/ i/ P& m
  • printf("file:%s,line:%d,date:%s,time:%s",__FILE__,__LINE__,__DATE__,__TIME__);
    6 ^7 s2 n; n  E1 A+ L+ V) l* j3 b4 e
    6" e: Z! |2 T' E+ t0 H4 D* i; A
    #与##( P7 \, E" [0 X3 {6 L" M
    #:是一种运算符,用于带参宏的文本替换,将跟在后面的参数转成一个字符串常量。) ~5 `& Q3 A5 K
    + f* B  }$ e+ v0 ~. h" R
    ##:是一种运算符,是将两个运算对象连接在一起,也只能出现在带参宏定义的文本替换中。
    ( \: k  y0 M* Q6 w# |
    1 m' O9 g/ s, x5 o
  • #include "stdio.h"
    % V/ h  A" n: U0 ]3 T#define TO_STR(s) #s#define COMB(str1,str2) str1##str2
    ! ]3 i8 ^0 F6 d1 C$ p2 Zint main(int argc, char *argv[]){    int UART0= 115200;
    ) }/ I( u) C, ]* P( G, s5 z- j5 k/ B" i    printf("UART0=%d
    / O* f. i' R3 x; Z1 Y", COMB(UART, 0));//字符串合并为变量UART0    printf("%s
    - K0 z6 X2 y; c& R, P4 s) p0 f", TO_STR(3.14));//将数字变成字符串7 g6 k  N3 N' I' u
        return 0;}
    - S6 @+ m( L2 ^! L: t" \
    0 W- ?) _+ O+ k, ]# g0 s, K70 \5 R3 T. A% n
    void 与 void*关键字7 u/ N! \" t, f5 d7 x% m: V
    void表示的是无类型,不能声明变量或常量,但是可以把指针定义为void类型,如void* ptr。void* 指针可以指向任意类型的数据,在C语言指针操作中,任意类型的数据地址都可转为void* 指针。因为指针本质上都是unsigned int。
    , k% q) |1 u% }" ?' G: f! t! k7 k) N( o+ a2 R0 d- X; f+ R5 i
    常用的内存块操作库函数:
    1 a4 _7 v" r, b: o0 c' t/ R/ f; q0 z* D* e; u/ X$ N9 q
  • void * memcpy( void *dest, const void *src, size_t len );void * memset( void *buffer, int c, size_t num);. @+ n  X& ]  f' l, {  C  `; U  o2 E1 D0 ?
    数据指针为void* 类型,对传入任意类型数据的指针都可以操作。另外其中memcpy第二个参数,const现在也如前文所述,拷贝时对传入的原数据内容禁止修改。; U6 y9 d6 I5 a( k9 N+ H: ?* B' z- a

    & q+ C4 G, b/ F- ~' g/ C0 g5 [  R/ A特殊说明,指针是不能使用sizeof求内容大小的,在ARM系统固定为int 4字节。对于函数无输入参数的,也尽量加上void,如下所示:
    % o$ U4 {+ U0 d8 I7 A! }( s# Q" O2 p# W/ |
  • void fun(void);3 A/ H6 z: t/ {! w! @  L; Z  ~7 O

    2 U. f3 |& h. m' {" g8
    ' q0 O% P8 t" N- G1 {; @weak关键字
    % M, L( g& m+ h: i5 F, {* D一般简化定义如下所示:
    4 x$ ^- ?1 v' j% z" {9 e  t9 C# F' R3 [; Q4 h& X8 X# r% \/ q5 ^6 F5 n
  • #define _WEAK __attribute__((weak))3 x! q. ^  t9 v
    函数名称前面加上__WEAK属性修饰符称为“弱函数”,类似C++的虚函数。链接时优先链接为非weak定义的函数,如果找不到则再链接带weak函数。+ q! X& z* y% o
    " l  i5 Q7 n) \4 h" ~6 [' h4 y( c: I
  • _WEAK void fun(void)  {      //do this}  
    $ d1 ?$ B1 W* s0 a: A# [//不在同一个.c,两同名函数不能在同一个文件void fun(void)  {      //do that}3 R) Z  r2 r$ k) i  R
    这种自动选择的机制,在代码移植和多模块配合工作的场景下应用较多。例如前期移植代码,需要调用某个接口fun,但当前该接口不存在或者未移植完整使用,可以使用weak关键字定义为空函数先保证编译正常。
    9 L/ {& y5 U+ @; C3 c1 K
    & v% N' j) U; J  S$ T7 j  l后续移植完成实现了fun,即软件中有2个fun函数没有任何错误,编译器自动会识别使用后者。当然也粗暴的#if 0屏蔽对fun的调用,但要确保后续记得放开。- L, ?0 E  i8 r$ }8 g+ y8 h& J
    9 C7 G- x# ?7 i) v

    w32z1grc1ce64023061336.png

    w32z1grc1ce64023061336.png
    ; p; q* |1 d3 A  r; k+ _
    往期推荐什么是内存碎片?
    # c7 R# |+ d! g详解UDS CAN诊断:什么是UDS(ISO 14229)诊断?1 O5 Z+ N2 @7 Y' {3 z# t
    磁耦合共振无线供电装置; b/ r- g5 Z, E; o: ]
    C语言:十六进制(HEX)和浮点类型(float、double)转换5 K( a9 D# V6 p3 ]6 U* W* S/ o
    HarmonyOS 分布式多端应用一站式开发平台(DevEco Studio 安装)" n/ j% H& a( S- A0 a" S+ i1 O

    - l4 ^+ S8 \. m
    & M) V* J1 H" K3 |8 q. r' a/ F

    uhuraiqmgcz64023061436.jpg

    uhuraiqmgcz64023061436.jpg
    ) y  n+ y* Y8 m& Y' {6 Q

    ndwrrmbi1fp64023061536.gif

    ndwrrmbi1fp64023061536.gif
    9 r  c7 y! r2 _  v) ?8 t3 {7 B
    点击阅读原文,更精彩~
  • 回复

    使用道具 举报

    发表回复

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

    本版积分规则


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