中国制造网官方网站首页,东莞寮步招聘网最新招聘信息,网易邮箱163登录,安徽伟诚建设工程有限公司网站现代嵌入式C教程#xff1a;快速的C语言复习PartB 完整的仓库地址在Tutorial_AwesomeModernCPP中#xff0c;您也可以光顾一下#xff0c;喜欢的话给一个Star激励一下作者 5. 指针
指针是C语言最强大也最容易出错的特性#xff0c;在嵌入式编程中尤为重要。这里因为是快速…现代嵌入式C教程快速的C语言复习PartB完整的仓库地址在Tutorial_AwesomeModernCPP中您也可以光顾一下喜欢的话给一个Star激励一下作者5. 指针指针是C语言最强大也最容易出错的特性在嵌入式编程中尤为重要。这里因为是快速的复习只是带大家闪过以下C的指针。5.1 指针基础intvalue42;int*ptrvalue;// ptr存储value的地址intderef*ptr;// 解引用deref 42*ptr100;// 通过指针修改value// 空指针int*null_ptrNULL;// 应始终初始化指针// 指针算术intarray[5]{1,2,3,4,5};int*parray;p;// 指向array[1]intval*(p2);// 访问array[3]val 45.2 指针与数组数组名在大多数情况下会退化为指向首元素的指针欸这可是要注意的是——数组不是指针intnumbers[10];int*ptrnumbers;// 等价于 numbers[0]// 数组访问的两种方式numbers[3]42;// 下标方式*(ptr3)42;// 指针方式等价// 指针遍历数组for(int*pnumbers;pnumbers10;p){*p0;}5.3 多级指针这个玩意让我想起来一个梗图了——一个人指着一个人指着一个人.jpg对就这个意思。一个指向了指向了指向了指向了一个变量的指针变量的指针变量的指针变量。嗯头都绕晕了笔者建议是非必须别玩这出你这是给你的同事埋大的。intvalue42;int*ptrvalue;int**ptr_ptrptr;// 指向指针的指针// 解引用intval1*ptr;// 42intval2**ptr_ptr;// 42多级指针在动态分配二维数组时很有用但在嵌入式系统中应谨慎使用动态内存分配。5.4 指针与constconst和指针的组合有多种含义intvalue42;// 指向常量的指针不能通过ptr修改valueconstint*ptr1value;// *ptr1 100; // 错误ptr1other;// 可以指针本身可以改变// 常量指针指针本身不能改变int*constptr2value;*ptr2100;// 可以可以修改指向的值// ptr2 other; // 错误指针不能改变// 指向常量的常量指针都不能改变constint*constptr3value;// *ptr3 100; // 错误// ptr3 other; // 错误6. 数组与字符串6.1 数组数组是相同类型元素的连续集合// 一维数组intnumbers[10];// 声明intprimes[]{2,3,5,7,11};// 初始化大小自动推导为5intmatrix[3][4];// 二维数组// 数组初始化intzeros[100]{0};// 全部初始化为0intpartial[10]{1,2};// 前两个元素为1和2其余为0// 指定初始化器C99intsparse[100]{[5]10,[20]30};在嵌入式系统中数组常用于缓冲区和查找表// 串口接收缓冲区uint8_tuart_rx_buffer[256];volatilesize_trx_head0;volatilesize_trx_tail0;// 查找表节省计算资源constuint8_tsin_table[360]{// 预计算的正弦值0-255范围128,130,133,135,// ...};6.2 字符串C语言中的字符串是以空字符\0结尾的字符数组charstr1[10]Hello;// 字符串字面量初始化charstr2[]World;// 大小自动推导为6包括\0charstr3[10];// 未初始化// 字符串操作需要包含string.h#includestring.hstrcpy(str3,str1);// 复制字符串strcat(str3,str2);// 连接字符串intlenstrlen(str1);// 获取长度intcmpstrcmp(str1,str2);// 比较字符串在嵌入式系统中应优先使用带长度限制的安全函数版本charbuffer[32];strncpy(buffer,source,sizeof(buffer)-1);buffer[sizeof(buffer)-1]\0;// 确保以空字符结尾// 更安全的做法snprintf(buffer,sizeof(buffer),Value: %d,value);字符串处理的注意事项确保目标缓冲区足够大始终确保字符串以\0结尾在资源受限的系统中考虑使用固定大小的缓冲区避免动态分配7. 结构体、联合体与枚举7.1 结构体结构体允许将不同类型的数据组合成一个单元// 定义结构体structPoint{intx;inty;};// 使用typedef简化typedefstruct{intx;inty;}Point;// 创建和初始化Point p1{10,20};// 顺序初始化Point p2{.y30,.x40};// 指定初始化器C99// 访问成员p1.x100;inty_valuep1.y;// 指针访问Point*ptrp1;ptr-x200;// 等价于 (*ptr).x 200在嵌入式开发中结构体广泛用于表示配置、状态和数据包// 传感器数据结构typedefstruct{uint32_ttimestamp;floattemperature;floathumidity;uint16_tlight_level;uint8_tstatus;}SensorReading;// 通信协议数据包typedefstruct{uint8_theader;uint8_tcommand;uint16_tlength;uint8_tdata[256];uint16_tchecksum;}__attribute__((packed))ProtocolPacket;// 禁用对齐填充7.2 位域位域允许在结构体中以位为单位分配存储这在处理硬件寄存器时极为有用// 寄存器位域定义typedefstruct{uint32_tEN:1;// 使能位uint32_tMODE:2;// 模式选择2位uint32_tRESERVED:5;// 保留位uint32_tPRIORITY:3;// 优先级3位uint32_t:21;// 未命名位域填充}ControlRegister;// 使用volatileControlRegister*ctrl_reg(ControlRegister*)0x40000000;ctrl_reg-EN1;ctrl_reg-MODE2;ctrl_reg-PRIORITY7;注意位域的实现依赖于编译器和平台在需要精确控制时应谨慎使用。7.3 联合体联合体的所有成员共享同一块内存用于节省空间或类型双关// 基本联合体unionData{inti;floatf;charbytes[4];};unionData d;d.i0x12345678;printf(%02X,d.bytes[0]);// 访问字节表示在嵌入式编程中联合体常用于数据类型转换和协议处理// 多类型数据容器typedefunion{uint32_tword;uint16_thalfword[2];uint8_tbyte[4];}DataConverter;DataConverter dc;dc.word0x12345678;// 现在可以按字节访问dc.byte[0], dc.byte[1], ...// 结构体与联合体结合typedefstruct{uint8_ttype;union{intint_value;floatfloat_value;charstring_value[16];}data;}Variant;7.4 枚举枚举定义命名的整数常量集合提高代码可读性// 基本枚举enumColor{RED,// 0GREEN,// 1BLUE// 2};// 指定值enumStatus{STATUS_OK0,STATUS_ERROR-1,STATUS_BUSY1,STATUS_TIMEOUT2};// 使用typedeftypedefenum{STATE_IDLE,STATE_RUNNING,STATE_PAUSED,STATE_ERROR}SystemState;枚举在嵌入式开发中常用于定义状态、命令码和配置选项// 命令定义typedefenum{CMD_NOOP0x00,CMD_READ0x01,CMD_WRITE0x02,CMD_ERASE0x03,CMD_RESET0xFF}Command;// 错误码typedefenum{ERR_NONE0,ERR_INVALID_PARAM1,ERR_TIMEOUT2,ERR_HARDWARE_FAULT3,ERR_OUT_OF_MEMORY4}ErrorCode;8. 预处理器预处理器在编译之前处理源代码它是C语言灵活性的重要来源在嵌入式开发中尤为重要。8.1 宏定义// 对象宏#defineMAX_SIZE100#definePI3.14159f#defineLED_PIN13// 函数宏#defineMAX(a,b)((a)(b)?(a):(b))#defineMIN(a,b)((a)(b)?(a):(b))#defineABS(x)((x)0?-(x):(x))// 多行宏#defineSWAP(a,b,type)do{\type temp(a);\(a)(b);\(b)temp;\}while(0)宏的注意事项参数应该加括号以避免优先级问题多行宏使用do-while(0)包装宏不进行类型检查使用时要小心在嵌入式开发中的典型应用// 寄存器位操作宏#defineBIT(n)(1UL(n))#defineSET_BIT(reg,bit)((reg)|BIT(bit))#defineCLEAR_BIT(reg,bit)((reg)~BIT(bit))#defineREAD_BIT(reg,bit)(((reg)(bit))1UL)#defineTOGGLE_BIT(reg,bit)((reg)^BIT(bit))// 数组大小#defineARRAY_SIZE(arr)(sizeof(arr)/sizeof((arr)[0]))// 范围检查#defineIN_RANGE(x,min,max)(((x)(min))((x)(max)))// 字节对齐#defineALIGN_UP(x,align)(((x)(align)-1)~((align)-1))8.2 条件编译条件编译允许根据条件选择性地包含或排除代码这个东西是跨平台实现的一个基本利器。// 基本条件编译#ifdefDEBUG#defineDEBUG_PRINT(fmt,...)printf(fmt,##__VA_ARGS__)#else#defineDEBUG_PRINT(fmt,...)((void)0)#endif// 使用DEBUG_PRINT(Value: %d\n,value);// 仅在DEBUG定义时输出// 平台相关代码#ifdefined(STM32F4)||defined(STM32F7)#defineMCU_FAMILY_STM32F4_F7#includestm32f4xx.h#elifdefined(STM32L4)#defineMCU_FAMILY_STM32L4#includestm32l4xx.h#else#errorUnsupported MCU family#endif// 功能开关#defineFEATURE_USB1#defineFEATURE_ETHERNET0#ifFEATURE_USBvoidusb_init(void);#endif#ifFEATURE_ETHERNETvoidethernet_init(void);#endif8.3 文件包含// 系统头文件#includestdio.h#includestdint.h// 用户头文件#includeconfig.h#includehal.h// 防止重复包含头文件保护#ifndefCONFIG_H#defineCONFIG_H// 头文件内容#endif// CONFIG_H// 或使用#pragma once非标准但广泛支持#pragmaonce8.4 预定义宏编译器提供了一些有用的预定义宏// 文件和行号#defineLOG_ERROR(msg)\fprintf(stderr,Error in %s:%d - %s\n,__FILE__,__LINE__,msg)// 函数名voidsome_function(void){DEBUG_PRINT(Entered %s\n,__func__);}// 日期和时间printf(Compiled on %s at %s\n,__DATE__,__TIME__);// 标准版本#if__STDC_VERSION__199901L// C99或更高版本#endif9. 存储类别与作用域9.1 存储类别C语言提供了几种存储类别说明符auto局部变量的默认存储类别很少显式使用voidfunction(void){autointx10;// 等价于 int x 10;}static有两种主要用途静态局部变量保持值在函数调用之间voidcounter(void){staticintcount0;// 仅初始化一次count;printf(Called %d times\n,count);}静态全局变量和函数限制作用域在当前文件staticintfile_scope_var0;// 只在本文件可见staticvoidhelper_function(void){// 只能在本文件内调用}extern声明变量或函数在其他文件中定义// file1.cintglobal_counter0;// file2.cexternintglobal_counter;// 声明不分配存储空间voidincrement(void){global_counter;}register建议编译器将变量存储在寄存器中现代编译器通常忽略voidfast_loop(void){registerinti;for(i0;i1000000;i){// 循环变量建议存储在寄存器}}9.2 作用域规则C语言有四种作用域文件作用域、函数作用域、块作用域和函数原型作用域。在嵌入式开发中合理使用作用域可以避免命名冲突和意外的副作用// 文件作用域全局intglobal_var0;staticintfile_static_var0;// 仅本文件可见voidfunction(void){// 函数作用域intlocal_var0;if(condition){// 块作用域intblock_var0;// local_var和block_var都可见}// block_var在这里不可见}10. 内存管理10.1 动态内存分配虽然在嵌入式系统中应尽量避免动态内存分配因为内存碎片和不确定性但了解这些函数仍然重要#includestdlib.h// 分配内存int*array(int*)malloc(10*sizeof(int));if(arrayNULL){// 分配失败处理}// 分配并清零int*zeros(int*)calloc(10,sizeof(int));// 重新分配array(int*)realloc(array,20*sizeof(int));// 释放内存free(array);arrayNULL;// 良好的实践10.2 内存布局理解程序的内存布局对嵌入式开发至关重要这里我们放到后面更加专门的部分介绍这里就是过一下。------------------ 高地址 | 栈(Stack) | 向下增长存放局部变量和函数调用 ------------------ | ↓ | | | | 未分配 | | | | ↑ | ------------------ | 堆(Heap) | 向上增长动态分配内存 ------------------ | BSS段 | 未初始化的全局变量和静态变量 ------------------ | 数据段(Data) | 初始化的全局变量和静态变量 ------------------ | 代码段(Text) | 程序代码只读 ------------------ 低地址在嵌入式系统中通常需要精确控制变量的存储位置// 放置在特定内存区域编译器扩展__attribute__((section(.ccmram)))staticuint32_tfast_buffer[1024];// 对齐要求__attribute__((aligned(4)))uint8_tdma_buffer[256];// 禁止优化__attribute__((used))constuint32_tversion0x01020304;