
简介《中兴通讯软件编程规范——C/C》是中兴通讯股份有限公司2003年发布的企业设计技术标准面向所有使用C/C语言进行软件开发的工程师用于统一公司内部软件编程风格提升源程序的可读性、可靠性和可维护性。这份规范按规则性和建议性两类要求组织内容涵盖基本原则、布局、注释、命名规则、变量常量与类型、表达式与语句、函数与过程、可靠性、可测试性、断言与错误处理等12个章节附录A和附录B还提供编程模板与规范检查表便于开发者在实际项目中逐项对照执行。压缩包为1个doc格式文档容量仅486KB轻量聚焦适合随时查阅。该文档已有301人学习浏览既能帮助企业团队快速建立统一的编码标准也适合个人开发者自查代码风格、减少缺陷是一份颇具参考价值的经典企业级编程规范资料。1. 一份C/C编码规范凭什么值得你逐行读完接手一个维护了三年多的嵌入式通信项目我第一次打开核心模块源码的时候心态是崩溃的一个文件里三种缩进风格变量名从a1、tmp到m_pStrCount混着来函数长度三百行起步最要命的是没有任何注释说明这段逻辑到底在干什么。后来项目组引进了一份内部的C/C编码规范要求所有新代码和改动代码必须按它执行。三个月后代码审查效率明显提升新人上手时间缩短线上故障排查也轻松了不少。这份规范就是某通信设备公司于2003年发布的企业标准《软件编程规范——C/C》编号 Q/ZX 04.302.1–2003。它覆盖了布局、注释、命名、表达式、函数、可靠性、可测试性和错误处理适合C/C开发工程师、嵌入式软件团队以及想建立代码规范的技术负责人阅读。今天我把这份规范拆开结合实战场景讲讲它到底怎么用。2. 设计原则与文件布局先统一思想再统一代码规范的价值从来不是约束而是统一认识。这一章是整套规范的思想底座之后所有章节的规则几乎都是从这几个原则推演出来的值得逐条细品。2.1 基本原则这八条守则是整份规范的思想根基规范开篇提出八条基本原则写得很朴素但每一条都对应着实际开发中的典型问题。【原则1-1】“首先是为人编写程序其次才是计算机”——初看像句口号但仔细想代码要经历开发、测试、生产、用户使用、版本升级和后期维护只有人能顺利读懂的代码才谈得上维护。很多团队翻车不是因为功能没实现而是因为三个月后没人敢改那段“高效但晦涩”的代码。【原则1-2】“保持代码的简明清晰避免过分追求技巧”——过度使用宏、表达式里堆叠运算符、为了少写几行把逻辑压缩成一行这些都是常见的反面教材。简明清晰就是把意图直接表达出来而不是让读者做阅读理解。【原则1-3】“所有代码尽量遵循ANSI C标准”——这一条在2003年的背景下尤其重要当时的编译器各有各的扩展语法。尽量不用未定义行为或编译器扩展是为了可移植性。【原则1-4】“首先达到正确性其次考虑效率”——这条顺序太关键了。我见过太多人一上来就优化循环、改用寄存器变量结果功能都没跑对。正确性、健壮性、可维护性先过关性能最后调这个次序不能倒。【原则1-5】“避免或少用全局变量”——全局变量带来的问题是模块间紧耦合一处改动处处受牵连。规范不搞一刀切而是说“少用”这符合工程实际。【原则1-6】“尽量避免使用GOTO语句”——GOTO会让程序的控制流变成意大利面条不到万不得已不用。这条原则在后续的“表达式与语句”章节会用规则进一步约束。【原则1-7】“尽可能重用、修正老的代码”——不是重新发明轮子而是对已验证的代码做修改优化来满足新需求减少测试成本和质量风险。【原则1-8】“尽量减少同样的错误出现的次数”——复盘历史缺陷把常见错误沉淀成规范条目这就是为什么规范越积越厚的原因。2.2 文件布局头文件和实现文件的书写顺序规范对头文件和实现文件的布局给出了明确的章节顺序这一点对读老代码特别有帮助——你打开一个文件就能知道哪个区块该找什么。头文件布局顺序是文件头注释、#ifndef防止重复包含、#include依次放标准库头文件、非标准库头文件、常量定义、全局宏、全局数据类型、类定义模板、全局函数原型、#endif。实现文件布局顺序是文件头注释、#include、常量定义、文件内部使用的宏、文件内部使用的数据类型、全局变量、本地变量静态全局变量、局部函数原型、类的实现、全局函数、局部函数。这里我实际用下来最受益的是两条硬性规则。第一条是【规则2-1-4】头文件必须避免重复包含用宏定义保护#ifndef MODULE_H #define MODULE_H /* 文件体 */ #endif这个写法现在已经是常识了但在老代码里依然能看到不写保护的头文件一旦被两个源文件包含就会报重复定义错误。第二条是【规则2-1-5】标准库头文件用尖括号非标准库用双引号这个约定让阅读者一眼分辨头文件的来源归属。2.3 文件布局的价值给代码建立“心理地图”把布局规则落到工程实践上本质上是给代码建立一套心理地图。比如我后来接手别人的模块先是翻文件头注释了解用途和修改记录然后扫常量定义和数据类型再看全局函数原型基本不用通读全文就能知道这个文件的“骨架”。这样做的好处是代码审查时能快速定位到关键节点缺陷排查时能判断哪个区域最可能出问题。3. 从布局到注释可读性不是靠感觉是靠规则布局是代码的外壳注释则是代码的说明书。这一章把规范里最详尽的布局规则和注释规则拆开讲每一条都有正反例可以直接当团队checklist用。3.1 基本格式与缩进四个空格的执念规范对格式的要求极其具体不搞“建议尽量”这种软话直接就是规则。【规则2-2-1】一行代码和注释不能超过80列包括空格在内。这条约束写代码的人要时刻注意断行否则在分屏对比代码时会被折行搞疯。【规则2-2-2】if、else、else if、for、while、do等语句自占一行执行语句不得紧跟其后不论执行语句有多少都要加{}。/* 正例 */ if (varible1 varible2) { varible1 varible2; } /* 反例执行语句紧跟if条件且不加花括号 */ if (varible1 varible2) varible1 varible2;这条规则我举双手赞成。不加花括号的写法在后续维护中极易埋雷——有人想往分支里加一行代码结果忘了加花括号新代码变成了无条件执行。这是典型的“省一行毁一夜”翻车现场。【规则2-3-1】禁止使用TAB键必须使用空格进行缩进缩进为4个空格。原因很简单不同的编辑器对TAB的解析宽度不一样在A机器上对齐的代码到B机器上就错位了。现在IDE基本都能配置“TAB转空格”但在老旧的嵌入式开发环境里这条规则还是能避免大量对齐问题。实战中我建议在项目根目录放一份.editorconfig文件把indent_style space和indent_size 4固定下来。【规则2-3-2】程序的分界符{和}应独占一行并且位于同一列同时与引用它们的语句左对齐。这样写的好处是成对的花括号垂直对齐代码块边界一目了然。3.2 对齐、空行与空格让代码呈现“结构感”布局规则有很多细节但它们都在为同一个目标服务让代码呈现出结构感让人眼能像看报纸一样扫读。【规则2-3-5】相关的赋值语句等号对齐。举规范里的例子tPDBRes.wHead 0; tPDBRes.wTail wMaxNumOfPDB - 1; tPDBRes.wFree wMaxNumOfPDB; tPDBRes.wAddress wPDBAddr; tPDBRes.wSize wPDBSize;一组同类的赋值语句对齐后右侧的变量值和左侧的逻辑关系一目了然。维护时增删字段也能立刻发现不对称的地方。【规则2-4-1】不同逻辑程序块之间要用空行分隔。函数与函数之间、循环体与后续代码之间这些位置都应该有空行让代码“喘口气”。void Foo::Hey(void) { /* Hey实现代码 */ } /* 空一行 */ void Foo::Ack(void) { /* Ack实现代码 */ }【规则2-4-2】【规则2-4-3】在运算符空格上做了标准化一元操作符如!、~、、--、*、地址运算符前后不加空格多元运算符和操作数之间至少一个空格。/* 一元运算符 */ !bValue ~iValue iCount *strSource fSum /* 多元运算符 */ fValue fOldValue; fTotal fValue; iNumber 2;【规则2-4-4】关键字之后要留空格。if、for、while等关键字后应留一个空格再跟左括号以突出关键字与函数调用的区别。函数名后则不要留空格【规则2-4-5】这样一眼就能区分if (和DoSomething(。3.3 断行规则长表达式怎么拆才不破坏逻辑超过80列时必须断行但怎么断有讲究。【规则2-5-1】长表达式要在低优先级操作符处拆分成新行操作符放在新行之首以便突出操作符。拆分出的新行要做适当缩进。/* 条件表达式续行在第一个条件处对齐 */ if ((iFormat CH_A_Format_M) (iOfficeType CH_BSC_M)) { DoSomething(); } /* 赋值语句的续行在赋值号处对齐 */ fTotalBill fTotalBill faCustomerPurchases[iID] fSalesTax(faCustomerPurchases[iID]);注意看操作符和都放在了新行的行首这样读代码时先看到操作符再看到操作数逻辑重心在前可读性确实更好。3.4 注释规则注释是解释意图不是复述代码规范的注释章节开篇就说了一段很有价值的话有效的注释是指在代码的功能、意图层次上进行注释提供有用、额外的信息而不是代码表面意义的简单重复。【规则3-1】C语言的注释符为/* ... */C语言中多行注释采用/* ... */单行注释采用// ...。【规则2-4-7】注释符与注释内容之间要用一个空格进行分隔。/* 注释内容 */ // 注释内容我自己的习惯是文件头写版权声明和修改历史函数定义上方写功能、参数、返回值和注意事项代码行内注释只加在有隐含逻辑和魔法数字的地方。一眼能看懂的代码不注释看不懂的代码才需要注释。4. 命名、变量与函数给机器写的代码给人命名命名是编程里最难的事之一。规范用了一整章的篇幅定义命名规则并且给出了一套实用的前缀体系。这一章把命名规则和变量、函数的规范串起来讲落到实际怎么写。4.1 命名规则与变量前缀从名字就能读出“元信息”规范的命名规则章节定义了完整的匈牙利命名法变体。变量名要带类型前缀指针用p函数指针用fp全局变量用g静态变量用s类成员变量用m_。拿规范里的例子看float *pfBuffer; /* p表示指针f表示floatBuffer是用途 */ int iCount; /* i表示int */ char chKey; /* ch表示char */ BYTE ucCallNo; /* uc表示unsigned char */这套命名法的精髓在于从变量名就能读出它的类型、作用域不用回头翻定义。【规则2-2-3】定义指针类型的变量*应放在变量前写成float *pfBuffer而不是float* pfBuffer。这个细节起初看起来有点别扭但读代码的时候“指针属性”紧贴变量名效果更直观。为了避免变量名过长或过短命名规则章节还要求不使用含义模糊的缩写。比如用iLength而不是iLen用strUserName而不是strUN。我见过太多用tmp1、tmp2糊弄的代码三个月后自己都分不清哪个是哪个。类定义内部【规则2-3-3】public、protected、private关键字要与{}对齐这些部分的内容要缩进。类成员变量统一加m_前缀避免与函数局部变量混淆class CCount { public: CCount(void); ~CCount(void); int GetCount(void); void SetCount(int iCount); private: int m_iCount; };4.2 变量、常量与类型全局变量严格控制规范对变量的约束集中在“少用全局变量”这个原则上具体落地包括模块内全局变量用static修饰限制可见范围定义常量用const而不是#defineC环境下枚举类型用于有穷集合的取值。类型定义上有两条硬规则结构体类型定义要放在头文件或文件前部的“数据类型定义”区块中复杂的类型定义必须写注释说明其用途和约束。这些都是小细节但加在一起能显著减少误用和误读。4.3 表达式、语句与函数用规则把“坑”填平【规则2-2-2】已经要求if语句强制执行体加大括号函数章节则对函数本身的写法提出了约束。函数实现遵循单一职责的原则一个函数只做一件事函数的行数控制在50行以内建议性规范。函数名用动词开头如GetCount()、SetCount()、DoSomething()。函数参数的书写有明确规则——【规则2-5-2】函数声明时类型与名称不允许分行书写/* 正例 */ extern double FAR CalcArea(double dWidth, double dHeight); /* 反例类型与名称被拆到两行 */ extern double FAR CalcArea(double dWidth, double dHeight);函数声明时类型与名称同行参数过多时在第一个参数处对齐续行。这样写让函数签名在代码搜索和IDE解析时更稳定尤其是在老式的源码浏览工具里折行会破坏函数原型的解析。4.4 实际应用代码审查时怎么用这套规则我在团队里推这套命名规范时有个体会与其靠人自觉不如把规范变成代码审查的检查项。审查时先看文件名和头文件布局再看变量命名是否带合法前缀最后看函数长度是否超标。自动化工具如cppcheck和clang-tidy能检查一部分格式问题但命名语义和前缀规则还是得靠人眼。5. 可靠性、可测试性与错误处理避坑与排查实战前面几章讲的都是代码“长什么样”这一章要讲的是代码“要不出事”。规范和错误处理章节直接关系线上质量是这次拆解的重点也是踩坑记录最丰富的部分。5.1 可靠性从卫生习惯开始可靠性章节的核心思路是“防御性编程”假设输入是恶意的、假设资源是有限的、假设外部环境是不可靠的。具体规则包括函数入口处检查参数合法性指针使用前判空除法运算前检查分母不为零数组访问前检查索引边界申请的内存一定要释放且成对出现。这些都是教科书写烂了的内容但真正能在每一行代码里坚持的人不多。另一个重要条目是防止整数溢出。在计算缓冲区长度或分配大小时先判断加法和乘法是否溢出再执行操作这是很多安全漏洞的根源。规范在2003年就写了不要使用无符号整数的循环条件因为for (unsigned int i n; i 0; i--)是一个死循环——i减到0后再减会变成最大值这个坑我亲眼见过有人踩排查了大半天。5.2 可测试性代码从设计上就要容易被验证可测试性章节建议函数尽量不依赖全局状态输入输出尽量通过参数和返回值传递模块通过接口解耦便于打桩和模拟。这在当时是相当先进的思想——很多团队还以为“测试”就是编译运行看结果。规范附录A的编程模板提供了标准的代码组织格式从文件头到函数实现都有空行和注释位附录B的规范检查表则是一个逐项打勾的清单。这两份附录是直接把规范落地的实操工具而不是停留在概念层面。5.3 断言与错误处理程序要“死得明白”断言与错误处理章节强调断言用于捕获“不应该发生”的程序逻辑错误错误处理用于处理“可能发生”的运行时异常。两者分工明确不能混淆。/* 断言示例 */ assert(pBuffer ! NULL); /* 错误处理示例 */ if (pBuffer NULL) { return ERROR_NULL_POINTER; }使用断言的一个重要准则是断言只应在调试版本中生效发布版本要把断言编译掉。如果断言保留在发布版本中可能会因为触发断言而暴露内部信息甚至被利用来发起拒绝服务攻击。错误处理的三条原则是错误信息要包含足够的上下文哪个模块、哪个操作错误码要有统一的定义和管理资源清理要做“对等释放”——在函数的所有退出路径上都要释放资源包括正常返回和错误返回。最常见的资源泄漏是提前return时忘了释放这类问题用cppcheck能自动查出来一部分但最好从代码审查源头解决。5.4 常见踩坑记录以下是我在实践中整理的高频问题每一条都是真实发生过的按“现象→原因→解决”的套路写出来。踩坑一TAB和空格混用导致代码错位现象代码在开发者的编辑器里对齐整齐提交到代码库后其他人打开看全是错位的。原因有人用TAB缩进有人用4个空格缩进而不同编辑器对TAB的渲染宽度不同。解决项目根目录统一放.editorconfig强制indent_style space、indent_size 4代码提交前跑一遍格式检查脚本不符合的直接拒绝提交。踩坑二匈牙利前缀误导类型判断现象代码里有个变量叫iYear结果实际是个unsigned int还有人底层是char的数组存年份。原因命名时没严格绑定类型前缀后面类型改了但名字没同步。解决把命名列入代码审查硬性检查项类型变更时变量名同步修改。虽然改起来麻烦但不改的后患更大。踩坑三#include头文件顺序混乱导致编译失败现象自己本地编译过了别人一拉代码就报错换个编译器又报错。原因头文件包含顺序依赖了编译环境的隐式包含非标准头文件写在了标准库前面导致宏定义或类型定义缺失。解决严格执行【规则2-1-5】的标准库在前、非标准库在后的顺序同时头文件加保护宏。这样依赖关系稳定编译错误也容易定位。踩坑四断言在发布版本中对用户弹错误框现象程序发布后用户操作时突然弹出一个断言失败对话框。原因断言没有被编译掉错误处理被当成了断言来写。解决区分断言和错误处理断言只检查“不可能发生”的条件编译时排除在发布版本外用户可见的错误路径一律用错误处理代码。踩坑五长表达式断行位置不对逻辑被读错现象后来的人修改条件表达式时误以为换行后的两行是并列的两个条件。原因断行时没有把操作符放在行首或者缩进不对让阅读者产生了歧义。解决严格按【规则2-5-1】操作符放行首续行缩进。这属于格式问题但格式问题直接影响人脑对逻辑的判断。6. 把规范落地到自己的项目从检查表到渐进式改造这可能是读者最关心的一章我怎么把这套2003年的规范用在自己的项目上而不是把它锁进抽屉里。我的经验是别指望一步到位用渐进式改造和工具化手段让规范真正在一个存量代码库里生根。第一步是引入规范检查表。规范附录B本来就是逐项打勾的检查表我把它翻译成团队代码审查的模板文件头是否完整、头文件保护宏是否存在、缩进是否4空格、变量名前缀是否合法、函数行数是否超标、空行是否分隔逻辑块、断行是否合规。审查者在代码评审时逐项过一遍几分钟就能完成。这三五分钟的成本换来的是代码库的长期一致性性价比很高。第二步是格式化工具先行。用clang-format把布局规则自动化配置项就按规范来ColumnLimit: 80、IndentWidth: 4、UseTab: Never、BreakBeforeBinaryOperators: All。人工审查只管逻辑和命名格式问题交给工具。这一步能消除团队里95%的风格争论。但clang-format管不了注释写法规范的注释规则还是要靠代码审查时提示。第三步是控制增量。对存量代码不做大范围重写只要求新代码和改动代码符合规范。改到哪个文件就顺手把这个文件的格式规范一遍。这样渐进式推进既不影响业务节奏又能让代码库的“规范覆盖率”稳步提升。最后一步是在团队内部建立共识编码规范不是某些人拍脑袋定的教条而是从历史代码的血泪教训里提炼出来的。规范里的每一条规则背后至少对应过一次线上故障或者一次低效排查。这个共识建立起来之后团队成员的执行力会明显提升因为他们是真心认可这些规则的价值。从那以后我每次给项目设计代码结构的时候都会强制走一遍规范的核心条目文件布局按照头文件和实现文件的固定顺序来排变量命名带类型前缀长表达式断行时把操作符放行首错误处理先于性能优化考虑。一圈走下来代码自己会说话。希望帮到你。本文还有配套的精品资源点击获取