📘 C 语言编程规范与底层原理笔记
1. 编程规范概览 (省流版) ⚡
- 命名规范:使用下划线命名法,宏定义全大写,预编译命令
#else,#endif行末加注释说明条件。 - 前缀规范:全局变量加
g_前缀,全局指针加p_前缀。 - 类型定义:结构体/联合体/枚举等复杂数据类型,除非不公开,统一用
typedef定义名称,并加上_t后缀。 - 缩进限制:缩进最多接受 6 层,超过 6 层必须重写;预编译如果嵌套命令不缩进。
- 嵌入式限制:嵌入式程序中不准使用递归。
- 返回值:对于有返回值的函数,必须返回一个默认值,不能什么都不返回。
2. 命名禁忌与规则 🚫
一. 命名禁忌
- 单字母:
int a,b,c;(但循环变量可以使用i,j,k,前提是不要定义成全局变量)。 - 重复无意义:
int aa, bb, cc, dd;。 - 拼音:
char[30] mingZi, sb, nb;(对于一些公认的拼音可以使用)。 - 不规范缩写:如
int cont_freq。
二. 命名规则
- 风格:统一使用下划线命名法,对于第三方代码保留其命名风格,不要修改。
- 统一性:编写模块代码时使用统一前缀,如操作 NAND Flash 的代码加
nand_前缀。 - 作用域关联:变量命名长度与作用域有关,作用域越大命名越长,以避免冲突。
- 长度限制:尽量不要超过 6 个单词。
- 避免编号:如
number1,number2,应用数组代替;除非逻辑上必须使用编号(如USART1,USART2)。
3. 代码格式与风格指南 📏
一. 命名语义
- 函数:动词 + 名词,准确对应功能或行为。
- 变量:形容词 + 名词。
- 句柄:加
_handle后缀,名称与类型功能匹配即可。
二. 文件规范
- 引用:只引用需要的头文件,不要包含一堆;
#include对大小写敏感。 - 目录结构:头文件放
Inc目录,源文件放Src目录。 - 后缀:C++ 头文件用
.hpp,源文件用.cpp。 - 路径:子目录包含头文件时使用相对路径,不要在 IncludePath 中添加。
- 宏保护:头文件开头末尾必须加
#ifndef ... #endif。
三. 空格与符号
- 二元/三元操作符:前后加空格 (
= + - < > * / % ^ <= >= == != ? :)。 - 一元操作符:后不加空格 (
& * + - ~ !),但声明指针时*靠近变量一侧且左边有空格。 - 自增减:前后都不加空格 (
num++,--num)。 - 逗号分号:只在后面加空格。
- 注释:块注释
/* xxx */内容前后各一空格;行注释// xxx双斜线后一空格,且与行对齐。
四. 代码布局
- 花括号:左花括号不换行,右花括号单独一行(else 的右花括号不换行),减少阅读负担。
- 空行:文件头注释后、函数定义前加空行;注释函数/结构体/枚举时不加空行。
- 单行限制:一行只做一件事;一行最多 80 字符。
- 语句块:
if, for, while, do必须加花括号,即使只有一条语句。 - 清理:及时清理死代码。
4. 函数与变量最佳实践 🛠️
一. 函数
- 复杂度:尽量不超过 10 个局部变量。
- 参数:最多 6 个。
- 返回值:必须
return默认值。 - 无参函数:使用
void,如int main(void)。 - 功能单一:不要让函数或文件成为“大杂烩”。
二. 变量
- 定义:哪里用哪里声明,使用前必须定义。
- 初始化:确定初值写在一行;不确定初值变量给 0/1,指针给 NULL,结构体给
{0}。 - Static 使用:如果全局变量仅本文件使用,加
static避免外部访问。
三. 复杂数据类型与杂项
- 结构体/联合体:公开的使用
typedef定义并带_t后缀。 - 枚举:成员全大写。
- 内存:申请必须释放。
- 嵌套限制:指针嵌套最多 3 层,数组最多 3 维。
- 参数传递:结构体/联合体尽量传指针。
- 浮点数:不要做相等/不等判断(精度问题)。
- 编码:统一使用 UTF-8+。
5. C 语言编译原理 🏗️
一. 编译流程
- C 语言到机器码:高级语言 -> 汇编语言 -> 二进制文件(机器语言)。
- 后缀变化:
.c->.s->.o->.exe。 - 详细步骤:
- 预处理:处理头文件包含、宏定义、条件编译。生成代码(编译单元),注释被删除。
- 编译:将编译单元转换为目标架构的汇编代码。
- 汇编:将汇编代码生成机器指令(目标文件 .o),此时文件不可执行。
- 链接:符号解析、地址分配、重定位。组合目标文件生成可执行文件。
二. 文件区别
- .obj 文件:编译后的中间文件,含相对地址和未解决符号。
- .exe 文件:链接后的可执行文件,含绝对地址。
三. 编译器分类
- 基于源语言(C/C++/Java 等)。
- 基于目标语言(机器码/中间语言)。
- 跨编译器 (Cross-compiler):在一个平台运行,生成另一个平台的代码。
四. 嵌入式编译器 vs PC 编译器
| 特性 | 嵌入式编译器 | PC 编译器 |
|---|---|---|
| 目标平台 | 特定硬件 (ARM, STM32),需交叉编译 | 通用架构 (x86),本地编译 |
| 优化方向 | 代码体积、功耗、实时性优先 | 执行速度优先 |
| 开发环境 | 含硬件调试 (JTAG),需手动配置硬件细节 | 侧重软件调试,依赖系统 API |
| 流程复杂度 | 需配置链接脚本,处理中断向量表 | 自动处理链接和内存 |
导出到 Google 表格
6. 位运算与 Static 关键字 🔢
一. 位运算
- 操作:置零用
&,置 1 用|。 - 表示:
bit[y:x]表示从 x 位到 y 位。 - 优化:对 2 的倍数的取余、乘除运算,编译器会优化为位运算。
二. Static 关键字详解
| 修饰对象 | 作用域 | 生命周期 | 说明 |
|---|---|---|---|
| 全局变量 | 仅当前文件可见 | 程序运行期间 | 隐藏变量,避免命名冲突 |
| 局部变量 | 仅函数内部 | 程序运行期间 | 仅初始化一次,函数结束后值保留 |
| 类成员变量 | 属于类 | 程序运行期间 | 所有对象共享,不占对象空间 |
| 类成员函数 | 属于类 | - | 不依赖对象,无 this 指针 |
导出到 Google 表格
7. 整数与数据类型 💾
一. 整数
- 类型:
uint8占用 8 个二进制位;stdint.h根据平台确定范围;inttypes.h处理类型输出。 - 隐式转换优先级:
char, short-->int-->unsigned-->long-->double<--float。 - 比较陷阱:有符号数与无符号数比较时,有符号数会被隐式转换为无符号数(例如 -1 变成巨大的正数)。
二. 枚举与结构体
- 枚举:用于状态/错误处理,默认从 0 递增。
- 结构体初始化:可使用
.成员名方式赋值。 - 结构体传参:作为参数会发生压栈复制,建议传指针以节省时间和空间。
三. 结构体内存对齐
- 规则:
- 第一个成员偏移为 0。
- 成员偏移地址必须是对齐数的整数倍。
- 结构体总大小必须是最大对齐数的整数倍。
- 对齐数:
min(成员大小, 默认对齐数)。 - 工具:
offsetof获取偏移,#pragma pack(n)或__attribute__设置对齐。
四. 位域与联合体
- 位域:
类型 成员 : 位数;不能用浮点和指针。 - 联合体 (Union):
- 通信应用:传输多字节数据时,发送拆分为字节,接收组合为数据。
- 特性:修改一个成员会影响其他成员。
8. 数组与指针 📌
- 零长数组:常用于变长结构体末尾,减少空间占用。
- 变长数组:全局数组不可用变量定义长度。
- 返回局部数组:不允许,编译器会报错或返回空指针;加
static可解决。 - 数组退化:
int []隐式转换为int *是允许的,反之不行。 - 指针定义:
int *ptr1, ptr2;中ptr2是整型,正确写法是int *ptr1, *ptr2;。
9. 内存管理:栈与堆 🧠
一. C 语言中的内存区域
| 区域 | 管理方式 | 用途 |
|---|---|---|
| 栈 (Stack) | 操作系统自动管理 | 局部变量、函数调用信息 |
| 堆 (Heap) | 程序员手动管理 | 动态分配 (malloc, free) |
导出到 Google 表格
二. 数据结构中的抽象
- 栈:LIFO(后进先出),仅允许栈顶操作。
- 堆:完全二叉树,满足堆属性(最大堆/最小堆)。
三. 内存段划分
程序内存分为 5 部分:程序段、初始化数据段、未初始化数据段、栈、堆。
四. 动态分配细节
- malloc vs calloc:
malloc分配后数据随机;calloc会将区域清零。 - calloc 参数:
成员个数 * 单个成员大小。 - 指针类型:指向零地址叫空指针,指向不存在地址叫野指针。
10. 预处理与 GNU 扩展 ⚙️
一. 预处理
- 指令:以
#开头,不以分号结尾,\为续行符。 - 头文件保护:使用
#ifndef ... #define ... #endif防止重复包含。 - 路径查找:双引号优先查源文件目录,尖括号查编译器路径和环境变量。
- 检查:
__has_include判断头文件是否存在。
二. 函数调用约定
- 规定参数压栈顺序、谁清理栈、返回值如何返回(如
__stdcall,__cdecl)。
三. GNU 扩展 (__attribute__)
- 变量属性:
packed:取消对齐。align(n):设置对齐。- 函数属性:
weak:弱定义,可被重定义。nonnull:限制传入空指针。noreturn:函数不返回 (如死循环错误处理)。- 头文件包装:
#include_next "filename"用于引用同名旧头文件。 - 数组初始化:
[0 ... 2] = 1(范围初始化)。