
FreeRTOS 第三课C 语言扩展 学习笔记嵌入式学习笔记 · FreeRTOS 第 3 课今天学习了 C 语言扩展变量的作用域和生命周期、变量在 SRAM 里的分布、堆和栈的概念、FreeRTOS 的任务栈以及 FreeRTOS 的代码规范整理成文方便复习。文章目录FreeRTOS 第三课C 语言扩展 学习笔记一、变量的作用域和生命周期1. C 语言的 5 类变量2. 完整代码例子二、变量在 SRAM 里的分布1. 单片机的 FLASH 和 SRAM2. 变量在 SRAM 的分布3. Minimum Heap Size 和 Minimum Stack Size三、堆的概念四、栈1. 栈的概念2. 调用栈Call Stack3. FreeRTOS 的任务栈五、FreeRTOS 的代码规范1. 三个常用数据类型2. 变量的命名规则前缀 实际名字3. 函数的命名规则返回类型 模块名 实际名字4. 宏定义的命名规则六、小结速查表一、变量的作用域和生命周期写 C 语言/嵌入式代码经常要回答两个问题这个变量在哪里能用—— 这就是作用域Scope这个变量什么时候创建、什么时候销毁—— 这就是生命周期Lifetime概念回答的问题通俗说法作用域变量在哪些代码里可见“这个变量管到哪一行”生命周期变量从生到死的过程“这个变量活多久”记忆作用域是空间范围生命周期是时间范围。1. C 语言的 5 类变量变量类型生命周期作用域典型用途全局变量程序开始 → 程序结束项目当中的所有文件需要跨文件共享的场合局部变量进入函数/代码块 → 退出函数/代码块定义它的函数/代码块函数内部存储临时数据静态全局变量程序开始 → 程序结束定义它的 .c 文件封装模块的内部数据静态局部变量首次进入函数 → 程序结束定义它的函数/代码块函数内需要保持的数据堆变量从 malloc() → 到 free()无固定作用域靠指针访问长度不定的数据注意堆变量没有固定作用域——它在堆上分配后靠指针在任意代码中访问指针传到哪就能在哪用。2. 完整代码例子下面这个例子把 5 类变量全部用上用户输入数字 n程序分配一个 n 个元素的 int 数组依次计算 1²、2²、3²……填入数组并打印。文件 1my_file.hintGetNextSquare();/* 获取下一个平方数 */文件 2my_file.c/* 静态全局变量仅展示用 */staticintstaticGlobalVar1100;/* 作用: 输入一个数, 计算它的平方 */staticintSquare(intinput)/* input函数参数 */{returninput*input;}/* 作用: 获取下一个平方数 */intGetNextSquare(){/* 静态局部变量存储当前底数的值 */staticintnum1;/* 计算 num 的平方 */intret;retSquare(num);/* 递增底数的值 */num;/* 返回结果 */returnret;}文件 3main.c#includestdio.h#includestdlib.h#includemy_file.hintglobalVar136;/* 全局变量1, 仅展示用 */intglobalVar2[100];/* 全局变量2, 仅展示用 */intmain(intargc,char*argv[]){intn;/* 局部变量 *//* #1. 要求用户输入数字n, n表示要产生的平方数的个数 */scanf(%d,n);/* #2. 使用malloc()分配一个长度为n的int型数组 */int*pBuffer(int*)malloc(sizeof(int)*n);/* 堆变量 *//* #3. 逐个计算平方数, 并填写到数组中 */for(inti0;in;i){pBuffer[i]GetNextSquare();}/* #4. 打印结果到控制台 */for(inti0;in;i){printf(%d, ,pBuffer[i]);}printf(\n);return0;}运行结果输入55 1, 4, 9, 16, 25,GetNextSquare() 每调用一次静态局部变量 num 自增 1返回 num²如果 num 是普通局部变量每次调用都会从 1 重新开始结果会变成 5 个 1。代码里各变量对照代码位置变量类型main.c 顶部globalVar1 / globalVar2全局变量main() 内n局部变量main() 内pBuffer堆变量my_file.c 顶部staticGlobalVar1静态全局变量Square() 参数input函数参数类似局部变量GetNextSquare() 内num静态局部变量GetNextSquare() 内ret局部变量二、变量在 SRAM 里的分布1. 单片机的 FLASH 和 SRAM对比项FLASHFlash 存储器SRAM静态随机存储器存什么程序代码、常量const变量全局/局部/堆栈掉电后数据不丢失数据丢失特点容量大、慢、只读运行中容量小、快、可读可写地址0x08000000 起0x20000000 起记忆程序住 FLASH变量住 SRAM。2. 变量在 SRAM 的分布SRAM 从上到下分为几段编译器把不同类型的变量放到不同段里┌───────────────────────────────┐ 高地址 │ .stack 段 │ 栈局部变量、函数参数等 │ 向下增长 │ 不包括静态局部变量 ├───────────────────────────────┤ │ 未分配区域 │ heap 和 stack 之间可扩展的空间 ├───────────────────────────────┤ │ .heap 段 │ 堆malloc() 分配的内存 │ 向上增长 │ ├───────────────────────────────┤ │ .bss 段 │ 无初值或零初值的全局变量/静态变量 ├───────────────────────────────┤ │ .data 段 │ 有初值的全局变量/静态变量 └───────────────────────────────┘ 低地址段存放内容说明.data 段有初值的全局变量或静态变量程序启动时从 FLASH 拷贝初值到 SRAM.bss 段无初值或者零初值的全局变量或静态变量启动时清零.heap 段堆malloc() 分配的内存从低地址向上增长.stack 段栈局部变量、参数等不包括静态局部变量从高地址向下增长未分配区域heap 与 stack 之间的剩余空间两边按需抢地盘要点静态局部变量不放在栈里它和全局变量一样放在 .data/.bss 段所以才能跨调用保持值。3. Minimum Heap Size 和 Minimum Stack Size在 Keil 工程里Options → Target有两个和 SRAM 分配相关的设置配置作用对应段Minimum Heap Size给 C 库 malloc/free 预留的最小堆大小.heap 段Minimum Stack Size给启动代码预留的最小栈大小.stack 段它们写在启动文件startup_xxx.s里Heap_Size EQU 0x00000200 Stack_Size EQU 0x00000400这是裸机/启动阶段的堆栈大小FreeRTOS 起来之后任务栈由 FreeRTOS 自己从 heap_x 分配互不干扰。三、堆的概念**堆Heap**是程序运行期间动态分配内存的区域特点说明分配方式malloc() / calloc() 动态申请释放方式free() 手动释放大小运行时才决定长度不定的数据管理由 C 库/内存管理器维护风险不 free 就内存泄漏多次分配释放会产生碎片int*p(int*)malloc(sizeof(int)*100);/* 申请 */if(p!NULL){/* 使用 */}free(p);/* 释放必须成对 */pNULL;在 FreeRTOS 里任务栈、队列等内核对象的内存由 FreeRTOS 自己的堆heap_1 ~ heap_5管理和 C 库的 malloc 是两套体系第二课讲过。四、栈1. 栈的概念**栈Stack**是一块后进先出LIFO的内存区域压栈Push数据从栈顶压入出栈Pop数据从栈顶弹出栈顶指针 SP 由 CPU 自动维护在 ARM 上栈从高地址向低地址增长。voidfuncA(void){inta1;funcB();}voidfuncB(void){intb2;}调用过程funcA 开始 → 压入 a1 funcA 调用 funcB → 压入返回地址 funcB 开始 → 压入 b2 funcB 返回 → 弹出 b、返回地址 funcA 返回 → 弹出 a2. 调用栈Call Stack函数调用时栈上依次保存内容说明返回地址函数结束后回到哪里函数参数传入的实参或寄存器保存局部变量函数内部定义的变量保存的寄存器调用前需要保留的 CPU 寄存器每调用一层函数栈就长高一层往下压递归太深或局部变量太大 →栈溢出Stack Overflow。3. FreeRTOS 的任务栈每个 FreeRTOS 任务都有一块独立的任务栈对比项裸机主栈FreeRTOS 任务栈数量只有 1 个每个任务 1 个分配方式启动文件预留xTaskCreate 时从堆分配大小Minimum Stack SizexTaskCreate 的 uxStackDepth 参数单位字内容主函数调用链任务的局部变量 任务切换时的上下文寄存器/* uxStackDepth 128表示 128 字 512 字节的任务栈 */xTaskCreate(Task_LED,LED,128,NULL,1,NULL);任务切换时FreeRTOS 把当前任务的寄存器压入它自己的栈再从下一个任务的栈里恢复互不干扰任务栈太小 → 栈溢出 → 程序跑飞可以开configCHECK_FOR_STACK_OVERFLOW检测。记忆裸机只有一个大栈RTOS 每个任务一个小栈。五、FreeRTOS 的代码规范FreeRTOS 源码有一套统一的命名规范看懂前缀就能猜出类型和作用。1. 三个常用数据类型类型作用说明TickType_t记录系统节拍的数据类型具体类型取决于 configTICK_TYPE_WIDTH_IN_BITS16/32 位BaseType_t基本数据类型表示成功/失败、是/否当前处理器架构下最有效率的类型STM32 int32_tUBaseType_tBaseType_t 的无符号版本非负数对 STM32F1 单片机为 uint32_tTickType_t xLastWakeTime;/* tick 计数 */BaseType_t xReturn;/* pdPASS / pdFAIL */UBaseType_t uxPriority;/* 优先级非负数 */2. 变量的命名规则前缀 实际名字前缀类型vvoidccharint8_tsshortint16_tllongint32_txBaseType_t、TickType_t或其它未知数据类型eenum枚举类型p指针uunsigned无符号数例子TickType_t xTicksToWait;/* x TickType_t */UBaseType_t uxPriority;/* u x unsigned BaseType_t */charcChar;/* c char */void*pvParameters;/* p v void 指针 */3. 函数的命名规则返回类型 模块名 实际名字函数名 返回类型前缀 所属模块名 动作名函数拆解含义vTaskDelayvvoid Task模块 Delay动作任务延时无返回值xTaskCreatexBaseType_t Task Create创建任务返回成功/失败xQueueSendx Queue Send发送到队列返回结果vSemaphoreGivev Semaphore Give释放信号量补充以prv开头的函数如 prvIdleTask表示模块内部私有函数static。4. 宏定义的命名规则宏定义全部大写 下划线分隔分类例子配置宏configUSE_PREEMPTION、configTICK_RATE_HZ返回值宏pdTRUE、pdFALSE、pdPASS、pdFAIL错误宏errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY移植宏portMAX_DELAY、portYIELD、portTICK_PERIOD_MS#defineconfigUSE_PREEMPTION1#definepdTRUE(1)#defineportMAX_DELAY(0xFFFFFFFFUL)六、小结速查表问题答案作用域变量在哪能用空间范围生命周期变量活多久时间范围5 类变量全局、局部、静态全局、静态局部、堆变量堆变量作用域无固定作用域靠指针访问程序代码/常量放哪FLASH变量放哪SRAM.data 段有初值的全局/静态变量.bss 段无初值/零初值的全局/静态变量堆malloc/free向上增长栈局部变量/参数向下增长LIFOFreeRTOS 任务栈每任务一个xTaskCreate 时从堆分配TickType_t记录系统节拍16/32 位BaseType_t / UBaseType_t有符号/无符号基本类型STM32 为 int32_t/uint32_t变量命名类型前缀 名字v/c/s/l/x/e/p/u函数命名返回类型前缀 模块名 动作名宏命名全大写 下划线记忆口诀作用域管空间生命周期管时间。程序住 FLASH变量住 SRAM.data 存有值、.bss 存清零、heap 向上、stack 向下。堆要手动 free栈自动生长RTOS 里每任务一个栈。x 是 BaseType_tu 是无符号p 是指针v 是 void函数名类型前缀模块动作。本文为 FreeRTOS 第三课学习笔记如有错误欢迎评论区指正。