电子产业一站式赋能平台

PCB联盟网

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

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

[复制链接]

1002

主题

1002

帖子

8854

积分

高级会员

Rank: 5Rank: 5

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

ehkf2mbeaxy6406095230.gif

ehkf2mbeaxy6406095230.gif
1 y* q: X7 F4 b$ {. Y
点击上方蓝色字体,关注我们
1 C& U% o7 G, ~
- t* D( G: x" s1 w9 Y* N0 w1# x) a# d) {9 p% w* B- t  ~" N$ p* l
volatile关键字
6 ~0 k# S9 ]0 g. k, D! Ivolatile是一个特征修饰符,提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,告诉编译器对该变量不做优化,都会直接从变量内存地址中读取数据,从而可以提供对特殊地址的稳定访问。& g  }" J3 U0 l/ C" Y
% N  G. j( F5 o7 m" Q* q. G0 M. }0 h
常用场景:中断服务与主程序共享变量。示例代码如下:" S3 r, p9 M+ t6 V
  h0 R' b  P4 O4 ]# |& L
  • //volatile uint8_t flag=1;uint8_t flag=1;
    1 y* h. U. o" A5 n4 o& s. {void test(void){    while(flag)    {        //do something    }}
    . H4 d5 U( W' h# p//interrupt service routinevoid isr_test(void){    flag=0;}- `) Z( _' }, @% y6 ]; D. l
    如果没使用volatile定义flag,可能在优化后test陷入死循环,因为test里使用的flag并没修改它,开启优化后,编译器可能会固定从某个内存取值。
    9 N8 M; z: D+ b: ^# Y* h- b" a; }2 U+ i. d9 b
    24 P0 W  @) F0 _: f) {
    const关键字& f3 W( ^8 }3 C( i/ f% u( J
    const 是 constant 的缩写,意思是“恒定不变的”,它是定义常变量的关键字。
    4 r! {4 @; Z5 [, o: K( |2 w$ d: A通常有4种用法。
    & H- G' C- ~+ G/ v& p% }" j# _7 ?5 {0 O) L; U( B0 \
      \% ]+ [0 f' S/ T) h% o3 K
    1、修饰变量2 X* c: Y; F9 n/ B7 Z+ X$ v; E

    ' H& K  g( t! e7 D5 q采用const修饰变量,即变量声明为只读,保护变量值以防被修改。
    ; Z/ n  k9 h' i6 ?# d8 |: G
    2 Z  R8 p, ^# R) ]" `" l2 v
  • const int i = 1;或者int const i=1;
    , o7 S* J9 l4 [2 g6 U2 l- V, l变量i具有只读特性,不能够被更改;若想对i重新赋值,如i = 10,属于错误操作。
    ( k5 g6 v- X, c3 w: c: [- ]" t+ m1 ]$ \, w$ t9 }
    % j: Q: s5 d  D, j
    2、修饰数组
    + N; w1 D2 _4 \2 p
    , j, x# ?; t* q8 j) c) z数组元素与变量类似,具有只读属性,不能被更改,一旦更改,编译时就会报错。7 B, d+ D. P4 B: k$ z4 N3 s
    : t1 b9 {# U& |: ~2 e$ g" r- \0 o- R
  • const int array[5] = {1,2,3,4,5};array[0] = array[0]+1; //错误,array是只读的,禁止修改% Z+ Z6 G& E3 Y# H
    使用大数组存储固定的信息,例如查表(表驱动法的键值表),可以使用const节省ram。编译器并不给普通const只读变量分配空间,而是将它们保存到符号表中,无需读写内存操作,程序执行效率也会提高。
    1 G  n% t* u& U+ a; y- I
    . Z1 L* L% P6 O" L% Z

    ' A& ~- Z5 P- A6 t  R/ N( d3、修饰指针% R: e. Q4 L* o! g4 p0 c

    3 n. {0 L9 a0 I2 GC语言中const修饰指针要特别注意,共有两种形式,一种是用来限定指向空间的值不能修改;另一种是限定指针不可更改。
    2 o( |8 {, `5 A" I( E( }- I) x- T7 x$ N$ g& [" f
  • int i = 1;int j = 2;
    % z; T" _: }$ e: u, `; nconst int *p1 = &i;int* const p2 = &j;
    ' i6 n0 u  U0 c: P9 z0 ]6 A5 s上面定义了两个指针p1和p2,区别是const后面是指针本身还是指向的内容。/ m, t0 e5 j: u2 T

    ' P% ^/ {* G+ F7 j6 G1 S+ q  S在定义1中const限定的是*p1,即其指向空间的值不可改变,若改变其指向空间的值如*p1=10,则程序会报错;但p1的值是可以改变的,对p1重新赋值如p1=&k是没有任何问题的。
    ( D5 u4 n* S/ {$ Y$ q: V0 ~1 M! Y
    在定义2中const限定的是指针p2,若改变p2的值如p2=&k,程序将会报错;但*p2,即其所指向空间的值可以改变,如*p2=20是没有问题的,程序正常执行。
    ( K" M( c" g; H; g% ~( R5 ~1 D2 e5 d. c
    $ w0 ~- ~# i* h) U; G2 m
    4、 修饰函数参数- I- T9 y% }! _, A5 ]% Z
    const关键字修饰函数参数,对参数起限定作用,防止其在函数内部被修改,所限定的函数参数可以是普通变量,也可以是指针变量。
    7 K- C3 I6 o# C7 o' I( j
    8 {" g5 W% G9 `' i
  • void fun(const int i){    ……    i++; //对i的值进行了修改,程序报错}
    ) M8 ]0 |# M3 d( f常用的函数如strlen。7 j& d, U% @& v, j' t# C% Q

    ! i" v5 F' C' q' u1 ]
  • size_t strlen(const char *string);- p7 o" G( F. y, K1 z! Q% D
    const在库函数中使用非常普遍,是一种自我保护的安全编码思维。, L  U  Q# x  s3 Z% M4 z
    3
    - E6 ?8 t8 Z) M$ D/ K/ r) f- nstatic关键字
    3 Z* b& P2 \& i. ~: W1 V& ]1、static修饰全局变量,该变量只在本文件内被访问,不能在其他文件被直接访问。
    + J8 h# P) A; e' ]4 i; K/ p" D" k8 g/ ~+ R! V/ C& V! |
    2、static修饰函数,该函数只能在本文件内被访问,不能被其他文件访问。但是可以通过嵌套的方式调用,变相的封装的表现。) O" k' u  P! N( f$ m% y
    1 P4 v. [3 B- E" Q, L, _
    3、static修饰局部变量,更改该局部变量的生命周期。
    & J, {2 w3 g) u
  • 生命周期:将临时变量的生命周期变成全局变量的生命周期。
  • 作用域不变:作用域仍然是在本代码块内。7 _5 N' F. A/ O' x2 Q. Q$ y

    5 {" a8 O* ~; E4/ F+ d: {) s7 b0 g% m
    struct与union) S+ U0 Y& l; a7 d4 h
    可以使用struct结构体来存放一组不同类型的数据。
    : k* C( @& M1 q, z' y, \! N" h5 I2 _0 _! q  Q
  • struct 结构体名{    结构体所包含的变量或数组};
    & e1 ?4 K# D' s$ k9 r( l6 E. g结构体是一种集合,它里面包含了多个变量或数组,它们的类型可以相同,也可以不同,每个这样的变量或数组都称为结构体的成员,通常我们使用结构体定义和解析协议,如下所示:
    0 T- ]. _9 }3 x) {2 l  }( e* s! g3 M, R8 N7 o, H5 x' h; ?4 ~) N" A( c
  • // 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(), o5 W, u% N: a" f( d: I7 c; Y
    union共用体关键字,定义union下面的成员变量共享一块内存,每一个成员在任一时刻有且只有一个成员使用此块内存。
    + @$ E% W$ j0 j* \5 a# y7 t- j- j) v7 _5 @/ [- E" V6 a! o
  • union 共用体名{    成员列表};
    # J6 Q0 U. {/ ?3 j结构体和共用体的区别在于:结构体的各个成员会占用不同的内存,互相之间没有影响;而共用体的所有成员占用同一段内存,修改一个成员会影响其余所有成员。& p# X* p; E7 Z/ @) V
    通常使用共用体做一些标志位操作,例如以下示例,可以非常灵活的访问Val中的bit位。
    / D, ?& y4 |6 o" V
    / w, p7 e3 [9 B7 \3 r! n  G2 r7 }
  • 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;, [* i5 q- F7 A7 p
    或者使用共用体实现单字节与多字节的转化和拼接,如下所示:
    * H2 [5 x. o$ c; ?
    ; s9 v; Q- X- o/ R+ `
  • #include "stdio.h"
    / L2 i0 l. m/ {2 T$ ztypedef struct{    union    {        struct        {            unsigned char low;            unsigned char high;        };        unsigned short result;    };}test_t;
    , m. y' U" {( fint main(int argc, char *argv[]){    test_t hello;
    1 k; k# }) c1 o- `9 w9 r    hello.high=0x12;    hello.low=0x34;
    ( a! P) N; m, t0 F% Z    printf("result=%04X\r
    5 R: d0 p3 }/ J& p+ T",hello.result);//输出 result=1234
    # W# L4 p+ R+ T    return 0;}) S2 l# P1 \. T9 H% d

    * j% I: l6 Z2 K5
      z3 H" g* \- }; j预定义标识符
    ! |) [7 O" N/ a* T一般编译器都支持预定义标识符,这些标识符结合printf等打印信息帮助程序员调试程序是非常有用的,一般编译器会自动根据用户指定完成替换和处理。. _  ]; U. }9 z% z) X0 Y

    * O9 w3 l7 e; h* r( ?: \常用的预定义标识符如下所示:
    # P# m8 O# a( t. p9 W' U3 |
    5 T/ c/ g% j+ ^9 d0 S) f) k: g& Z
  • __FILE__    //表示编译的源文件名__LINE__   //表示当前文件的行号__FUNCTION__  //表示函数名__DATE__  //表示编译日期__TIME__   //表示编译时间8 F/ e8 c0 a4 }7 @- Z. I2 j8 Q
    在Debug打印日志时候经常会用到,如下所示:7 t# p% U# \* ]+ P
    $ ?: S! |0 x3 Q7 J
  • printf("file:%s,line:%d,date:%s,time:%s",__FILE__,__LINE__,__DATE__,__TIME__);
    # h9 S  I2 y# }- {( o* N" C
    1 g- b/ ?6 H" E( H) A1 L6 Y9 d' l3 m$ {6
    : g8 v4 k+ b* s# X( M$ f& B#与##( }4 p+ O5 Y$ `  q% c7 g
    #:是一种运算符,用于带参宏的文本替换,将跟在后面的参数转成一个字符串常量。$ l" g( O4 t1 ^* L$ f9 Y0 m
    6 @# C7 O, V6 ^/ \
    ##:是一种运算符,是将两个运算对象连接在一起,也只能出现在带参宏定义的文本替换中。' Q# b1 R' S6 P2 k3 l
    5 }1 D& r: [& J; I. O* V$ |
  • #include "stdio.h"
    5 m$ R9 l% p9 Q% ^1 `" [#define TO_STR(s) #s#define COMB(str1,str2) str1##str2  E! x$ V1 T% F6 P
    int main(int argc, char *argv[]){    int UART0= 115200;
    $ P5 ?& O. }3 t) ^7 m    printf("UART0=%d
    ! T$ A2 d% t8 @  F" A  Z( Z1 L", COMB(UART, 0));//字符串合并为变量UART0    printf("%s
    ) c( f" Z! {5 N4 f", TO_STR(3.14));//将数字变成字符串( J* B$ q2 D  m! {" c: Z$ X
        return 0;}9 i. X2 B( H3 ]$ n8 M& g
    7 f% s+ F$ Q" p: o4 ]  e7 A
    7
    6 z8 y) m" u0 Cvoid 与 void*关键字7 o1 \% C9 L$ I. K) i
    void表示的是无类型,不能声明变量或常量,但是可以把指针定义为void类型,如void* ptr。void* 指针可以指向任意类型的数据,在C语言指针操作中,任意类型的数据地址都可转为void* 指针。因为指针本质上都是unsigned int。
    ' a! x6 e: X! p3 R5 W! J1 N- T4 P/ \0 y0 K0 h+ w
    常用的内存块操作库函数:
    % m: k# k8 g$ ]; d) y
    1 |! l4 M. g9 p" h% A! I! g- N
  • void * memcpy( void *dest, const void *src, size_t len );void * memset( void *buffer, int c, size_t num);
    ! x% K" f( U1 i* ]0 V5 C5 M数据指针为void* 类型,对传入任意类型数据的指针都可以操作。另外其中memcpy第二个参数,const现在也如前文所述,拷贝时对传入的原数据内容禁止修改。* L- s/ p0 a9 O2 B1 M

    : k4 C8 g9 B8 g3 }' i# t特殊说明,指针是不能使用sizeof求内容大小的,在ARM系统固定为int 4字节。对于函数无输入参数的,也尽量加上void,如下所示:, ?4 x$ I, e, E) Z2 V! M6 r8 G% c
    0 r; E; A; y7 [$ P
  • void fun(void);- i2 f/ K# ^' y0 P! P

    7 J7 h7 x! h4 u" v8
    4 b" V6 I" e! }5 Nweak关键字8 V4 W( X- N. k( k$ c
    一般简化定义如下所示:
    # u2 E+ h+ c2 ^" w  G* }; g9 j3 Q- F: u, G& S$ J$ l
  • #define _WEAK __attribute__((weak))
    ! ]4 i2 r% g5 n函数名称前面加上__WEAK属性修饰符称为“弱函数”,类似C++的虚函数。链接时优先链接为非weak定义的函数,如果找不到则再链接带weak函数。
    $ e5 _2 X: x7 x5 Q# w8 C  T5 C8 r# g/ U' C
  • _WEAK void fun(void)  {      //do this}  & u: u2 h& z3 w7 F
    //不在同一个.c,两同名函数不能在同一个文件void fun(void)  {      //do that}
    : N* u  y- `0 t' \( r, k这种自动选择的机制,在代码移植和多模块配合工作的场景下应用较多。例如前期移植代码,需要调用某个接口fun,但当前该接口不存在或者未移植完整使用,可以使用weak关键字定义为空函数先保证编译正常。& q) q& I' {7 P0 [) H! p

    " j7 B+ O. O% z% u# U/ ~后续移植完成实现了fun,即软件中有2个fun函数没有任何错误,编译器自动会识别使用后者。当然也粗暴的#if 0屏蔽对fun的调用,但要确保后续记得放开。& j) @* W& _- ]- i1 a

    ; p$ j5 d1 f4 r2 W% G+ T

    obzzf0ax3dx6406095330.png

    obzzf0ax3dx6406095330.png
    . l4 }- j  ^4 O# ]6 k
    往期推荐什么是内存碎片?
    , r7 z7 N. c' h" @' Q4 R详解UDS CAN诊断:什么是UDS(ISO 14229)诊断?
    2 k6 d  X; I* i( i& F磁耦合共振无线供电装置8 v' w' ~9 i+ L! ^( P
    C语言:十六进制(HEX)和浮点类型(float、double)转换
    0 q, @+ ~  I$ M) N0 PHarmonyOS 分布式多端应用一站式开发平台(DevEco Studio 安装)6 P* s3 a" O  F6 g# r6 [% N

    , j( z2 Y1 L; D: `( x8 X' Y, y

    & ]( Q- v; I9 z

    0gfwuj32of06406095430.jpg

    0gfwuj32of06406095430.jpg

    3 G! I8 C4 F, q7 t+ G) P

    isieitqkphf6406095530.gif

    isieitqkphf6406095530.gif

    4 b, C. S3 h. c8 I( l点击阅读原文,更精彩~
  • 回复

    使用道具 举报

    发表回复

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

    本版积分规则


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