p.s ,日进步积,本文系小一年时间的不断积累迭代而成,所以是谓集大成者,仍将长期更新。Github仓库会不断更新,而CSDN文章这里不会跟进!
本文系广泛撷取、借鉴和整理,侵删。本文适合刚入门的人阅读和遵守,也适合已经有较多编程经验的人参看。如有错误恭谢指出!
按 1:引用观视频工作室视频【大师计划·林宝军03】北斗三号总师:我来跟你说说,...里面总师受访时所说的话:
5:48:“...所以我经常讲,要把一个产品做好,其实有三方面,一个是技术,一个是质量,一个是管理,这三方面;技术水平,质量保障能力和管理能力,三条腿哪个都少不了,少一个,这个东西(航天)也做不上去,其实技术只是一方面,...”。
6:44:“...光是技术上去了,不见得能做出一个系统。对我们工程来讲,就是(需要)规范的文化,什么意思呢,比如我们几十年的航天经验,我把这个经验总结成文字,总结成规范,不管是谁做,只要有一定的经验,按这个规范做出来,做得卫星出来,打到天上去就能好用,这就是规范文化。包括匠人文化和规范文化,最后都是按规矩去做,它强调的是解决了怎么做的问题,但它有一个缺点,没强调为什么,其实我认为在做的过程中,加个为什么可能更好。”
按 2:引用 雷军写代码水平如何? - 知乎 (zhihu.com)。
雷总也在给后辈的寄语中不断强调代码要整洁,逻辑要无懈可击,自己写的代码要达到例程(示范程序)的程度。这一点和《代码整洁之道》的作者Bob大叔英雄所见略同了。
Bob大叔就在《代码整洁之道》提出一种观点:代码质量与其整洁度成正比。
优秀的系统往往有优秀的结构设计,层次清晰,职责单一,模块化,方便拓展和复用。功能的添加往往只是在现有的框架中添加一个个模块和少量代码。
C & MCU编写规范和其他(coding style and more)
O 目录
1 日常素养
2 程序框架要点
首要地重中之重
一些方面的提醒
3 代码格式化工具列举
IT 学习路线
相关坚韧大厚书
相关有趣/耐看书或视频
4 普适规则(General rules)
5 具体各部分的规范形式
关于函数定义形式(Functions)
关于变量定义形式(Variables)
关于结构体、枚举和类型定义形式(Structures, enumerations, typedefs)
关于联合和其类型的定义形式(union)
关于宏定义和预编译指令定义形式(Macros and preprocessor directives)
关于注释的形式(Comments)
其他常用写法汇集
6 常用宏定义
7 C 标准库的使用
8 ST HAL 库的编写形式
9 本文参考源
10 大厂规范和名设计模式
大厂规范
设计定律、原则和模式
提高代码运行效率
11 尾记
开光保护
署名
-
维护干净整洁的编程环境(保持愉悦的心情,干净整洁的桌面,友好和蔼的同事等等)。
-
身体坐直,按时走走,保证睡眠,计划运动。
-
要学习或使用新东西,全网搜集到好的手册和资料,就已经成功了一半(所谓好的资料不一定是官方教程或手册,可以是别人总结的入门系列文章或视频,适合自己快速摄入的才是好的)。
-
循序渐进,由浅入深。对什么也不感兴趣、不深入是浮躁,而且不经过深入全面的了解就硬扛大型项目也是一种浮躁。
-
习惯去看源代码(熏陶优秀代码风格,有时还能发现新东西)。
-
先搞清楚需求,再构思,再开发,顺序不能错。
-
不要重复造轮子,时常逛开源网站,有新想法可以先去找轮子,也许比自己写的更好,相信前人的智慧和同行们雪亮的眼光。
-
关于调试:
-
编译错误,如果不明白哪错了,直接无脑复制编译器的错误信息扔到搜索引擎框,然后点“搜索”按钮(大部分报错都是语法错误)。
-
嵌入式十年调Bug经验总结 - 知乎 (zhihu.com)。
-
-
关于提问,是一门艺术:
-
谈谈提问的艺术 | How To Ask Questions The Smart Way
-
《提问的艺术:如何快速获得答案》(精读版)
-
如何问出一个好问题?| 提问的艺术
-
-
有结对编程,协作开发的能力。
-
有写文档的习惯(对项目写文档,或者日常写博客,或者没事就画一画流程图梳理想法)(不要在技术文章里写“日记”、写“小说”。),条理清晰,简化描述(Keep it stupid simple),并且一定要写上用例,写上例子,写上实例(重要的事情讲三遍)!
-
做好版本管理,有备份的意识(打压缩包写上时间也好,使用git工具也好,放到U盘里也好,传到私人网盘也好)。
-
时常看书,时常看看同行的文章,常读常新。
-
不止技术,不想当将军的士兵不是好士兵(有的人领导能力强,有的人能开发有竞争力的产品,有的人能把知识讲地透彻,有的人理论功底强),时常把视角拉远看一看。
首要地重中之重
-
软件工程结构的分层思想永不灭。
-
多任务、复杂流程的整机功能要使用状态机方法来表达、建模和实现。详细内容见 “状态机与层次化状态机” 一节。
-
设计高效、方便的数据存储的数据结构,设计高效、方便的算法来操作这些数据。
-
标准化、通用化和可靠性设计高于功能设计。
在方案设计完之后准备开始实施/实现,不论软硬件,先全网找优秀的实例、原理图、代码、项目、库来参考实现,不要一上来就自己吭哧做。
-
EmbedSummary: 嵌入式大杂烩资源汇总 (gitee.com)。
-
programthink/opensource: 【编程随想】收藏的开源项目清单 (github.com)。
-
github 上面的 各种 Awesome 系列 汇总仓库。
一些方面的提醒
时间、空间复杂度
时间复杂度表示一个算法内执行语句的数量在最坏的情况下随着循环次数 n 的增加而增长的数量级。一个算法内语句的使用次数(频度)表示为 f(n),n 为算法内循环语句的循环数,n 的变化直接改变 整个算法的语句使用次数;时间复杂度 O(g(n)) 的定义为,对于一个算法,当且仅当存在正整数 c 和 n0,使得 f(n) ≤ cg(n) 对于所有 n ≥ n0 成立,则该算法的渐进时间复杂度为 f(n) = O(g(n)),g(n) 为 n 的函数。
各个时间复杂度的语句频度的增长速度比较:O(log2n) < O(n) < O(nlog2n) < O(n^2) < O(n^3) < O(2^n) < O(n!),前三个很好,最后两个不可接受,剩余的强差人意。
程序的执行时间不仅依赖于问题规模,还可能随着数据的状态不同而变化,即其时间复杂度会变化,一般评价算法时候取最坏的情况的时间复杂度。
空间复杂度大同小异。
状态机与分级/并发状态机
-
多任务、复杂流程的整机功能要使用状态机方法来表达、建模和实现;益于:思路清楚、维护方便、扩展性好;不用状态机编程比较难得到这几个优点。
-
设计状态机要根据需求画状态图,再着手实现。要点:关键是画好状态图、不能进入死循环、不能进入非预知状态、穷举所有可能的分支。
-
在系统的状态划分时,如果细分出的状态特别多,那么要考虑分级状态机或并发状态机(参考书 《基于状态机的嵌入式系统开发》,该书比较老了,思想进行提取)。两种状态机以下分别说。
-
分级状态机属于上下级划分,而并发状态机属于多个状态机没有从属关系而一同运行(并发不等同于并行)。
-
这里介绍两种状态机构建模板/方法,一种是 “一个状态对应一个函数” 的模板,另一种是 “switch-case” 方式。前者为作者自实现的,比较易懂好用,其核心代码开源在 "stm32_framework" 仓库中 F4 里的 路径内;后者的例子见 。
分级状态机
-
系统的顶层状态机一般设计有包括初始化(init)、运行(run)、待机/空闲(standby)和停机(halt)状态等这几个最基本的状态,在每个基本状态下又可细分多个状态组成子层状态机。
-
在这里,顶层状态机使用 “一个状态对应一个函数” 的模板来实现,子层状态机使用 “switch-case” 来实现。
并发状态机
-
一个系统物理上可以分为若干个同级别的大块,而每一个大块在信号连接上与其他大块之间有少量联系。如果若干是指 10,此时就可以设计 10 个状态机,每个状态机里有若干状态,而某个状态机里的某个状态的转移条件有其他状态机里的事件发生。
-
意义,比如系统可以划分为 10 个状态机,每个状态机有 3 个状态,如若合成一个状态机,那么将会有 3^10 那么多个系统状态。
-
多个平级的同时运行的状态机(并发状态机)可以任选用上述两种模板/方法构建。
"低耦合,可重用,参数化,注释全"
-
划分好文件、功能函数和所需变量。函数 "低耦合,可重用,参数化,注释全",变量尽量用结构体打包;可重用意味着直接复制代码或者直接复制文件到另一个项目上直接就用。
-
功能增加裁剪的灵活性:做好预编译设置。方便于切换调试版本和执行版本,方便于切换行为模式,方便于剪裁功能块;功能剪裁用一个名字带"_config"的文件集中管理,供用户修改各种剪裁用的宏定义,就像总控台。
-
参数化设计的适应性:对比如协议解析、模块/功能数量增减、数据范围的变化适应等地方尽量写的通用,通过参数设定来改变运行时/编译时的功能灵活变化。
-
关于 MCU 的编写框架,我目前大抵就认我自己的开源项目 "stm32_framework" 的吧,规范都对齐这个项目。
嵌入式 C 的一些规范
p.s 以下为项目 "stm32_framework" 编写时形成的一些经验和规范,更多具体的还以此项目的源码和架构为准!
-
本文章 "C 编写规范" 的全部规则都适用。
-
尽量利用硬件资源和外设资源,减少 cpu 负担。
-
系统外设功能的启用与否均用宏定义 SYSTEM_SUPPORT_XX 来管理剪裁。
-
RTOS任务函数均使用 os_task_xx_xx() 命名,属于"os_task"。
-
中断优先级分组选用分组4,即16级抢占优先级,不用0级响应优先级。
-
IO的低电平为有效电平,高电平截止或者无效;按键IO尽量都使用外部中断。
-
至少用一个定时器提供1ms或者10ms的时基,再用软件分频为 50ms/100ms/300ms/1s 等。
-
外设(Periph)和设备(Devices)分别初始化,外设的启停成对编写,命名统一。
-
通讯外设的发送和接收都使用中断,并尽量使用上DMA,以串口为例如下:
接收:(依时间间隔区分帧为例)
接收中断->打接收标志位,记当前时间(定时器计数器),比较上次接收的时间,启动DMA->接收缓冲区(足够大,大于2帧)->送解析函数。
发送:(类 lwip 的发送逻辑,事件驱动)
要发送的串放入 发送缓冲区,检查发送启动标志位是否就绪,若就绪就打 发送启动标志位->发送中断中,检测发送启动标志位,判断是否发送 发送缓冲区 的串,发完清此标志位,即设为就绪态。
-
等等等等。
嵌入式通讯设计要点
-
通讯尽量使用成熟的协议来封装数据。
以打包、解包形式进行通讯。鉴于见识有限,协议选择还需要广泛调研和商榷,以下仅为举例
-
串口通讯协议可以上 Modbus。
-
CAN 通讯协议可以上 CANOpen。
-
TCP 通讯协议还有待确定,需要选择一个支持大带宽的。 可以用 json 格式封装数据。
如果要自定串口等接口的通信协议,要考虑的点:
-
数据帧格式规定:
-
自定义数据帧(数据打包)的一般格式为:帧头 — 命令/数据类型枚举 — 数据区长度 — 数据区 — 校验区。
这是一般做法,可以有效避免数据区出现帧头打断接收或解析等等问题;帧头可占一个字节,类型枚举可占两个字节,数据区长度可占四个字节,校验区可占两个字节(可选算法:0xFFFF - 该帧前面的所有字节加和)。
-
如果数据区内有多个数据块,每一块可以按照此一般格式合成:该数据块类型枚举(1个字节) — 该数据块数据长度(1或2个字节) — 该数据块的数据。
-
一帧有确定的长度;如果按照上面的帧格式,可以没有帧尾,也可以加上;帧头和帧尾必须是确定的,帧头和帧尾的选取尽量避免与数据区重合。
-
-
保证一帧尽量连续传输,没有中断;
-
在有限确定的时间内完成发送;
-
数据的发送接收相关的函数与数据的打包和解析的相关函数,编写规范上应相互解耦,可重用;
-
接收完成标志位的置位大抵有两种方式:一个是判断有帧头和等待帧尾来判断为一帧,一个是从接收字节开始计时并在一定时间间隔没有接收数据后判断为一帧。
-
p.s 针对较乱的"祖传代码"做初步治疗使用。代码格式化工具还可以把代码中的 tab 符变成四个空格,这样,当代码在不同的编辑器中打开时不会产生格式错误,所以可以用 AStyle 自己定制一份配置文件,在每次码完代码之后顺手运行一下 AStyle 即可。
(TODO)下面部分条目尚未补全。
(TODO)查一查astyle配置文件的用法,按照自己的规范形式,写一个配置文件
-
通用工具 AStyle:
配置文件: c-code-style仓库中的 astyle-code-format.cfg 文件 AStyle官网:AStyle官网 AStyle is a great piece of software that can help with formatting the code based on input configuration. This repository contains file which can be used with software as command line below.
astyle --options="astyle-code-format.cfg" "input_path/*.c,*.h" "input_path2/*.c,*.h"
-
VS Code:在 VS Code 中搜索 AStyle 插件 即可。或者 Beautify 插件,代码格式化,都还没试过。
-
MDK:关于“把代码中的 tab 符变成四个空格”,在 MDK 的 Edit 的 Configuration 中,把 “Insert spaces for tab” 都勾上即可。
-
IAR:
-
Eclipse:
配置文件: c-code-style仓库中的 eclipse-ext-kr-format.xml Repository contains file that can be used with eclipse-based toolchains to set formatter options. It is based on K&R formatter with modifications to respect above rules. You can import it within eclipse settings, tab.
-
Source Insight:
-
Notepad:关于“把代码中的 tab 符变成四个空格”,在 Notepad 的 设置 的 语言 中,把制表符框的 “替换为空格” 勾上即可。
一些网友开源项目:
-
mysterywolf/formatting: 源码格式自动化调整工具 (github.com)。
-
...
-
C语言基础 →
-
C语言三剑客:《C和指针》、《C陷阱与缺陷》和《C专家编程》,经典永流传 →
-
数据结构与算法(线性表/树/图/哈希 + 排序/搜索/规划等等等 按需学) →
-
计算机专业科学看的《计算机组成原理》/《计算机体系结构》,《计算机操作系统》/《现代操作系统》/《深入理解计算机系统》,《编译原理》,《深入分析GCC》,网络协议如《计算机网络》、《TCP-IP详解卷一/卷二/卷三》等 →
-
可选 《CPU自制入门》 →
-
走向:嵌入式 Linux 方向、FPGA / 芯片设计方向、具体某算法方向等等。
更多可详细参考 rd2coding/Road2Coding: 编程之路 (github.com) 的总结,比较全面了。
没给出链接的 网搜名字即可。
-
哪本《数据结构与算法》最好? - 知乎 (zhihu.com) 该回答列举了一些不错的数据结构与算法方面的书籍。
-
《算法导论》(经典)是计算机学科的算法入门书。
-
《计算机体系结构》(经典),《计算机操作系统》/《现代操作系统》/《深入理解计算机系统》。
-
《编码的奥秘》,相关介绍/推荐 想练习《编码的奥秘》里面的知识,有什么软件有帮助? - 知乎 (zhihu.com)。《编译原理》(经典),《深入分析GCC》。
-
网络协议如《计算机网络》、《TCP-IP详解卷一/卷二/卷三》, 想深入了解 HTTP 协议,有哪些值得推荐的书籍? - 知乎 (zhihu.com)。
-
嵌入式应用相关:《GNU Make》,《Debugging with GDB》,《Linux 高级程序开发》,《POSIX 多线程程序设计》,《嵌入式Linux基础教程》,《嵌入式Linxu应用开发完全手册》,《嵌入式Linxu应用程序开发详解》。
-
嵌入式底层相关:内核相关:《深入理解Linux内核》,《Linux内核源代码情景分析》,《Linux内核设计与实现》;驱动相关:《Linux设备驱动程序》,《Linux设备驱动开发详解》,《Linux驱动开发入门与实践》。
-
《算法新解》开源书。《啊哈!算法》。 -
图解系统 小林。图解网络 小林。
-
趣谈网络协议。
-
手绘图解HTTP。30张图解HTTP常见面试题。
-
TCP/IP 教程 | 菜鸟教程 (runoob.com)。HTTP 教程 | 菜鸟教程 (runoob.com)。
-
《嵌入式C语言的自我修养》 从沙子讲到CPU,从编辑器讲到编译器,从高阶C语言讲到内存管理,从GNU讲到多任务编程。
-
(完结)(小甲鱼)数据结构和算法_ 哔哩哔哩 _bilibili。
-
国嵌唐老师主讲【数据结构与算法C语言】(非常犀利)_ 哔哩哔哩 _bilibili 讲的慢。
p.s 以下所有章节中示例代码均以 32/64 位机为运行环境,即 int / long int 占 4 字节。
-
第一条,请您重视编写规范!可以有代码洁癖。
-
使用 C99 标准。
-
一个 tab 四个空格。
-
运算符前后空一格,给函数传递的多个变量之间在逗号后空一格,一元操作符后不要加空格,例子如下。
-
注释里,字母和数字的两边空一格,尽量用注释,而非,例子如下。
-
关于命名。
-
变量和函数的命名都只用小写(尽量),宏定义使用全大写(尽量),并遵循 "属什么 _ 是什么 _ 做什么" 的命名形式,如:sys_irq_disable(),属于 sys 级别函数,是 irq 管理,做 dsiable 的功能。不要用晦涩的英文缩写甚至拼音就不用讲了吧。
-
具有互斥意义的变量或者动作相反的函数应该是用互斥词组命名,例子如下。
add/remove begin/end create/destroy insert/delete first/last get/release increment/decrement put/get add/delete lock/unlock open/close min/max old/new start/stop next/previous source/target show/hide send/receive source/destination copy/paste up/down
-
不要使用单字节命名变量,但是允许使用 i, j, k 这样的作为局部循环变量。
-
文件统一采用小写命名。
-
关于函数、变量、宏定义等的命名看 章节。
-
-
控制语句总加括号(即使分支执行语句只有一句),括号在竖方向对齐,用 tab 把层次分地清清楚楚,例子如下。(为了节省空间,下面示例用横向写~)
-
层次分明,多用 tab 划分层次关系(预编译代码也不例外),例子如下。
-
用 代替 ,判断是否为 '0' 可以用后者的写法(或者用 ),判断 '1' 用前者写法。
-
判断指针是否为空只用 "NULL",即 。
-
不用变长数组,用内存分配释放函数 和 。
-
循环尽量用 替代 等,无论有限次循环还是无限次循环,条件循环语句用后者。
-
长运算语句尽量多的用括号(每一步运算都用括号括起来),并做好空格增加可读性,例子如下。
-
尽量减少数据传输过程中的拷贝,对于全局变量的字符串、数组和结构体等,采用传递指针的方式。
-
大块内存使用请用内存管理。
-
文件操作中 open 和 close 成对使用,内存管理 malloc 和 free 成对使用。
-
每一个文件在最后留有至少一个空行。
-
对于 c 语言的文件,其 .h 文件的主体文件包含在下面的括号之内(标有 "..." 的位置)。
-
定义时带有 static 修饰符的变量(无论是声明在在某个函数里还是函数外)是 只在该文件具有作用域的,其他文件不能够访问到。
-
一个文件的变量声明都放在 .h 里面,公有变量声明时 加 extern 修饰符 以供其他文件调用,私有变量声明时不加 extern 修饰符。
所以一个 .h 内要对外开放的变量完整声明形式为 。
-
在 .c 文件中 include 自己对应的 .h 文件和需要用到的 .h 文件,不要引用多余的 .h 文件;.h 文件中同样只引用用到的头文件,但是头文件尽量写成无依赖的,这就考验整个系统的规划和设计。
-
如果一个模块包含了多个 .c 源文件,那么将它们放入同一个文件夹并用模块名命名,然后只用一个 .h 头文件声明接口。
-
关于开源协议,Every file (header or source) must include license (opening comment includes single asterisk as this must be ignored by doxygen). Use the same license as already used by project/library.
-
-
理论上讲,任何递归算法都可以通过循环等方法实现,尽量不用递归,不好查阅和容易栈溢出。
更多网友总结的杂类细节规范、规则:
-
学C/C++语言,32个必备修养! (qq.com)。
-
etc...
-
关于函数定义形式(Functions)
-
小写;星号 * 靠近类型名一端;用" _ "分割语义;对齐以保持良好阅读性。
-
命名遵循 的形式,例子如下。
-
函数的局部变量数量最好不超过 5 - 10 个,即不要占用太多的内存/栈资源。
-
一个函数尽量只做一件事,否则划分为多个更小的函数;不要重复,保持各个代码块的独特性。
-
低耦合,可重用,参数化,注释全!
-
对函数的错误返回要做全面的处理;一般 返回 0 表示正确,返回其他表示错误,具体的值表示错误代号,可用定义了所有错误类型的枚举变量作为函数返回值类型,或者返回值 0 表示成功,正数表示失败,此正数可以表示错误代码;并设计专门的机制对错误标识做处理。
-
对函数的参数做合法性检查;检查指针;检查变量范围,变量有大小限制的,在注释里写明;在其他地方调用此变量的时候要进行检查或限幅,例子如下。
-
如果函数传入参数(形参)的数量过多(超过 5 个),那么要考虑精简或者用其他办法,即可以将参数打包为全局的 数组 或 结构体 等然后传递其指针;对于返回多个值同理;字符串指针 和 结构体指针 等在定义时若未初始化,则使用前要用 malloc() 为其申请空间。
-
对于函数可能传入的参数是不定的任意类型,定义形参用 修饰。
-
函数的嵌套不要过多,一般控制在最多 4 层。不要用递归这种反阅读便利的写法(并且控制不好易栈溢出),用循环语句实现。
-
关于 指针函数 和 函数指针。
指针函数即指 返回值带指针变量的函数,使用情景上面已说。
函数指针即指 函数类型的指针,定义的形式和使用情景如下,函数指针名加后缀 "_ fn",函数指针类型定义名再追加后缀 "_ t"。
关于变量定义形式(Variables)
-
同类型的变量声明放在一行,变量定义时避免用函数返回值。
-
小写,星号 * 靠近类型名一端(除了一行多变量定义的情况),对齐以保持良好阅读性,例子如下。
-
用" _ "分割语义,命名遵循 "属什么 _ 是什么 _ 做什么" 的形式,意义明确,也不容易重名。
-
避免使用 stdbool.h 里的 "true" 或 "false",用 "1" 或 "0" 代替。
-
变量类型,除了char* 、float 和 double,都使用 stdint.h 库里面的,统一起来。
-
合理的常用 const 修饰符,防止变量或指针在层层传递过程中被篡改,或者在定义的时候永远加上 const 修饰符,例子如下。
-
为防止编译器优化程序中一些关键/重要的变量的给值顺序等,可在变量定义时加 volatile 声明。
-
对于函数内的局部变量,不希望在函数跳出后局部变量数据丢失那么加上 static 修饰符(指示该变量具有所在文件作用域),static 修饰符的变量若定义在一个文件内当作 “全局变量”,其是 只在该文件具有作用域的,其他文件不能够访问到。
-
玩一下,比较极端的情况,一个完整的变量声明形式:。
-
二阶指针的理解用 二维数组 或者 字符串数组 比较直观。对于 长度不一样的 多个 一维数组 常用 指针数组 定义,如 定义缺省值个不等长的字符串,定义 6 个不等长的整数数组,要么在定义时初始化其值,要么定义时不初始化然后在用的时候使用 malloc() 为其申请空间再幅值。指针传递 或者。
-
变量如果是低有效,变量名加尾缀"_n",比如使能 en 是低有效(en 上面有一横),则命名为"en_n"。
-
明确全局变量的初始化顺序,系统启动阶段,使用全局变量前,要考虑到全局变量该在什么地方初始化,使用全局变量和初始化全局变量之间的时序关系一定要分析清楚。
-
明确变量的作用域,防止在预想的作用域外能够调用到具体的某个变量,降低模块间耦合度。
-
尽量减少不必要的数据类型转换。
关于结构体、枚举和类型定义形式(Structures, enumerations, typedefs)
-
适用 "关于变量定义形式(Variables)"里面的所有内容。
-
枚举定义形式有直接定义、类型定义、指针和数组等,枚举内可以嵌套定义结构体,结构体内也可以嵌套定义枚举。
-
结构体和枚举可以用 typedef 修饰。
-
结构体里的成员小写,枚举里的所有成员大写。
-
结构体定义后加“_ struct”尾缀,对于类型定义后再追加 "_ t",例子如下。
-
结构体的实例化尽量用 "表格" 形式,并在每列头部写好注释,例子如下。
关于联合和其类型的定义形式(union)
-
联合的长度为其中最大一个变量/数组的长度,定义形式同样有直接定义、类型定义、指针和数组等,联合内可以嵌套定义结构体,结构体内也可以嵌套定义联合;联合的定义和应用情景举例如下。
关于宏定义和预编译指令定义形式(Macros and preprocessor directives)
-
宏定义使用全大写(尽量),并遵循 "属什么 _ 是什么 _ 做什么" 的命名形式。
-
尽量把常数数字用宏定义代替;常量建议使用 const 定义来代替宏;前面这两句话实际是矛盾的,因地制宜吧,优化速度用前者,优化空间用后者。
-
对宏定义中的所有输入和输出(整个结果语句)用括号保护起来,举例如下,长句用 。
-
预编译指令语句使用 tab 标识好层次,举例如下。
关于注释的形式(Comments)
-
注释里尽量写为什么,而不是把重点放在做了什么,虽然后者也很重要。
-
尽量使用 Doxygen 的注释语法,然后可以使用 Doxygen 这个软件从源码工程的注释中自动化生成软件工程的说明文档,注释写的全(包括文件和API等的描述等等)那么生成的文档也会很全。documented code allows doxygen to parse and general html/pdf/latex output, thus it is very important to do it properly。目测目前我见过的用源文件产生手册的大型项目有:LWIP、FreeRTOS、ST HAL、CMSIS等,关于 ST HAL 库里面的注释形式的详细情况请看 “8 ST HAL 的编写形式” 章节!
-
Doxygen 的注释语法规范。网上很多,这里列举几个:
-
Doxygen 注释语法规范 - 黄树超 - 博客园 (cnblogs.com)。
-
C语言中的Doxygen注释模板胡图图-CSDN博客c语言函数注释模板。
需要注意的是,Doxygen 并不处理所有的注释,其重点关注与程序结构有关的注释,比如:文件、类、结构、函数、全局变量、宏等注释,而忽略函数内局部变量、代码等的注释。先从文件开始注释,然后是所在文件的全局函数、结构体、枚举变量、命名空间→命名空间中的类→成员函数和成员变量。
-
-
使用 Doxygen 生成文档的教程(这几个教程里面也包含有 Doxygen 的语法介绍):
-
Doxygen给C程序生成注释文档 - on_the_road - 博客园 (cnblogs.com)。
-
Doxygen生成注释文档destiny的专栏-CSDN博客doxygen生成文档。
-
代码注释规范之Doxygen - silencehuan - 博客园 (cnblogs.com)。
-
-
Vs Code 的 Doxygen 格式注释生成插件:
-
Doxygen documentation Generator - Visual Studio Marketplace,用这个,官方比较全。或者在 VScode 扩展里 安装 C/C++ Extension Pack,里面包含了许多 C/C++ 实用扩展,包括 Doxygen documentation Generator。
-
Vs code自动生成Doxygen格式注释_wang0huan的博客-CSDN博客。
p.s 关于 Doxygen 文档的更多具体写法用时再详看进行手写,或者使用生成插件
-
-
-
下面列举几种花哨的,其中有我自己“创造”的。函数定义的注释,主任务函数的注释,用于显眼!
文件说明注释:
-
程序文件开头的版权信息写法列举:
引用自软件中声明版权的写法-专业指导文档类资源-CSDN下载,侵删。
正确的格式应该是:Copyright [dates] by [author/owner]
© 通常可以代替Copyright, 但是不可以用(c)。 All Rights Reserved 在某些国家曾经是必须的,但是现在在大多数国家,都不是法律上必须有的字样。
参见下面几个正确的格式:
-
©1995-2004 Macromedia, Inc. All rights reserved.
-
©2004 Microsoft Corporation. All rights reserved.
-
Copyright © 2004 Adobe Systems Incorporated. All rights reserved.
-
©1995-2004 Eric A. and Kathryn S. Meyer. All Rights Reserved.
请注意标点符号和大小写的用法,这也是专业精神的一种体现。
现在流行some rights reserved:creativecommons.org
some rights reserved 和copyright 本身并不矛盾,但是其中的界限更多是一个道德问题,真正的保留一部分权力,是指给浏览者fair use 的权利,fair use的界定也决不是随便乱用,或者抄袭。
甚至说,除了copyright, 还有copyleft,它的定义是为了程序员开发能够共享源代码的一个方式,英文里free, 并不仅仅是免费。 而且这种的源码公开免费使用,和版权也一点都不冲突。请大家不要误解。
-
其他常用写法汇集
将一些常用的并且技巧性、灵活性较高的或者少见的写法进行罗列。
实用技巧
-
巧用 按位 与/或/非 来 组合想要的二进制序列。
-
置位和清位,常用于嵌入式开发。
-
创建内存地址上连续区域的结构体,常用于嵌入式开发。
-
使用 结构体 的 位带 来直接对 bit 进行操作。引自 推荐一种超简单的硬件位带bitband操作方法,让变量,寄存器控制,IO访问更便捷,无需用户计算位置 (qq.com)。《安富莱嵌入式周报》第243期:2021.12.06--2021.12.12 (qq.com) 里面 3 硬件位带 小节有订正。
-
关于连接符 “#” 和 “##” 的使用说明,这两个都是预处理命令。
-
变长参数函数定义的使用说明。变长参数函数性能比较低而且难维护,非必要不建议使用。
-
若要修改函数的局部变量的值那么请用一级指针,若要修改局部变量一级指针的值那么用二级指针,以此类推。
-
C 有一个鲜为人知的运算符叫 ”趋向于”( “-->” )。
-
快速范围判断:再来一种新写法 - 知乎 (zhihu.com)。
-
scanf 的正则表达式用法举例。
黑魔法
OS Kernel,游戏引擎,编译器之类的,会用到不少 C 语言的黑魔法。你会惊叹于各种人类智慧的精华!!!前方高能预警!!!
-
从结构体成员的地址获取结构体地址。
-
代码增殖(黑魔法,慎用)。
-
达夫设备(Duff's Device)。
-
用字符画表述 16 进制二维数组的 “图像”,C 语言有什么奇技淫巧? - 知乎 (zhihu.com),要注意在最后加上三句 undef,再也用不到的宏定义要及时清理。
-
大段数据单独放一个文件,用预编译命令引用。
-
两数交换。
-
求取对数 和 判断一个数是否为 2 的幂。
-
快速浮点数开平方(牛顿求根法导出的开平方算法,一个详解世界上最快的浮点开方算法 - 百度文库 (baidu.com))。
-
32 位 Bit 翻转。
-
...
更多奇技淫巧
没有写明 “实用” 的大概率仅为图个新鲜:
-
gurugio/book_cprogramming: framework and Plugin design in C (github.com),总结 C 技巧,大概比较实用。
-
C 语言有什么奇技淫巧? - 知乎 (zhihu.com),特殊写法实现一些计算加速,比较实用。
-
一个“蝇量级” C 语言协程库 | 酷 壳 - CoolShell,介绍 Adam 的 protothreads 这个协程库,用 switch 实现 yield 语义。
-
C PUZZLES, Some interesting C problems (gowrikumar.com),C programming questions/puzzles,做了这些题,会学到很多的奇技。
-
code (uguu.org),可编译的源码字符画;C 语言有什么奇技淫巧? - 知乎 (zhihu.com),源码字符画 CP。
用 C 实现高阶特性
-
将 C 语言变成支持动态类型的函数式编程语言,Cello • High Level C (libcello.org)。
Github 页:orangeduck/Cello: Higher level programming in C (github.com)。
受 Python 等高级语言的启发,实现了通用数据结构/动态函数/类等等,比较全和丰富。
-
lw_oopc(C语言的面向对象) - robert_cai - 博客园 (cnblogs.com),作者做了大量的工作实现了 c 语言的封装、多态、继承这三种面向对象特征,还实现了所谓的虚函数。OOPC-C面向对象 - 知乎 (zhihu.com)。
-
C 语言有什么奇技淫巧? - 知乎 (zhihu.com),介绍 Morn 库,用 C 实现 “函数重载” 和 “泛型”。
p.s 以下有一些在 C 标准库里有实现,资源紧张可以用下面的宏定义,不紧张推荐全部使用标准库
更多实用内容:这两个总结一下写在这里,表明引用
-
【C语言笔记】认识认识#pragma、#error指令 - 知乎 (zhihu.com);
-
C语言、嵌入式中一些实用的宏技巧 - 知乎 (zhihu.com)。
p.s 资源不紧张推荐全部使用标准库(除了 malloc 和 free)
-
最常用的:
#include <stdio.h> #include <stdlib.h> #include <ctype.h> #include <string.h> #include <math.h>
参考这几个地方齐活了:
-
C标准库函数新编手册: C语言标准库函数API使用手册中文版 (gitee.com)。
上面的 gitee 仓库很优秀,已经离线到了本地中。
-
C语言常用标准库解读_张巧龙的博客-CSDN博客。
-
C 标准库 – 参考手册 | 菜鸟教程 (runoob.com)。
-
-
可能会用到的:
#include <time.h> // 提供储存时间的结构体和计算时间差等函数; #include <limits.h> // 这两个库包含了各种变量类型的最大、最小值等信息; #include <float.h>
-
不常用的:
#include <stdarg.h> // 用于函数定义变长形参; #include <assert.h> // 提供了一个名为 assert 的宏,仅在 debug 模式有效,判断一个表达式是否为 FALSE(即 0),如果是则报告错误并终止程序; #include <errno.h> // 被其他库文件调用,提供一些返回值定义; #include <locale.h> // 定义了特定地域的设置,比如日期格式和货币符号; #include <setjmp.h> #include <signal.h> #include <stddef.h>
相关文章:
-
STM32 注释风格参考:STM32注释风格参考_wanshiyingg的专栏-CSDN博客。
ST HAL 的各个文件编写风格非常一致,下面以 F4 SPI 为例:
.h 文件:
.c 文件:
-
c-code-style;
-
20个成熟软件中常用的宏定义;
-
ST HAL;
-
知乎问题页:程序员们有什么好的编程习惯?;
-
【正点原子】嵌入式Linux C代码规范化V1.0;
-
本文作者长期摸索的经验;
-
其他。
这是 ZLG致远电子 在2018年的一篇肺腑文章:
p.s 本 C 规范系广泛约取而成,参考并非照搬。
p.s 正文中部分小的内容段落的引用源有在其旁边有标注,这里列出正文的其余引用原。
p.s 此文件系业余整理而成,远不及"Google C/C++编程规范"、"华为编程规范"等文件的专业程度。
以下强烈建议空闲时认真学一学。
大厂规范
-
Google 开源项目风格指南——中文版 — Google 开源项目风格指南 (zh-google-styleguide.readthedocs.io)。
-
华为 C语言编程规范
-
华为C语言编程规范(精华总结)。
-
C语言编程规范(一)(华为标准要求)。
-
C语言编程规范(二)(华为标准要求)。
-
-
Qihoo360/safe-rules: 详细的C/C++编程规范指南,由360质量工程部编著,适用于桌面、服务端及嵌入式软件系统。 (github.com)。
-
MISRA C Coding Standard。
-
[Linux CodingStyle] Linux 内核源代码目录下的 documentation/CodingStyle 文件。中文:Linux 内核代码风格 — The Linux Kernel documentation。
设计定律、原则和模式
-
比较优雅地编码(良好的命名,清晰的结构和不差的算法);
-
nusr/hacker-laws-zh 对开发人员有用的定律、理论、原则和模式;
-
如何正确地使用设计模式? - 知乎 (zhihu.com);
-
C语言和设计模式(总结篇)平凡的程序员-CSDN博客c设计模式;
-
23种设计模式全解析_CodeAllen的博客-CSDN博客;
-
架构与设计 之一 C 嵌入式设计模式(Design Patterns for Embedded Systems in C)的学习记录_itexp-CSDN博客;
-
书籍推荐《调试九法-软硬件错误的排查之道》 - lumang - 博客园 (cnblogs.com);
-
etc。
提高代码运行效率
-
提高代码运行效率;
-
etc。
开光保护
-
佛祖保佑永无BUG 神兽护体 代码注释(各种版本)。
-
厉害了word程序猿,进寺庙给服务器开光保永不宕机。
以下是效果图。
-
发表时间:始于 2021.2 且无终稿
-
首发平台:【规范】C & MCU编写规范和其他 - 知乎 and
-
遵循协议:CC BY-NC-SA 4.0
-
其他说明:
-
本文件是“瞰百易”计划的一部分,尽量遵循 “二项玻”定则,致力于与网络上碎片化严重的现象泾渭分明(这中二魂...)!
-
本文系广泛撷取、借鉴和整理,侵删。本文适合刚入门的人阅读和遵守,也适合已经有较多编程经验的人参看。如有错误恭谢指出!
-