1 C知识总结
菜鸟教程之C语言基础上 - beiweisanshidu - 博客园C 语言教程 | 菜鸟教程1.三目运算符#include stdio.h // 带参数宏求两个数的最大值 #define MAX(a, b) ((a) (b) ? (a) : (b)) #define LED(c) ((c) ? (1) : (0)) int main() { int x 10, y 20; // 使用宏 // int max_val MAX(x, y); int max_val ((x) (y) ? (x) : (y)); int set LED(1); printf(set %d, 最大值%d\n, set, max_val); return 0; }2.malloc free3.堆栈等内存分配全局变量、指针等用法、例子这是一个完整的 STM32F103 计算器演示程序约 350 行涵盖了你要求的全部内存段和变量类型各内存段对应代码内存段物理位置代码示例.textFlash所有函数体如Calc_Add()、Calc_Div()、Calc_Run()等.rodataFlashconst uint8_t g_OperandCount 2、const uint16_t g_CalcVersion 0x0100、g_OpSymbols[]字符串表.dataRAMuint32_t g_CalcCount 0、static uint8_t s_InitFlag 1有初始值的全局变量.bssRAMint32_t g_LastResult、int8_t g_ErrorCode、static uint8_t s_IsInitialized无初始值的全局变量StackRAM所有函数内的局部变量如int32_t a, b、char buffer[64]、CalcResult_t result等变量用法覆盖MCU内存段的作用-CSDN博客概念出现位置全局变量g_CalcCount.data、g_LastResult.bss局部变量各函数内的a、b、result、product、temp1等普通指针CalcOp_t *pOp operationconst T*指针指向的内容只读const CalcOp_t *pConstOpT *const指针常量指针本身不可变注释中演示buffer数组名本身就是const T *const双 constconst CalcResult_t *const pResultconst变量g_CalcVersion、g_OperandCount函数指针Calc_Execute()的第三个参数函数定义与调用四则运算函数、调度函数、打印函数结构体返回CalcResult_t Calc_Div()枚举CalcOp_t { OP_ADD, OP_SUB, OP_MUL, OP_DIV }计算器功能Calculator_Demo()函数会依次执行100 ÷ 20 5使用 const 指针传入操作符50 5 55、50 - 5 45、50 × 5 250、50 ÷ 5 10循环演示全部四则运算安全缓冲区拷贝演示从.rodata读取版本号等常量/* * calculator_demo.c — MCU 内存模型演示简单计算器 * * 本文件演示 STM32F103 上 .text / .rodata / .data / .bss / Stack 各段 * 以及全局变量、局部变量、指针、const 指针、const 变量、函数的完整用法。 * * 计算器功能支持 、-、×、÷ 四则运算结果通过串口或 LCD 等外设输出。 * 实际输出函数可自行对接 HAL_UART_Transmit 或 LCD 驱动 * * 内存布局速查 (STM32F103) * 区域 起始地址 存放内容 * Flash 0x08000000 .text (代码) .rodata (只读数据) * RAM 0x20000000 .data (已初始化全局/静态变量) * .bss (未初始化全局/静态变量) * Heap (堆) * Stack (栈) * */ /* Includes ------------------------------------------------------------------*/ #include stdint.h #include string.h #include stdio.h /* * 1. .rodata 段 —— 只读数据 (存储在 Flash 中掉电不丢失) * 关键字 const 修饰的全局变量编译后放入 .rodata 段。 * */ /** brief 计算器支持的操作符表字符串常量也在 .rodata 段 */ static const char *const g_OpSymbols[] { , /* 加法 */ -, /* 减法 */ *, /* 乘法 */ / /* 除法 */ }; /** brief 操作数数量常量 - const 修饰的全局整型变量存放在 .rodata */ const uint8_t g_OperandCount 2; /** brief 计算器版本号 - const 全局变量 */ const uint16_t g_CalcVersion 0x0100; /* v1.0 */ /** brief 最大操作数缓冲区大小 - const 全局变量 */ const uint8_t g_MaxBufferSize 64U; /* * 2. .data 段 —— 已初始化全局/静态变量 (存储在 RAM 中) * 启动代码会将 Flash 中的初始值拷贝到 RAM 的 .data 区。 * */ /** brief 计算总次数 - 全局变量有初始值存入 .data 段 */ uint32_t g_CalcCount 0; /** brief 连续正确计算次数 - 全局变量有初始值存入 .data 段 */ int16_t g_ConsecutiveCorrect 0; /** brief 静态计数器 - static 全局变量作用域仅限于本文件也存入 .data 段 */ static uint8_t s_InitFlag 1; /* * 3. .bss 段 —— 未初始化全局/静态变量 (存储在 RAM 中启动时清零) * 没有初始值或初始值为 0 的全局/静态变量。 * */ /** brief 上一次计算结果 - 未初始化默认 0放入 .bss 段 */ int32_t g_LastResult; /** brief 错误标志 - 未初始化放入 .bss 段 */ int8_t g_ErrorCode; /** brief 静态状态标志 - static 未初始化也放入 .bss 段 */ static uint8_t s_IsInitialized; /* * 4. 类型定义 枚举 (仅类型描述不占用存储空间) * */ /** brief 四则运算枚举 */ typedef enum { OP_ADD 0, /* 加法 */ OP_SUB 1, /* 减法 */ OP_MUL 2, /* 乘法 */ OP_DIV 3 /* 除法 */ } CalcOp_t; /** brief 计算结果结构体 */ typedef struct { int32_t value; /* 计算结果值 */ uint8_t isValid; /* 结果是否有效: 0无效, 1有效 */ uint8_t errorCode; /* 错误码: 0无错误, 1除零错误, 2溢出 */ } CalcResult_t; /* * 5. 函数声明 (.text 段 —— 代码区) * 函数代码编译后全部存放在 Flash 的 .text 段。 * */ /* 基本运算函数 */ static int32_t Calc_Add(int32_t a, int32_t b); static int32_t Calc_Sub(int32_t a, int32_t b); static int32_t Calc_Mul(int32_t a, int32_t b); static CalcResult_t Calc_Div(int32_t a, int32_t b); /* 核心计算函数 —— 接收函数指针作为参数演示指针用法 */ static CalcResult_t Calc_Execute(int32_t a, int32_t b, CalcResult_t (*operation)(int32_t, int32_t)); /* 调度函数 —— 接收 const 指针参数演示 const 指针用法 */ static CalcResult_t Calc_Run(int32_t a, int32_t b, const CalcOp_t *pOp); /* 格式化输出函数 —— 演示 const 指针指向 const 数据的用法 */ static void Calc_PrintResult(const CalcResult_t *const pResult, const int32_t *const pA, const int32_t *const pB, const char *const *pSymbol); /* 缓冲区溢出安全测试函数 —— 演示指针与数组的关系 */ static void Calc_BufferWriteSafe(uint8_t *pBuf, const uint8_t *pSrc, uint8_t len, uint8_t maxLen); /* 初始化函数 */ void Calculator_Init(void); /* * 6. 函数定义 (.text 段) * */ /** brief 初始化计算器模块 */ void Calculator_Init(void) { int32_t localInitValue 0; /* 局部变量 —— 分配在栈上 */ /* 全局变量赋值 */ g_CalcCount 0; g_ConsecutiveCorrect 0; g_LastResult 0; g_ErrorCode 0; /* 静态变量赋值 */ s_InitFlag 0; /* 标记已初始化 */ s_IsInitialized 1; /* .bss 变量现在赋予初值执行期赋值不是编译期 */ /* 局部变量仅在当前函数内有效函数返回后栈空间释放 */ (void)localInitValue; /* 防止编译警告 */ } /** * brief 加法运算 * param a 加数1 * param b 加数2 * return a b * * 函数体存放在 .text 段。 * 形参 a, b 由调用者压栈传入在函数内部也是栈上变量。 */ static int32_t Calc_Add(int32_t a, int32_t b) { return (a b); } /** * brief 减法运算 */ static int32_t Calc_Sub(int32_t a, int32_t b) { return (a - b); } /** * brief 乘法运算 * 演示局部变量存储中间结果在栈上。 */ static int32_t Calc_Mul(int32_t a, int32_t b) { int32_t product; /* 局部变量 —— 分配在栈上 */ int32_t temp1 a; /* 局部变量带初始化 —— 也在栈上 */ int32_t temp2 b; /* 同上 */ product temp1 * temp2; /* 演示栈变量地址product 是栈上的一个地址 */ /* 注意函数返回后此地址无效 */ return product; } /** * brief 除法运算 * 返回结构体结构体在栈上构造后拷贝回调用者。 */ static CalcResult_t Calc_Div(int32_t a, int32_t b) { CalcResult_t result; /* 局部结构体变量 —— 在栈上 */ if (b 0) { /* 除零错误 */ result.value 0; result.isValid 0; result.errorCode 1; /* 除零错误码 */ } else { result.value a / b; result.isValid 1; result.errorCode 0; } return result; } /** * brief 执行一次计算通过函数指针调用具体的运算函数 * param a 操作数1 * param b 操作数2 * param operation 函数指针 —— 指向具体的运算函数 * return CalcResult_t 计算结果 * * 演示函数指针作为参数。 * 函数指针本身也是变量占用 4 字节存放在栈上。 */ static CalcResult_t Calc_Execute(int32_t a, int32_t b, CalcResult_t (*operation)(int32_t, int32_t)) { CalcResult_t res; /* 栈上局部变量 */ /* 通过函数指针调用函数 —— 函数名就是函数入口地址 */ res operation(a, b); /* 更新全局统计 */ g_CalcCount; return res; } /** * brief 计算调度函数 —— 演示 const 指针 * param a 操作数1 * param b 操作数2 * param pOp 指向操作符枚举的指针 * const CalcOp_t * —— 指针指向的内容是只读的 * 指针本身可以改变指向但不能通过指针修改指向的内容 * return CalcResult_t * * 知识点 * const CalcOp_t *p —— 指针可变指向的内容不可变常量指针 * CalcOp_t *const p —— 指针不可变指向的内容可变指针常量 * const CalcOp_t *const p —— 指针和内容都不可变 */ static CalcResult_t Calc_Run(int32_t a, int32_t b, const CalcOp_t *pOp) { CalcResult_t result; /* 通过 const 指针读取枚举值 —— 只能读不能写 */ CalcOp_t op *pOp; /* 允许读取 const 指向的内容 */ /* *pOp OP_ADD; */ /* 不允许编译错误const 指针指向的内容不可修改 */ switch (op) { case OP_ADD: /* 使用函数指针方式调用加法 */ result Calc_Execute(a, b, (CalcResult_t (*)(int32_t, int32_t))Calc_Add); break; case OP_SUB: result Calc_Execute(a, b, (CalcResult_t (*)(int32_t, int32_t))Calc_Sub); break; case OP_MUL: result Calc_Execute(a, b, (CalcResult_t (*)(int32_t, int32_t))Calc_Mul); break; case OP_DIV: /* 除法直接调用因为返回结构体与函数指针类型一致 */ result Calc_Div(a, b); g_CalcCount; break; default: result.value 0; result.isValid 0; result.errorCode 2; /* 未知操作 */ break; } return result; } /** * brief 格式化并输出计算结果 * param pResult 指向计算结果的 const 指针 * 指针本身和所指内容都是 const —— 双 const 限定 * param pA 指向操作数 A 的 const 指针 * param pB 指向操作数 B 的 const 指针 * param pSymbol 指向操作符字符串的 const 指针实际上是 char ** * * 演示const 指针的多种组合用法。 * const Result_t * const pResult * └── 第1个 const指针指向的内容不可修改 * └── 第2个 const指针变量本身不可修改必须在定义时初始化 * └── 等价于只读指针指向只读数据 */ static void Calc_PrintResult(const CalcResult_t *const pResult, const int32_t *const pA, const int32_t *const pB, const char *const *pSymbol) { /* ---- 局部缓冲区 —— 在栈上分配 ---- */ char buffer[64]; /* 64 字节栈空间 */ /* ---- 局部变量 —— 在栈上分配 ---- */ int32_t localA *pA; /* 从 const 指针读取数据 */ int32_t localB *pB; (void)localA; (void)localB; /* 清零局部缓冲区演示 memset 操作 */ memset(buffer, 0, sizeof(buffer)); /* 格式化字符串结果放入栈缓冲区 */ if (pResult-isValid) { snprintf(buffer, sizeof(buffer), [OK] %d %s %d %d (total: %lu), (int)*pA, *pSymbol, /* 解引用得到操作符字符串 */ (int)*pB, (int)pResult-value, (unsigned long)g_CalcCount); } else { snprintf(buffer, sizeof(buffer), [ERR] code%d (total: %lu), (int)pResult-errorCode, (unsigned long)g_CalcCount); } /* ---- 指针常量的演示 ---- * CalcResult_t * const pWritable —— 指针本身不能变但内容可以改 * 这里只做演示不实际使用。 */ /* ---- 数组名作为指针常量 ---- * buffer 是 char *const 类型数组名是地址常量不可修改 * 但 buffer 的内容可以通过下标修改。 */ buffer[0] buffer[0]; /* 这是合法的数组内容可以修改 */ /* 实际工程中可将 buffer 通过 UART/LCD 打印 */ /* HAL_UART_Transmit(huart1, (uint8_t *)buffer, strlen(buffer), 100); */ (void)pSymbol; } /** * brief 安全缓冲区写入 —— 演示指针偏移与数组访问 * param pBuf 目标缓冲区指针可读写 * param pSrc 源数据指针const —— 只读 * param len 要写入的长度 * param maxLen 缓冲区最大容量 * * 演示 * - 指针与 const 指针的区别 * - 指针算术运算 * - 边界检查确保栈不溢出 */ static void Calc_BufferWriteSafe(uint8_t *pBuf, const uint8_t *pSrc, uint8_t len, uint8_t maxLen) { uint8_t i; /* 局部变量 —— 栈上 */ uint8_t copyLen; /* 局部变量 —— 栈上 */ /* 防止缓冲区溢出取 len 和 maxLen 的较小值 */ copyLen (len maxLen) ? len : maxLen; for (i 0; i copyLen; i) { /* 指针偏移方式访问 —— 等价于 pBuf[i] pSrc[i] */ *(pBuf i) *(pSrc i); /* 说明 * pBuf 是 uint8_t * —— 指针可读写指向的内容可读写 * pSrc 是 const uint8_t * —— 指针可读写指向的内容只读 * (pBuf i) 是地址运算每次偏移 i 个 uint8_t 的大小1 字节 */ } } /* * 7. 主演示函数 —— 调用计算器各功能演示所有变量用法 * */ /** * brief 简单计算器演示入口 * 可在 FreeRTOS 任务中调用或在 main() 中直接调用。 * * 本函数完整演示了以下概念 * ┌──────────────────────┬─────────────────────────────────────────────┐ * │ 概念 │ 对应代码 │ * ├──────────────────────┼─────────────────────────────────────────────┤ * │ 全局变量 (.data) │ g_CalcCount, g_ConsecutiveCorrect │ * │ 全局变量 (.bss) │ g_LastResult, g_ErrorCode │ * │ const 全局 (.rodata) │ g_OperandCount, g_CalcVersion │ * │ static 全局 │ s_InitFlag(.data), s_IsInitialized(.bss) │ * │ 局部变量 (栈) │ Calc_Run 中 a,b, result │ * │ 指针 │ function pointer, data pointer │ * │ const 指针 │ const CalcOp_t *pOp │ * │ 双 const 指针 │ const Result_t *const pResult │ * │ 函数定义 (.text) │ 所有 static 函数 │ * │ 结构体返回 │ CalcResult_t Calc_Div() │ * │ 函数指针 │ Calc_Execute 的 operation 参数 │ * │ 字符串 (.rodata) │ - * / 字面量 │ * │ 数组 │ g_OpSymbols[], buffer[] │ * │ 枚举 │ CalcOp_t {OP_ADD, ...} │ * └──────────────────────┴─────────────────────────────────────────────┘ */ void Calculator_Demo(void) { /* ---- 局部变量栈上 ---- */ int32_t num1 100; int32_t num2 20; CalcResult_t finalResult; CalcOp_t operation OP_DIV; /* ---- 指针变量栈上存地址 ---- */ CalcOp_t *pOp operation; /* 普通指针指向的内容可读写 */ const CalcOp_t *pConstOp operation; /* const 指针内容只读 */ /* *pOp OP_ADD; */ /* 合法普通指针可修改所指内容 */ /* *pConstOp OP_ADD; */ /* 不合法const 指针不能修改 */ /* ---- 演示 const 指针指向 const 变量 ---- */ const uint16_t *pVersion g_CalcVersion; /* 指向 .rodata 的指针 */ /* *pVersion 0x0200; */ /* 不合法g_CalcVersion 是 const */ /* ---- 初始化 ---- */ Calculator_Init(); /* ---- 执行运算 ---- */ finalResult Calc_Run(num1, num2, pConstOp); /* ---- 更新全局 .bss 变量 ---- */ g_LastResult finalResult.value; g_ErrorCode (int8_t)finalResult.errorCode; /* ---- 输出结果 ---- */ Calc_PrintResult(finalResult, num1, num2, g_OpSymbols[operation]); /* ---- 更多运算演示 ---- */ { /* ---- 代码块内的局部变量 —— 也在栈上作用域限于本块 ---- */ int32_t a 50; int32_t b 5; CalcOp_t ops[] {OP_ADD, OP_SUB, OP_MUL, OP_DIV}; for (int i 0; i 4; i) { CalcResult_t r Calc_Run(a, b, ops[i]); Calc_PrintResult(r, a, b, g_OpSymbols[i]); } } /* ---- 演示缓冲区安全操作 ---- */ { uint8_t src[8] {1, 2, 3, 4, 5, 6, 7, 8}; /* 栈数组 */ uint8_t dst[8]; /* 栈数组初始未定 */ Calc_BufferWriteSafe(dst, src, 8, sizeof(dst)); } /* ---- 使用全局 const.rodata 段---- */ if (g_CalcCount 0) { uint16_t ver g_CalcVersion; /* 从 .rodataFlash读取 */ uint8_t cnt g_OperandCount; /* 从 .rodataFlash读取 */ (void)ver; (void)cnt; } /* 抑制未使用变量警告 */ (void)pOp; (void)pConstOp; (void)pVersion; } /* * 总结各变量在 MCU 内存中的存放位置 * * ┌─────────────────────┬───────────┬───────────────────────────────────┐ * │ 变量声明 │ 段 │ 物理位置 │ * ├─────────────────────┼───────────┼───────────────────────────────────┤ * │ int32_t g_CalcCount 0; │ .data │ RAM (0x20000000 区域) │ * │ static uint8_t s_InitFlag1; │ .data │ RAM │ * │ const uint8_t g_OperandCount │ .rodata │ Flash (0x08000000 区域) │ * │ static const char *op[] │ .rodata │ Flash (字符串也在 Flash) │ * │ int32_t g_LastResult; │ .bss │ RAM (启动时清零) │ * │ static uint8_t s_IsInit; │ .bss │ RAM │ * │ 函数体 (Calc_Add, ...) │ .text │ Flash │ * │ 局部变量 (函数内的 a, b) │ Stack │ RAM (运行时分配返回后释放)│ * │ 函数形参 │ Stack │ RAM (通过入栈传递) │ * │ 函数返回地址 │ Stack │ RAM (BL 指令自动压栈) │ * │ malloc 分配的内存 │ Heap │ RAM (需实现 _sbrk) │ * └─────────────────────┴───────────┴───────────────────────────────────┘ * * .map 文件查看方法 * 编译后打开 Debug/*.map 文件搜索相应符号即可看到其具体地址。 * * 示例 .map 内容片段 * .text 0x08000100 0x4XXX .../calculator_demo.o * .rodata 0x08002XXX 0x00XX .../calculator_demo.o * .data 0x20000000 0x0010 .../calculator_demo.o * .bss 0x20000010 0x0010 .../calculator_demo.o * g_CalcCount 0x20000000 Data 4 calculator_demo.o (.data) * g_CalcVersion 0x08002XXX Data 2 calculator_demo.o (.rodata) * */