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 free
3.堆栈等内存分配,全局变量、指针等用法、例子
这是一个完整的 STM32F103 计算器演示程序(约 350 行),涵盖了你要求的全部内存段和变量类型:
各内存段对应代码
| 内存段 | 物理位置 | 代码示例 |
|---|---|---|
.text | Flash | 所有函数体,如Calc_Add()、Calc_Div()、Calc_Run()等 |
.rodata | Flash | const uint8_t g_OperandCount = 2、const uint16_t g_CalcVersion = 0x0100、g_OpSymbols[]字符串表 |
.data | RAM | uint32_t g_CalcCount = 0、static uint8_t s_InitFlag = 1(有初始值的全局变量) |
.bss | RAM | int32_t g_LastResult、int8_t g_ErrorCode、static uint8_t s_IsInitialized(无初始值的全局变量) |
Stack | RAM | 所有函数内的局部变量,如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 = &operation |
const T*指针(指向的内容只读) | const CalcOp_t *pConstOp |
T *const指针常量(指针本身不可变) | 注释中演示,buffer数组名本身就是 |
const T *const双 const | const CalcResult_t *const pResult |
const变量 | 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; /* 从 .rodata(Flash)读取 */ uint8_t cnt = g_OperandCount; /* 从 .rodata(Flash)读取 */ (void)ver; (void)cnt; } /* 抑制未使用变量警告 */ (void)pOp; (void)pConstOp; (void)pVersion; } /* ============================================================================ * 总结:各变量在 MCU 内存中的存放位置 * * ┌─────────────────────┬───────────┬───────────────────────────────────┐ * │ 变量声明 │ 段 │ 物理位置 │ * ├─────────────────────┼───────────┼───────────────────────────────────┤ * │ int32_t g_CalcCount = 0; │ .data │ RAM (0x20000000 区域) │ * │ static uint8_t s_InitFlag=1; │ .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) * ============================================================================ */