简介:《中兴通讯软件编程规范——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表示float,Buffer是用途 */ 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而不是#define(C++环境下);枚举类型用于有穷集合的取值。
类型定义上有两条硬规则:结构体类型定义要放在头文件或文件前部的“数据类型定义”区块中;复杂的类型定义必须写注释说明其用途和约束。这些都是小细节,但加在一起能显著减少误用和误读。
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管不了注释写法,规范的注释规则还是要靠代码审查时提示。
第三步是控制增量。对存量代码不做大范围重写,只要求新代码和改动代码符合规范。改到哪个文件,就顺手把这个文件的格式规范一遍。这样渐进式推进,既不影响业务节奏,又能让代码库的“规范覆盖率”稳步提升。
最后一步是在团队内部建立共识:编码规范不是某些人拍脑袋定的教条,而是从历史代码的血泪教训里提炼出来的。规范里的每一条规则,背后至少对应过一次线上故障或者一次低效排查。这个共识建立起来之后,团队成员的执行力会明显提升,因为他们是真心认可这些规则的价值。
从那以后,我每次给项目设计代码结构的时候,都会强制走一遍规范的核心条目:文件布局按照头文件和实现文件的固定顺序来排,变量命名带类型前缀,长表达式断行时把操作符放行首,错误处理先于性能优化考虑。一圈走下来,代码自己会说话。希望帮到你。
本文还有配套的精品资源,点击获取