电子产业一站式赋能平台

PCB联盟网

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

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

[复制链接]

840

主题

840

帖子

6491

积分

高级会员

Rank: 5Rank: 5

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

ehkf2mbeaxy6406095230.gif

ehkf2mbeaxy6406095230.gif

9 T. D- U; v5 A  j. y) ~6 t, B点击上方蓝色字体,关注我们) `% D! U' M0 F2 U. t& N

6 T# w* p& L# u5 v7 l' g1. m& f! D; g$ ]1 w
volatile关键字
  \0 Y2 B0 A4 n/ Pvolatile是一个特征修饰符,提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,告诉编译器对该变量不做优化,都会直接从变量内存地址中读取数据,从而可以提供对特殊地址的稳定访问。7 S, N+ l2 v* `- x$ u
9 r9 G& \5 @; j8 V  z; ~& i1 `8 [% M* e6 R
常用场景:中断服务与主程序共享变量。示例代码如下:5 p9 M9 i1 o3 t: F
" ^* t$ V# v/ X$ a) Y& `
  • //volatile uint8_t flag=1;uint8_t flag=1;( L+ U8 b; b8 c
    void test(void){    while(flag)    {        //do something    }}& }( v; p; `: G# d5 p/ L' G( O
    //interrupt service routinevoid isr_test(void){    flag=0;}6 C0 i* a+ d" M" \
    如果没使用volatile定义flag,可能在优化后test陷入死循环,因为test里使用的flag并没修改它,开启优化后,编译器可能会固定从某个内存取值。5 A  {/ J& F% Y' [
    * A" y$ R# X# M4 C! }% ~
    2
    8 ^5 D# M* f, Rconst关键字; Y8 g& D/ `$ r& z
    const 是 constant 的缩写,意思是“恒定不变的”,它是定义常变量的关键字。
    2 Y& m9 U" f2 K通常有4种用法。: E8 G0 t# N! b1 a$ L- L

    7 U9 C/ t# ~  C

    4 z  b' U0 H8 ~  y1 ?' X# j1、修饰变量
    / i4 E; Y6 Z1 r2 O$ E# d
    ! M# u; P; J& t% @7 z采用const修饰变量,即变量声明为只读,保护变量值以防被修改。. D4 O5 `: j) n

      O" U  }0 e5 B4 z! ~6 E
  • const int i = 1;或者int const i=1;* Y0 M: b, Y+ F; t5 x
    变量i具有只读特性,不能够被更改;若想对i重新赋值,如i = 10,属于错误操作。
    : I% F0 C( x3 V- ]
    2 W0 @( V$ @+ X3 j
    " n4 Y3 x1 q! [) E# q) _. ]* B
    2、修饰数组" z) g: \6 e. Z$ q# h( s

    - w/ j# d0 C" _) T数组元素与变量类似,具有只读属性,不能被更改,一旦更改,编译时就会报错。
    & f0 m( _7 R& h4 C: I3 E) Y, N8 L2 O( E6 X( B
  • const int array[5] = {1,2,3,4,5};array[0] = array[0]+1; //错误,array是只读的,禁止修改7 u. J. Z, B8 b& y) j8 Y; Y
    使用大数组存储固定的信息,例如查表(表驱动法的键值表),可以使用const节省ram。编译器并不给普通const只读变量分配空间,而是将它们保存到符号表中,无需读写内存操作,程序执行效率也会提高。1 u  s# \0 c! L; K* q, O9 S4 V
    . w9 i; S$ m- |

    ( M, E0 z% x2 F% I3、修饰指针
    ! A. K5 K$ `- G! C' L9 a7 H" J. L# Z6 a( `1 L
    C语言中const修饰指针要特别注意,共有两种形式,一种是用来限定指向空间的值不能修改;另一种是限定指针不可更改。; |) C0 k" C  h& e+ v! H* }

    # O' w" [+ ^; U% T7 J
  • int i = 1;int j = 2;
    - n% z4 `* g$ l! i9 `- Gconst int *p1 = &i;int* const p2 = &j;! N. {; b0 {$ [; u# d
    上面定义了两个指针p1和p2,区别是const后面是指针本身还是指向的内容。+ X' k" I/ P( f/ x% n

    1 v5 |7 [" X5 w8 s3 r* k在定义1中const限定的是*p1,即其指向空间的值不可改变,若改变其指向空间的值如*p1=10,则程序会报错;但p1的值是可以改变的,对p1重新赋值如p1=&k是没有任何问题的。/ j- @4 L, {# L3 Q. ^
    8 y8 v! U4 J* k' h
    在定义2中const限定的是指针p2,若改变p2的值如p2=&k,程序将会报错;但*p2,即其所指向空间的值可以改变,如*p2=20是没有问题的,程序正常执行。
      p$ R4 M* n& O( i! I8 B+ O( G/ \: K/ C7 W
    ' f) g& I5 |+ C6 O/ O0 [% H
    4、 修饰函数参数
    * u3 \# f2 S) G* W3 iconst关键字修饰函数参数,对参数起限定作用,防止其在函数内部被修改,所限定的函数参数可以是普通变量,也可以是指针变量。$ I1 y6 F: S+ [1 M
    % C3 o+ S0 D; m0 b) }. N
  • void fun(const int i){    ……    i++; //对i的值进行了修改,程序报错}  D" l7 o# O- o: J
    常用的函数如strlen。9 G0 @2 Z. o8 F2 H
    0 n/ A9 ^. L" F8 {2 h7 C. s
  • size_t strlen(const char *string);
    0 e- C: _7 b5 `  r: K2 X# n$ ^' Vconst在库函数中使用非常普遍,是一种自我保护的安全编码思维。/ c/ Y; t1 S$ R5 |8 i
    3
    & \& v: [3 C5 I' w; U$ J8 E  kstatic关键字! ]. e2 o9 g( d4 c
    1、static修饰全局变量,该变量只在本文件内被访问,不能在其他文件被直接访问。
      S' j: |4 {' D3 K; s8 D8 n* L% ?! s2 a4 ?! A& I5 o
    2、static修饰函数,该函数只能在本文件内被访问,不能被其他文件访问。但是可以通过嵌套的方式调用,变相的封装的表现。
    " m: \% l) x; P+ i( Z& `/ V
    3 E  n  e) t: h6 r6 R3、static修饰局部变量,更改该局部变量的生命周期。! ^$ v0 a: n2 A8 e- I
  • 生命周期:将临时变量的生命周期变成全局变量的生命周期。
  • 作用域不变:作用域仍然是在本代码块内。
    : A- v) C) n6 a0 }8 s

    5 [4 M3 M0 y; \/ k& _# M4
    - }; ?! f7 @- g1 E; ^% j& l  ]* Sstruct与union! ^& @  D% Z* ~2 w% y
    可以使用struct结构体来存放一组不同类型的数据。( y. z. V# D# Y" x

    # X& w% x# E. ~& `0 A1 {
  • struct 结构体名{    结构体所包含的变量或数组};
    " `$ W' L& |, S* a% W/ |结构体是一种集合,它里面包含了多个变量或数组,它们的类型可以相同,也可以不同,每个这样的变量或数组都称为结构体的成员,通常我们使用结构体定义和解析协议,如下所示:8 x9 {9 }) `/ F6 F
    : a+ i' y5 Y4 }
  • // 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()5 e/ `( }1 o1 p; X4 ^
    union共用体关键字,定义union下面的成员变量共享一块内存,每一个成员在任一时刻有且只有一个成员使用此块内存。
    + x; {8 u0 @5 Y* m% i' Z, F2 i6 G0 k7 T
  • union 共用体名{    成员列表};
    . y+ u9 A& s2 C8 K% g结构体和共用体的区别在于:结构体的各个成员会占用不同的内存,互相之间没有影响;而共用体的所有成员占用同一段内存,修改一个成员会影响其余所有成员。0 R, ]/ d5 \" V
    通常使用共用体做一些标志位操作,例如以下示例,可以非常灵活的访问Val中的bit位。
    # R0 x5 X9 L) A/ B6 p4 f- w1 P8 O! Z6 \, }
  • 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;
    # ?( k5 u/ g% w1 t4 P或者使用共用体实现单字节与多字节的转化和拼接,如下所示:6 h4 q' l# W% a* S- d" Q  o

    % ?2 E" x7 h# U1 r- t4 U
  • #include "stdio.h"! d+ i% d& A' O6 ^* j9 `* D7 P: }
    typedef struct{    union    {        struct        {            unsigned char low;            unsigned char high;        };        unsigned short result;    };}test_t;; S7 ]2 l6 h( ~" m2 I/ a
    int main(int argc, char *argv[]){    test_t hello;8 p$ w! A8 v- m  z
        hello.high=0x12;    hello.low=0x34;
      W4 w3 V& F( H/ }: m    printf("result=%04X\r
    ( j7 W$ X  ^/ {& y! L' E6 G",hello.result);//输出 result=1234
    9 D& M, i) f9 @) t6 P3 w, Y. p    return 0;}
    / @( G3 C9 ?8 \4 g( B7 c- k! r0 ~. a' i- \
    5
    , X0 Z$ N4 H3 _0 S& ?! S预定义标识符
    - S8 p) Q) _) q) i, {' C$ r4 z一般编译器都支持预定义标识符,这些标识符结合printf等打印信息帮助程序员调试程序是非常有用的,一般编译器会自动根据用户指定完成替换和处理。& ]  z0 v# b7 x. Q) m7 g
    4 N8 H. b  K1 O3 ?6 N' c$ c/ m
    常用的预定义标识符如下所示:" Y# B7 a* D% g$ c5 z6 H% v6 u3 ?

    6 h( i1 n4 ?! `
  • __FILE__    //表示编译的源文件名__LINE__   //表示当前文件的行号__FUNCTION__  //表示函数名__DATE__  //表示编译日期__TIME__   //表示编译时间
    + }! Q0 O0 x5 e+ \在Debug打印日志时候经常会用到,如下所示:
    ' p3 Q% z3 S! ^% `) T
    , J4 P& A9 U0 b* K3 W8 F4 v
  • printf("file:%s,line:%d,date:%s,time:%s",__FILE__,__LINE__,__DATE__,__TIME__);
    3 m6 `) A4 X. D1 t. X. f+ W& B& A7 }9 {  f0 P! z2 U1 Y
    6' \$ }6 V& K; G- h8 }$ }( P" T
    #与##4 l6 l+ a0 S  C: |9 }
    #:是一种运算符,用于带参宏的文本替换,将跟在后面的参数转成一个字符串常量。
    - A1 Z% q$ r9 t3 g  Z+ t' `
    " y8 i1 s# u( R/ H##:是一种运算符,是将两个运算对象连接在一起,也只能出现在带参宏定义的文本替换中。$ O& U% I4 K) q" V" L6 N6 E
    . M, L' g! [* o7 d: p) D# c
  • #include "stdio.h"$ J5 P  a/ ?# x, Z# B# G
    #define TO_STR(s) #s#define COMB(str1,str2) str1##str2. c( u# F, |: @' x) _2 w
    int main(int argc, char *argv[]){    int UART0= 115200;
    / y/ L$ Z: r4 q. y% P    printf("UART0=%d
    1 B2 f) F5 T2 p9 R", COMB(UART, 0));//字符串合并为变量UART0    printf("%s# h! L7 e$ F" T1 V8 n; L
    ", TO_STR(3.14));//将数字变成字符串
    9 |4 y, t- q( H- k    return 0;}3 V; {* |! B8 a* q3 A
    9 U/ H6 [) G4 P! e: l
    7
    : R) c. r" {' ~3 V! m0 f9 tvoid 与 void*关键字
    1 I1 _/ x5 j4 X% i2 nvoid表示的是无类型,不能声明变量或常量,但是可以把指针定义为void类型,如void* ptr。void* 指针可以指向任意类型的数据,在C语言指针操作中,任意类型的数据地址都可转为void* 指针。因为指针本质上都是unsigned int。: U# p+ A5 a! N# u. Z* f! C# z
    ( a) L: B9 _& V/ r1 o/ f1 X* v$ _
    常用的内存块操作库函数:
    ' `: d; h% p4 a* O9 K3 w' j
    7 S; l" ?8 h% L) \
  • void * memcpy( void *dest, const void *src, size_t len );void * memset( void *buffer, int c, size_t num);
    , M9 z: d& h. K6 c8 p# v数据指针为void* 类型,对传入任意类型数据的指针都可以操作。另外其中memcpy第二个参数,const现在也如前文所述,拷贝时对传入的原数据内容禁止修改。6 d' h7 g; T& g2 h

    + Q3 z7 u% e1 j- Z3 N特殊说明,指针是不能使用sizeof求内容大小的,在ARM系统固定为int 4字节。对于函数无输入参数的,也尽量加上void,如下所示:$ d6 y8 z# ~' O4 J7 F& v" v) V

      G/ q4 c* S; I) a! B4 ?% j/ H8 @
  • void fun(void);
    # w% l, S* O) F" Z6 g
    ; X; ^- M7 B! U  i+ a5 v80 O, i( W1 V; z. {& g3 s. P
    weak关键字8 Y! d! U3 E6 l
    一般简化定义如下所示:# g: w+ [( c# X/ t0 W2 d
    4 d/ i2 d7 X- O
  • #define _WEAK __attribute__((weak))
    7 d' ~; ]; q) C" c% P4 w; T函数名称前面加上__WEAK属性修饰符称为“弱函数”,类似C++的虚函数。链接时优先链接为非weak定义的函数,如果找不到则再链接带weak函数。! Y# f* o' C9 V7 ?8 H
    * U; Q0 M/ q% B- V0 ]% C" g/ |. `
  • _WEAK void fun(void)  {      //do this}  
    . k" c( v2 W9 ^//不在同一个.c,两同名函数不能在同一个文件void fun(void)  {      //do that}
      h9 }2 w( Y) v, l0 A' J  z这种自动选择的机制,在代码移植和多模块配合工作的场景下应用较多。例如前期移植代码,需要调用某个接口fun,但当前该接口不存在或者未移植完整使用,可以使用weak关键字定义为空函数先保证编译正常。6 Q# h# T- K* Q" R. h. T+ Y
    + v# S5 ~# U2 [) \
    后续移植完成实现了fun,即软件中有2个fun函数没有任何错误,编译器自动会识别使用后者。当然也粗暴的#if 0屏蔽对fun的调用,但要确保后续记得放开。" }, Q4 E6 t0 p- e, [4 b
    4 o, v3 \) O( Y3 C

    obzzf0ax3dx6406095330.png

    obzzf0ax3dx6406095330.png
    3 p) S" F) w2 @# T2 e: j( F
    往期推荐什么是内存碎片?; p5 z( K7 y* k
    详解UDS CAN诊断:什么是UDS(ISO 14229)诊断?
    5 Z5 l6 Y4 d' Z8 |# D, ]( d1 Z磁耦合共振无线供电装置
    4 s/ s2 E4 j6 {+ y# \5 GC语言:十六进制(HEX)和浮点类型(float、double)转换+ O1 O) G/ v% s- W1 V) F
    HarmonyOS 分布式多端应用一站式开发平台(DevEco Studio 安装), {7 u; C4 x0 A3 [

    - }9 L9 i; K: x; j) s

    % K' \0 l9 T- t- g/ ^. ?1 e

    0gfwuj32of06406095430.jpg

    0gfwuj32of06406095430.jpg

    3 K; X. i: _% w' D8 {/ a- E2 F

    isieitqkphf6406095530.gif

    isieitqkphf6406095530.gif
    : k( p- ?" x; a
    点击阅读原文,更精彩~
  • 回复

    使用道具 举报

    发表回复

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

    本版积分规则


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