电子产业一站式赋能平台

PCB联盟网

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

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

[复制链接]

1002

主题

1002

帖子

8854

积分

高级会员

Rank: 5Rank: 5

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

0vgbbq0aug264018782742.gif

0vgbbq0aug264018782742.gif
# B7 O& q" P! f2 N
点击上方蓝色字体,关注我们& H. q6 |0 G/ f( D% H2 A
% ^; A: [- V7 L  x6 r9 A) w
1
4 l$ b+ Z' a9 T* [& @volatile关键字
* O0 c& a1 U6 E8 a) t+ d: I* N8 \volatile是一个特征修饰符,提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,告诉编译器对该变量不做优化,都会直接从变量内存地址中读取数据,从而可以提供对特殊地址的稳定访问。- T9 ^, I) e# a& _5 g: J4 L0 c* k5 N
+ H$ @2 v# q: s/ |' u
常用场景:中断服务与主程序共享变量。示例代码如下:- \6 j8 }/ k( Y$ b2 e* p

. T8 @! w6 o9 {. r5 r
  • //volatile uint8_t flag=1;uint8_t flag=1;) ^+ _1 W/ A( U  ?& z
    void test(void){    while(flag)    {        //do something    }}. l- G0 h( d) ^3 w0 Q: R
    //interrupt service routinevoid isr_test(void){    flag=0;}2 w4 ]% r# S  k, A9 {  G
    如果没使用volatile定义flag,可能在优化后test陷入死循环,因为test里使用的flag并没修改它,开启优化后,编译器可能会固定从某个内存取值。
    - h( P: z! B  l$ w2 M& V  w9 h+ z+ a- P6 h
    2
    9 S" o$ J2 k" _% b4 `const关键字. b, E( q0 z) ^' e6 x, B
    const 是 constant 的缩写,意思是“恒定不变的”,它是定义常变量的关键字。
    ) O2 d+ x& a# g- Y通常有4种用法。8 o' @& f. M3 k0 S9 g
    ! r% l% w# z4 i* W7 i8 v
    0 w6 S; M! ?& M8 q. s
    1、修饰变量
    $ X5 C! M) ?+ c# [9 u4 K' P" H$ t0 ]+ I9 }! W  w5 y
    采用const修饰变量,即变量声明为只读,保护变量值以防被修改。
    3 E3 y9 Y: o) D$ S6 K* `
    1 ?+ H$ j" c5 X5 v. f$ D
  • const int i = 1;或者int const i=1;
    , H7 L2 D: D/ Q变量i具有只读特性,不能够被更改;若想对i重新赋值,如i = 10,属于错误操作。
    ( {0 l4 R& P0 h9 w" \$ b, c
    " k5 ^# L; R( [6 u- M5 c

    5 g) s) f# N2 H2、修饰数组
    . b7 T! ]; m( j/ X8 y5 v; f) x1 x! w5 i* c9 ~* X
    数组元素与变量类似,具有只读属性,不能被更改,一旦更改,编译时就会报错。+ b: R5 [/ G) o, t& V8 a9 S
    8 A, v1 L1 Q1 c! u) d, l: C7 I
  • const int array[5] = {1,2,3,4,5};array[0] = array[0]+1; //错误,array是只读的,禁止修改. l3 r1 `# U) ]$ C* U8 z
    使用大数组存储固定的信息,例如查表(表驱动法的键值表),可以使用const节省ram。编译器并不给普通const只读变量分配空间,而是将它们保存到符号表中,无需读写内存操作,程序执行效率也会提高。
    : i- [( L0 z3 E3 |% }  X3 @# u* w  v5 g4 @  F( d
    ' g' `9 `: A# O  B% u
    3、修饰指针7 O' y8 b& z( B- p$ g' K7 Z5 ^% k* r
    8 z. {  M+ d7 j
    C语言中const修饰指针要特别注意,共有两种形式,一种是用来限定指向空间的值不能修改;另一种是限定指针不可更改。1 v" T$ W  ]% H3 T# W& Q
    # [  s) n+ G: Q
  • int i = 1;int j = 2;
    : A+ {7 H, F2 K; iconst int *p1 = &i;int* const p2 = &j;6 H8 {8 q( ?3 v  B: o$ w$ m1 D& S, W' T
    上面定义了两个指针p1和p2,区别是const后面是指针本身还是指向的内容。
    ( c3 q! F7 E+ S! C
    + h! O! b3 P8 ]4 B6 A2 {$ h" w在定义1中const限定的是*p1,即其指向空间的值不可改变,若改变其指向空间的值如*p1=10,则程序会报错;但p1的值是可以改变的,对p1重新赋值如p1=&k是没有任何问题的。
    8 t0 V5 J# a" t8 R) s5 d7 h9 O3 a4 o" ], B) w  w1 N- Y
    在定义2中const限定的是指针p2,若改变p2的值如p2=&k,程序将会报错;但*p2,即其所指向空间的值可以改变,如*p2=20是没有问题的,程序正常执行。; P2 j; l9 B  x: s5 j6 a" v) ]

      i5 V! x4 T0 g0 g7 p, }7 Z

    " S# k0 d0 V: [* q4、 修饰函数参数/ H. k0 y6 Y7 F' Y& S
    const关键字修饰函数参数,对参数起限定作用,防止其在函数内部被修改,所限定的函数参数可以是普通变量,也可以是指针变量。
    " Q; N% {, U* h9 `/ S+ p8 b" V
    1 C- f5 z$ s. u5 V8 S
  • void fun(const int i){    ……    i++; //对i的值进行了修改,程序报错}1 A8 @  ~, e  t
    常用的函数如strlen。
    5 _, K: c; ]  [" ]! B! ~% d5 H, Z& F* R+ d8 G0 [- {  j5 b  a
  • size_t strlen(const char *string);
    0 U$ l3 [' t" ~  cconst在库函数中使用非常普遍,是一种自我保护的安全编码思维。# x, V# J, w9 t- D; b4 X
    3
    & e; n8 b$ {/ D3 k8 _8 j; g; _1 |static关键字. y/ _  p0 n5 L; u) G" Z. k0 b
    1、static修饰全局变量,该变量只在本文件内被访问,不能在其他文件被直接访问。' A' ?) }9 u  ?( I! o

    ( ^3 Z, E3 ?% P7 x2、static修饰函数,该函数只能在本文件内被访问,不能被其他文件访问。但是可以通过嵌套的方式调用,变相的封装的表现。
    0 i1 Q3 l  |# Y5 Q0 O, m- q# ~% k& T4 W: Y! Y7 W% ?# }* t
    3、static修饰局部变量,更改该局部变量的生命周期。  H$ m" n/ q, g
  • 生命周期:将临时变量的生命周期变成全局变量的生命周期。
  • 作用域不变:作用域仍然是在本代码块内。9 k" y2 x( @9 x& \+ e8 U7 F
    1 ?8 m0 G5 H: d
    4
    ' U7 w# {, Q7 w: J) M* I, d7 Rstruct与union" C/ [2 I2 Q$ {. r; f3 {/ n0 p
    可以使用struct结构体来存放一组不同类型的数据。
    ) I5 ?9 W: K7 F% s% m/ x6 a; S0 b! f" m: O' e) ]; y+ W0 a2 C- o1 C
  • struct 结构体名{    结构体所包含的变量或数组};6 L) o3 O4 }" b+ W  O! f9 f
    结构体是一种集合,它里面包含了多个变量或数组,它们的类型可以相同,也可以不同,每个这样的变量或数组都称为结构体的成员,通常我们使用结构体定义和解析协议,如下所示:) j) l7 y( u$ s5 A6 C" w
    9 F5 l' u7 Q, c3 J4 g9 Y3 N, J
  • // 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()
    1 O) Y6 [2 G5 T8 i5 iunion共用体关键字,定义union下面的成员变量共享一块内存,每一个成员在任一时刻有且只有一个成员使用此块内存。
    $ v% _: J  R* u. C+ Y, C
    * \% j) b4 D2 X8 u
  • union 共用体名{    成员列表};$ M2 I: V' N2 S
    结构体和共用体的区别在于:结构体的各个成员会占用不同的内存,互相之间没有影响;而共用体的所有成员占用同一段内存,修改一个成员会影响其余所有成员。
    ! ?; D, A4 G$ G通常使用共用体做一些标志位操作,例如以下示例,可以非常灵活的访问Val中的bit位。
    8 G1 V" C# s2 D* k( ~/ w8 E3 n
    & ~+ x5 H7 e$ w8 H" U# A! T
  • 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 u1 z$ c# y2 H* H& z) t
    或者使用共用体实现单字节与多字节的转化和拼接,如下所示:0 {' n9 W) N1 t
    0 |* N$ o5 i# L9 d4 W7 z, o. Q: r
  • #include "stdio.h"
    - {! t4 w; S/ Y% htypedef struct{    union    {        struct        {            unsigned char low;            unsigned char high;        };        unsigned short result;    };}test_t;. H* {) }) g) `2 C
    int main(int argc, char *argv[]){    test_t hello;9 r& c( G- M" D" A: u4 |4 x4 a9 h
        hello.high=0x12;    hello.low=0x34;9 U! @' X" O% Q$ a+ B3 x
        printf("result=%04X\r3 h3 f8 l5 B$ H+ ~; K7 m
    ",hello.result);//输出 result=1234
    2 `" J  l1 K$ t8 T" L" g& `0 }    return 0;}
      B$ n5 G- L, |! {& f1 r$ [/ k4 T  e
    $ K4 ~4 x2 B  B5 R: J5+ B; h( ?% ~  W, T# L' p2 Z
    预定义标识符
    / ?3 Q1 e% D+ M& m9 R0 m: D+ I6 v一般编译器都支持预定义标识符,这些标识符结合printf等打印信息帮助程序员调试程序是非常有用的,一般编译器会自动根据用户指定完成替换和处理。3 c: y( U# I# l
    " N* j2 k5 c) A7 i. L7 o
    常用的预定义标识符如下所示:/ n9 S" j* I  d' g5 r

    & |9 Y) I1 ^1 i8 h4 E
  • __FILE__    //表示编译的源文件名__LINE__   //表示当前文件的行号__FUNCTION__  //表示函数名__DATE__  //表示编译日期__TIME__   //表示编译时间
    2 [5 C/ z$ c& w/ Y% u在Debug打印日志时候经常会用到,如下所示:
    , B  e: o$ u& u# c& D2 K' u  _
    7 ~/ }: N1 H0 Z- r9 T! l' \1 x: |3 q
  • printf("file:%s,line:%d,date:%s,time:%s",__FILE__,__LINE__,__DATE__,__TIME__);8 ]' C2 S2 X3 R9 X0 c: f/ |
    & ?8 D* v& D, j8 D1 c# z: R
    6
      P& `, P' R4 f6 A( \0 Q#与##
    $ F. N# W$ x5 {2 W/ I#:是一种运算符,用于带参宏的文本替换,将跟在后面的参数转成一个字符串常量。
    - Z( s5 N5 I% E; s) P9 h
    2 L; u" n9 \9 I* O, C1 M" n0 K##:是一种运算符,是将两个运算对象连接在一起,也只能出现在带参宏定义的文本替换中。
    8 e+ R2 f; ^% N" X/ s9 K
    8 V; Z  e( }2 h2 U! F% l: b
  • #include "stdio.h"
    0 d; c3 _, q; S9 i3 `9 _#define TO_STR(s) #s#define COMB(str1,str2) str1##str24 ]% T, K! I( o5 Q& y. }' j+ n/ o
    int main(int argc, char *argv[]){    int UART0= 115200;
    ! b8 ?$ ~) D2 I+ k% k    printf("UART0=%d
    ) d1 z  d6 ~2 S", COMB(UART, 0));//字符串合并为变量UART0    printf("%s) Y% G* X+ N# I! D& z
    ", TO_STR(3.14));//将数字变成字符串- m6 v- k5 K3 U, \$ H8 l
        return 0;}5 c8 V$ h/ i6 S* U! s/ L: b" u
    ( x. @0 |0 k+ }1 R) F# u
    7( m* Z' g, d% K" w( @3 v/ d+ k4 p
    void 与 void*关键字
    ! S/ h, M- Y1 d  `, s/ w! Lvoid表示的是无类型,不能声明变量或常量,但是可以把指针定义为void类型,如void* ptr。void* 指针可以指向任意类型的数据,在C语言指针操作中,任意类型的数据地址都可转为void* 指针。因为指针本质上都是unsigned int。
    , v; J& Q! Y) U: H7 i& M  B
    ; k3 D5 d! i+ x. P/ Y& `常用的内存块操作库函数:. R; l8 ~  d5 ^3 S& u
    6 k0 B( n+ S) \- N
  • void * memcpy( void *dest, const void *src, size_t len );void * memset( void *buffer, int c, size_t num);
      h! {& S  P) ?- V( P( t数据指针为void* 类型,对传入任意类型数据的指针都可以操作。另外其中memcpy第二个参数,const现在也如前文所述,拷贝时对传入的原数据内容禁止修改。* e/ X7 [% D$ l" ]7 ^& E
    5 |. C# ^; d- S3 Q, K3 p/ D2 C
    特殊说明,指针是不能使用sizeof求内容大小的,在ARM系统固定为int 4字节。对于函数无输入参数的,也尽量加上void,如下所示:( u; A* P) w7 D+ k$ D9 o6 A: z
    ! `/ i# V- E' N+ R1 J1 Y; v, z8 g
  • void fun(void);. s* o$ a: u* d; ^( U9 o2 \' _! {
    9 Y% w) g) I) i/ b; b; v# X
    8
    , t- v+ v& _: O8 t! v" E; Cweak关键字
    + n# ?1 _1 `2 |% o一般简化定义如下所示:
    0 v# \: v  k+ o6 O
    5 o- }9 U7 ?1 `' x- s2 X
  • #define _WEAK __attribute__((weak))
    / E( ^5 c& W! {函数名称前面加上__WEAK属性修饰符称为“弱函数”,类似C++的虚函数。链接时优先链接为非weak定义的函数,如果找不到则再链接带weak函数。
    % q9 h) n  e  I& s/ A1 q  G3 B  j0 J- S
  • _WEAK void fun(void)  {      //do this}  5 a5 Q- P- Z; x( G' V
    //不在同一个.c,两同名函数不能在同一个文件void fun(void)  {      //do that}
    ! S8 J- L; E- V+ Y这种自动选择的机制,在代码移植和多模块配合工作的场景下应用较多。例如前期移植代码,需要调用某个接口fun,但当前该接口不存在或者未移植完整使用,可以使用weak关键字定义为空函数先保证编译正常。
    ! `. e' G( h& a! F1 [
    9 E# H% u8 R1 z2 f* A后续移植完成实现了fun,即软件中有2个fun函数没有任何错误,编译器自动会识别使用后者。当然也粗暴的#if 0屏蔽对fun的调用,但要确保后续记得放开。0 z2 P7 S8 [2 q3 h4 Q

    & c. `5 Z3 m" k! P& {+ {5 u

    1yxnknbkizw64018782842.png

    1yxnknbkizw64018782842.png

    7 h% D, |0 b2 a# i4 K1 z往期推荐什么是内存碎片?9 `, a9 T: ]7 G! `3 E7 T
    详解UDS CAN诊断:什么是UDS(ISO 14229)诊断?
    6 n- h  e  v6 E磁耦合共振无线供电装置  L& s8 B9 _' V4 B( _3 m
    C语言:十六进制(HEX)和浮点类型(float、double)转换
    5 D6 |$ a; o5 N" H5 K: T) UHarmonyOS 分布式多端应用一站式开发平台(DevEco Studio 安装)
    0 r3 U0 p3 n7 S+ b. ~
    . N) ^3 c: x1 z3 x) N, v  Y

    ) o2 b; n" n7 R6 j$ O' o4 a

    ikto4m4zhwe64018782942.jpg

    ikto4m4zhwe64018782942.jpg
    4 |: A3 ]5 j8 _9 q0 L+ @

    jvqqbumgoef64018783042.gif

    jvqqbumgoef64018783042.gif
    6 |2 A) f2 N' }# c# C
    点击阅读原文,更精彩~
  • 回复

    使用道具 举报

    发表回复

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

    本版积分规则


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