电子产业一站式赋能平台

PCB联盟网

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

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

[复制链接]

840

主题

840

帖子

6491

积分

高级会员

Rank: 5Rank: 5

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

0vgbbq0aug264018782742.gif

0vgbbq0aug264018782742.gif

3 V6 c7 P& S( ~" o' k; w点击上方蓝色字体,关注我们
$ z" v9 [# J& W# x: Y
" F& t* c: S8 \9 Y12 L8 a" s  X, e/ ?
volatile关键字
( ?$ J3 S* K- x2 evolatile是一个特征修饰符,提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,告诉编译器对该变量不做优化,都会直接从变量内存地址中读取数据,从而可以提供对特殊地址的稳定访问。
; Q/ h- h8 D. }9 s7 v9 O& h4 i. y' v* x3 k+ ]2 j) B& u
常用场景:中断服务与主程序共享变量。示例代码如下:
  z5 N# S! f8 z/ U9 B& j
! l  F# F5 Y6 v3 F3 b6 o7 d( o5 b+ Z5 I
  • //volatile uint8_t flag=1;uint8_t flag=1;; L  V5 j1 @- \) m& J
    void test(void){    while(flag)    {        //do something    }}
    * [+ H1 k) Q6 K' [( K( D3 L//interrupt service routinevoid isr_test(void){    flag=0;}# i5 D+ U+ [& C6 D( k2 C
    如果没使用volatile定义flag,可能在优化后test陷入死循环,因为test里使用的flag并没修改它,开启优化后,编译器可能会固定从某个内存取值。
    5 k" _+ {# ]' w/ n6 o" Z, z; R* g/ y4 q' `2 n% x
    2
    1 z+ y7 b5 C" R; W; Kconst关键字, c) L+ I2 x& n  M7 U
    const 是 constant 的缩写,意思是“恒定不变的”,它是定义常变量的关键字。1 g0 o$ m3 I/ ?; H! g, R; H6 h
    通常有4种用法。( s( i* k, ?: l/ j: N; i' T' a
    1 b3 Z) n8 V. d% ^7 Q; h

    + a+ t4 x4 {7 d. V  K' `2 U5 e1、修饰变量
    5 I4 R6 u! U, V
    " [, r( Q- I3 c6 M采用const修饰变量,即变量声明为只读,保护变量值以防被修改。
    2 O( P. g1 ]/ F5 n) q$ m8 ^. @: r" g9 X- X
  • const int i = 1;或者int const i=1;) d% _# a/ w  n+ P$ h# y
    变量i具有只读特性,不能够被更改;若想对i重新赋值,如i = 10,属于错误操作。0 e- e8 F: ?3 o

    . [& g+ k* X; X4 l+ H
    8 u' z$ C( W) D/ Z* H$ t& g3 o
    2、修饰数组; P; k  {1 K6 h3 g

    + d( z2 r" d4 N数组元素与变量类似,具有只读属性,不能被更改,一旦更改,编译时就会报错。- P2 V+ U7 @' T5 H8 Z8 P# ?

    5 s/ C+ [: B* o. f0 v# i1 s+ }
  • const int array[5] = {1,2,3,4,5};array[0] = array[0]+1; //错误,array是只读的,禁止修改
    8 \- l7 v3 ~$ x! K使用大数组存储固定的信息,例如查表(表驱动法的键值表),可以使用const节省ram。编译器并不给普通const只读变量分配空间,而是将它们保存到符号表中,无需读写内存操作,程序执行效率也会提高。7 j. s) Y! }* n  X% Y: N1 T
    9 ~6 c! I9 t- O  x  ^. H  e
    & m3 G/ h; Y% k9 i
    3、修饰指针
    " v$ M, x8 Z+ f- J
    , k" M5 m, M: Q" c" EC语言中const修饰指针要特别注意,共有两种形式,一种是用来限定指向空间的值不能修改;另一种是限定指针不可更改。
    % p1 V! E1 p* L+ x; Y% J2 B
    3 n4 E; L, v$ [; r& Z+ F
  • int i = 1;int j = 2;1 X9 X& ?( o% K' }8 j' y" L" P7 A5 _
    const int *p1 = &i;int* const p2 = &j;
    0 [9 A% K6 q: ]' S! u上面定义了两个指针p1和p2,区别是const后面是指针本身还是指向的内容。
    5 J) w- ]3 S9 q; A; Z# t8 ?
    0 F- S/ g9 s# ^! L: M7 W5 F在定义1中const限定的是*p1,即其指向空间的值不可改变,若改变其指向空间的值如*p1=10,则程序会报错;但p1的值是可以改变的,对p1重新赋值如p1=&k是没有任何问题的。
    4 z' f; i/ P+ Q8 l0 g" w% {5 W" l7 l
    - V4 D& i) B' T. P) h5 R, V在定义2中const限定的是指针p2,若改变p2的值如p2=&k,程序将会报错;但*p2,即其所指向空间的值可以改变,如*p2=20是没有问题的,程序正常执行。3 t. M5 w  r% O
    7 k/ V2 d" \$ A1 ]6 O5 J& y! E

    + E8 m- ?3 x$ I5 X* M& ^! Z1 l8 n4、 修饰函数参数7 n0 Z# e( S) L) C
    const关键字修饰函数参数,对参数起限定作用,防止其在函数内部被修改,所限定的函数参数可以是普通变量,也可以是指针变量。* g# m' W3 B% z7 B3 k% f# C# s
    $ D" v% ^# _: q; B1 G. z
  • void fun(const int i){    ……    i++; //对i的值进行了修改,程序报错}
    4 h8 w- T1 s$ I3 D# d常用的函数如strlen。  T$ n9 w: A; ~5 ~
    3 w- o; t6 L$ }2 J- A* p: f* s
  • size_t strlen(const char *string);
    - q/ M/ X9 m4 u- ?8 Kconst在库函数中使用非常普遍,是一种自我保护的安全编码思维。
    , d- E' i0 D* A6 L3, T; e8 D$ s2 l  J
    static关键字
    ! F, D  p' L4 f1、static修饰全局变量,该变量只在本文件内被访问,不能在其他文件被直接访问。4 U) X+ e: ]+ q  m3 g  e
    ! \3 ^4 q9 o/ Y8 s: K7 _
    2、static修饰函数,该函数只能在本文件内被访问,不能被其他文件访问。但是可以通过嵌套的方式调用,变相的封装的表现。
    ( }* n# ?5 L; a. g  T% }+ Y4 t* x/ j6 i& z, W  M( x) D
    3、static修饰局部变量,更改该局部变量的生命周期。8 s$ U8 K8 `% T( u' h$ C
  • 生命周期:将临时变量的生命周期变成全局变量的生命周期。
  • 作用域不变:作用域仍然是在本代码块内。$ O& u( }4 b  g0 Q6 N. x* E
    ! N0 }2 \8 r1 ]/ y! X
    4
    + \3 v. d1 T' W$ [; Gstruct与union5 T& q+ d0 ~$ h6 u
    可以使用struct结构体来存放一组不同类型的数据。- |: \' X( B7 I/ X6 [

    6 A. ?2 n# {. X7 w/ X' _
  • struct 结构体名{    结构体所包含的变量或数组};4 c- M$ B9 [. v" X; w1 J  l8 Y
    结构体是一种集合,它里面包含了多个变量或数组,它们的类型可以相同,也可以不同,每个这样的变量或数组都称为结构体的成员,通常我们使用结构体定义和解析协议,如下所示:7 F8 D2 k( X% d/ G& ^

    8 G& a; q9 M" p2 K5 Z  i
  • // 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()* o7 x' P) p6 H  D+ z
    union共用体关键字,定义union下面的成员变量共享一块内存,每一个成员在任一时刻有且只有一个成员使用此块内存。
    ) E, M% h2 @4 k' E4 t# B0 P1 M
  • union 共用体名{    成员列表};
    : h) m1 g& b$ N4 B* a& l$ k+ e! a* H8 n8 \结构体和共用体的区别在于:结构体的各个成员会占用不同的内存,互相之间没有影响;而共用体的所有成员占用同一段内存,修改一个成员会影响其余所有成员。
    * s7 L5 ?, F8 F: X3 ^通常使用共用体做一些标志位操作,例如以下示例,可以非常灵活的访问Val中的bit位。$ l6 c2 q% }5 ^% A, {
    # j/ k. o! L" I1 o4 ~& w
  • 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;+ }' k% v8 I2 W: e7 O; p
    或者使用共用体实现单字节与多字节的转化和拼接,如下所示:
    2 K9 L  o0 z' b. I: W5 Z
    1 m: |3 t) t" b0 V+ `; W9 k
  • #include "stdio.h"
    * t5 k8 X1 S7 q) i; W1 Itypedef struct{    union    {        struct        {            unsigned char low;            unsigned char high;        };        unsigned short result;    };}test_t;- B) }* V$ Q  p# x6 d# E9 `" R
    int main(int argc, char *argv[]){    test_t hello;& n+ W3 B8 j9 U- O7 [$ S" f
        hello.high=0x12;    hello.low=0x34;3 H! l3 y1 U% s- _! @8 |+ _* `
        printf("result=%04X\r
    . Z4 G/ Z- Z' t: ~$ g1 _4 D' l",hello.result);//输出 result=1234 - [' e* ?# O- `6 Z3 ]
        return 0;}5 i& D* \4 t% S2 J1 A/ x3 K$ w

    4 L  h8 E' z8 [9 u9 q8 o5
    - e+ J, W8 I6 K: G" c: m预定义标识符
    6 p8 C. a5 t3 A6 |, O  ~一般编译器都支持预定义标识符,这些标识符结合printf等打印信息帮助程序员调试程序是非常有用的,一般编译器会自动根据用户指定完成替换和处理。+ E: P  t% D3 p( ?2 X& C# T5 i
    ! P0 f/ d/ n3 R3 [  Z) q' \8 k% G
    常用的预定义标识符如下所示:
    * i: Z; I! b% b) F& r* k. j( Q2 v" q( P5 d
  • __FILE__    //表示编译的源文件名__LINE__   //表示当前文件的行号__FUNCTION__  //表示函数名__DATE__  //表示编译日期__TIME__   //表示编译时间
    5 K* f0 S. {* ?; O2 @在Debug打印日志时候经常会用到,如下所示:# Q+ z. s6 U9 q6 z7 M5 ?6 g
    5 @& c9 U4 V( ]" N% ?& s6 @
  • printf("file:%s,line:%d,date:%s,time:%s",__FILE__,__LINE__,__DATE__,__TIME__);7 r: f8 m4 P4 a( l! g* G, T: G
    % L) r+ o, ?! u9 ^/ E/ s" @
    6
    6 ^' D/ E1 T: t# @+ g' S& X#与##: w! @) X; Q/ ]! T- J
    #:是一种运算符,用于带参宏的文本替换,将跟在后面的参数转成一个字符串常量。% K3 w; V. t: u- @& x

    9 ^! Y& d) X5 C& [9 R##:是一种运算符,是将两个运算对象连接在一起,也只能出现在带参宏定义的文本替换中。( y, e4 F( `/ w* [! u

    ' V+ F% ?8 O' A# f" ?, O0 c
  • #include "stdio.h"
    2 O/ r- y; j. J' U2 P! d0 I#define TO_STR(s) #s#define COMB(str1,str2) str1##str2
    8 e; I7 p: p2 ^int main(int argc, char *argv[]){    int UART0= 115200;( |7 R+ m: n8 {5 W: ~+ t2 y( C; S
        printf("UART0=%d
    1 R) B2 Z+ e4 E, l- m* {", COMB(UART, 0));//字符串合并为变量UART0    printf("%s( w. Z: h# {5 Q' r
    ", TO_STR(3.14));//将数字变成字符串: b. q4 T) r, [+ ~
        return 0;}
    9 n1 y6 t$ ~; v) C" T4 W9 O7 [
    ' N. y9 b5 ?) |# @/ s6 g7* ~( F: z. [" X; J  k% Y7 N
    void 与 void*关键字, o3 k6 ]* a- Z% Q% _& z
    void表示的是无类型,不能声明变量或常量,但是可以把指针定义为void类型,如void* ptr。void* 指针可以指向任意类型的数据,在C语言指针操作中,任意类型的数据地址都可转为void* 指针。因为指针本质上都是unsigned int。
    8 o0 V: S- u) S% R3 X# f- @
    0 }! C' ~! ^' b3 a8 O4 q" H0 ^+ X常用的内存块操作库函数:3 z& ?6 {$ |+ T+ v, e$ ?3 E
    8 V0 f/ d' Q* H4 Y
  • void * memcpy( void *dest, const void *src, size_t len );void * memset( void *buffer, int c, size_t num);( ]0 H" b4 z" J+ O; M' Y+ T
    数据指针为void* 类型,对传入任意类型数据的指针都可以操作。另外其中memcpy第二个参数,const现在也如前文所述,拷贝时对传入的原数据内容禁止修改。
    5 E, `6 y2 w5 L5 r4 o& `% S( v1 L2 \3 b; P) e2 H9 M
    特殊说明,指针是不能使用sizeof求内容大小的,在ARM系统固定为int 4字节。对于函数无输入参数的,也尽量加上void,如下所示:$ r1 w- s. r, B0 W2 i9 g

    4 A7 z! A& p/ ^4 I# F8 z
  • void fun(void);
    2 f- ?4 I/ {2 l8 |( e" t/ n8 y4 M- c/ |( G$ O
    8: p1 A1 J) l7 w+ K
    weak关键字( Y4 y; J2 j  m; \0 n
    一般简化定义如下所示:) S4 p$ @% ?' F: v3 Y
    , l& G2 u% b" r  m! S4 C
  • #define _WEAK __attribute__((weak))
    3 j7 B* q) r- P' @& q函数名称前面加上__WEAK属性修饰符称为“弱函数”,类似C++的虚函数。链接时优先链接为非weak定义的函数,如果找不到则再链接带weak函数。( w- z# C+ {5 g
    * @2 ]. C% b) g1 `. w; u$ I
  • _WEAK void fun(void)  {      //do this}  . b6 {2 w1 U, G! P! a* |% H- a; _9 n
    //不在同一个.c,两同名函数不能在同一个文件void fun(void)  {      //do that}
    ; W& b3 S4 P1 i" M! W这种自动选择的机制,在代码移植和多模块配合工作的场景下应用较多。例如前期移植代码,需要调用某个接口fun,但当前该接口不存在或者未移植完整使用,可以使用weak关键字定义为空函数先保证编译正常。9 G" x  ]: s' p& o3 m

    " U$ K7 h( B: u' {! L- A& q( i后续移植完成实现了fun,即软件中有2个fun函数没有任何错误,编译器自动会识别使用后者。当然也粗暴的#if 0屏蔽对fun的调用,但要确保后续记得放开。: T0 f* g, E9 Z. @7 `8 Y/ F: J7 y

    ( q; g# r. H/ B1 c. ?

    1yxnknbkizw64018782842.png

    1yxnknbkizw64018782842.png
    0 v# f) s. l4 o5 X3 t
    往期推荐什么是内存碎片?0 f! l4 d9 Q5 d) q
    详解UDS CAN诊断:什么是UDS(ISO 14229)诊断?
    9 r) I$ X  v. q6 d7 u) }磁耦合共振无线供电装置
    ' b- C$ a$ D5 E: U2 ]C语言:十六进制(HEX)和浮点类型(float、double)转换
      _; C7 K$ V& \! ^HarmonyOS 分布式多端应用一站式开发平台(DevEco Studio 安装)1 u  C( x; }0 |/ b7 c

    / O- p8 u6 Z) X7 r" B

    - i& Z- I; [( u; w! Y9 m# J  o

    ikto4m4zhwe64018782942.jpg

    ikto4m4zhwe64018782942.jpg
    + C- u9 }. ~5 w

    jvqqbumgoef64018783042.gif

    jvqqbumgoef64018783042.gif

    3 m! W' y; {; Y; ~5 E点击阅读原文,更精彩~
  • 回复

    使用道具 举报

    发表回复

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

    本版积分规则


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