精华内容
下载资源
问答
  • 华为c语言编程规范v3.2
    万次阅读 多人点赞
    2018-01-26 23:07:12

    总体原则
    1、清晰第一
    2、简洁为美
    3、选择合适的风格,与代码原有风格保持一致

    1 头文件

    对于C语言来说,头文件的设计体现了大部分的系统设计。
    原则1.1 头文件中适合放置接口的声明,不适合放置实现。
    说明:头文件是模块(Module)或单元(Unit)的对外接口。头文件中应放置对外部的声明,如对外提供的函数声明、宏定义、类型定义等。

    原则1.2 头文件应当职责单一。
    说明:头文件过于复杂,依赖过于复杂是导致编译时间过长的主要原因。很多现有代码中头文件过大,职责过多,再加上循环依赖的问题,可能导致为了在.c中使用一个宏,而包含十几个头文件。

    原则1.3 头文件应向稳定的方向包含。
    说明:头文件的包含关系是一种依赖,一般来说,应当让不稳定的模块依赖稳定的模块,从而当不稳定的模块发生变化时,不会影响(编译)稳定的模块。
    产品依赖于平台,平台依赖于标准库。
    除了不稳定的模块依赖于稳定的模块外,更好的方式是两个模块共同依赖于接口。

    规则1.1 每一个.c文件应有一个同名.h文件,用于声明需要对外公开的接口。
    说明:如果一个.c文件不需要对外公布任何接口,则其就不应当存在,除非它是程序的入口,如main函数所在的文件。

    规则1.2 禁止头文件循环依赖。
    说明:头文件循环依赖,指a.h包含b.h,b.h包含c.h,c.h包含a.h之类导致任何一个头文件修改,都导致所有包含了a.h/b.h/c.h的代码全部重新编译一遍。而如果是单向依赖,如a.h包含b.h,b.h包含c.h,而c.h不包含任何头文件,则修改a.h不会导致包含了b.h/c.h的源代码重新编译。

    规则1.3 .c/.h文件禁止包含用不到的头文件。
    说明:很多系统中头文件包含关系复杂,开发人员为了省事起见,可能不会去一一钻研,直接包含一切想到的头文件,甚至有些产品干脆发布了一个god.h,其中包含了所有头文件,然后发布给各个项目组使用,这种只图一时省事的做法,导致整个系统的编译时间进一步恶化,并对后来人的维护造成了巨大的麻烦。

    规则1.4 头文件应当自包含。
    说明:简单的说,自包含就是任意一个头文件均可独立编译。如果一个文件包含某个头文件,还要包含另外一个头文件才能工作的话,就会增加交流障碍,给这个头文件的用户增添不必要的负担。

    规则1.5 总是编写内部#include保护符(#define 保护)。
    说明:多次包含一个头文件可以通过认真的设计来避免。如果不能做到这一点,就需要采取阻止头文件内容被包含多于一次的机制。

    规则1.6 禁止在头文件中定义变量。
    说明:在头文件中定义变量,将会由于头文件被其他.c文件包含而导致变量重复定义。

    规则1.7 只能通过包含头文件的方式使用其他.c提供的接口,禁止在.c中通过extern的方式使用外部函数接口、变量。
    说明:若a.c使用了b.c定义的foo()函数,则应当在b.h中声明extern int foo(int input);并在a.c中通过#include <b.h>来使用foo。禁止通过在a.c中直接写extern int foo(int input);来使用foo,后面这种写法容易在foo改变时可能导致声明和定义不一致。

    规则1.8 禁止在extern "C"中包含头文件。
    说明:在extern "C"中包含头文件,会导致extern "C"嵌套,Visual Studio对extern "C"嵌套层次有限制,嵌套层次太多会编译错误。

    建议1.1 一个模块通常包含多个.c文件,建议放在同一个目录下,目录名即为模块名。为方便外部使用者,建议每一个模块提供一个.h,文件名为目录名。
    说明:需要注意的是,这个.h并不是简单的包含所有内部的.h,它是为了模块使用者的方便,对外整体提供的模块接口。

    建议1.2 如果一个模块包含多个子模块,则建议每一个子模块提供一个对外的.h,文件名为子模块名。
    说明:降低接口使用者的编写难度。

    建议1.3 头文件不要使用非习惯用法的扩展名,如.inc。
    说明:目前很多产品中使用了.inc作为头文件扩展名,这不符合c语言的习惯用法。在使用.inc作为头文件扩展名的产品,习惯上用于标识此头文件为私有头文件。但是从产品的实际代码来看,这一条并没有被遵守,一个.inc文件被多个.c包含比比皆是。本规范不提倡将私有定义单独放在头文件中。

    建议1.4 同一产品统一包含头文件排列方式。
    说明:常见的包含头文件排列方式:功能块排序、文件名升序、稳定度排序。

    2 函数

    函数设计的精髓:编写整洁函数,同时把代码有效组织起来。
    原则2.1 一个函数仅完成一件功能。
    说明:一个函数实现多个功能给开发、使用、维护都带来很大的困难。

    原则2.2 重复代码应该尽可能提炼成函数。
    说明:重复代码提炼成函数可以带来维护成本的降低。

    规则2.1 避免函数过长,新增函数不超过50行(非空非注释行)。
    说明:本规则仅对新增函数做要求,对已有函数修改时,建议不增加代码行。

    规则2.2 避免函数的代码块嵌套过深,新增函数的代码块嵌套不超过4层。
    说明:本规则仅对新增函数做要求,对已有的代码建议不增加嵌套层次。

    规则2.3 可重入函数应避免使用共享变量;若需要使用,则应通过互斥手段(关中断、信号量)对其加以保护。
    说明:可重入函数是指可能被多个任务并发调用的函数。在多任务操作系统中,函数具有可重入性是多个任务可以共用此函数的必要条件。共享变量指的全局变量和static变量。

    规则2.4 对参数的合法性检查,由调用者负责还是由接口函数负责,应在项目组/模块内应统一规定。缺省由调用者负责。
    说明:对于模块间接口函数的参数的合法性检查这一问题,往往有两个极端现象,即:要么是调用者和被调用者对参数均不作合法性检查,结果就遗漏了合法性检查这一必要的处理过程,造成问题隐患;要么就是调用者和被调用者均对参数进行合法性检查,这种情况虽不会造成问题,但产生了冗余代码,降低了效率。

    规则2.5 对函数的错误返回码要全面处理。
    说明:一个函数(标准库中的函数/第三方库函数/用户定义的函数)能够提供一些指示错误发生的方法。这可以通过使用错误标记、特殊的返回数据或者其他手段,不管什么时候函数提供了这样的机制,调用程序应该在函数返回时立刻检查错误指示。

    规则2.6 设计高扇入,合理扇出(小于7)的函数。
    说明:扇出是指一个函数直接调用(控制)其它函数的数目,而扇入是指有多少上级函数调用它。

    规则2.7 废弃代码(没有被调用的函数和变量)要及时清除。
    说明:程序中的废弃代码不仅占用额外的空间,而且还常常影响程序的功能与性能,很可能给程序的测试、维护等造成不必要的麻烦。

    建议2.1 函数不变参数使用const。
    说明:不变的值更易于理解/跟踪和分析,把const作为默认选项,在编译时会对其进行检查,使代码更牢固/更安全。

    建议2.2 函数应避免使用全局变量、静态局部变量和I/O操作,不可避免的地方应集中使用。
    说明:带有内部“存储器”的函数的功能可能是不可预测的,因为它的输出可能取决于内部存储器(如某标记)的状态。这样的函数既不易于理解又不利于测试和维护。在C语言中,函数的static局部变量是函数的内部存储器,有可能使函数的功能不可预测,然而,当某函数的返回值为指针类型时,则必须是static的局部变量的地址作为返回值,若为auto类,则返回为错针。

    建议2.3 检查函数所有非参数输入的有效性,如数据文件、公共变量等。
    说明:函数的输入主要有两种:一种是参数输入;另一种是全局变量、数据文件的输入,即非参数输入。函数在使用输入参数之前,应进行有效性检查。

    建议2.4 函数的参数个数不超过5个。
    说明:函数的参数过多,会使得该函数易于受外部(其他部分的代码)变化的影响,从而影响维护工作。函数的参数过多同时也会增大测试的工作量。

    建议2.5 除打印类函数外,不要使用可变长参函数。
    说明:可变长参函数的处理过程比较复杂容易引入错误,而且性能也比较低,使用过多的可变长参函数将导致函数的维护难度大大增加。

    建议2.6 在源文件范围内声明和定义的所有函数,除非外部可见,否则应该增加static关键字。
    说明:如果一个函数只是在同一文件中的其他地方调用,那么就用static声明。使用static确保只是在声明它的文件中是可见的,并且避免了和其他文件或库中的相同标识符发生混淆的可能性。

    3 标识符命名与定义

    unix like风格:单词用小写字母,每个单词直接用下划线“_”分割。
    Windows风格:大小写字母混用,单词连在一起,每个单词首字母大写。
    原则3.1 标识符的命名要清晰、明了,有明确含义,同时使用完整的单词或大家基本可以理解的缩写,避免使人产生误解。
    说明:尽可能给出描述性名称,不要节约空间,让别人很快理解你的代码更重要。

    原则3.2 除了常见的通用缩写以外,不使用单词缩写,不得使用汉语拼音。
    说明:较短的单词可通过去掉“元音”形成缩写,较长的单词可取单词的头几个字母形成缩写,一些
    单词有大家公认的缩写,常用单词的缩写必须统一。协议中的单词的缩写与协议保持一致。对于某个

    规则3.1 产品/项目组内部应保持统一的命名风格。
    说明:Unix like和windows like风格均有其拥趸,产品应根据自己的部署平台,选择其中一种,并在产品内部保持一致。

    规则3.2 全局变量应增加“g_”前缀。

    规则3.3 静态变量应增加“s_”前缀。
    说明:增加g前缀或者s前缀,原因如下:首先,全局变量十分危险,通过前缀使得全局变量更加醒目,促使开发人员对这些变量的使用更加小心。其次,从根本上说,应当尽量不使用全局变量,增加g_和s_前缀,会使得全局变量的名字显得很丑陋,从而促使开发人员尽量少使用全局变量。

    规则3.4 禁止使用单字节命名变量,但允许定义i、j、k作为局部循环变量。

    规则3.5 对于数值或者字符串等等常量的定义,建议采用全大写字母,单词之间加下划线„_‟的方式命名(枚举同样建议使用此方式定义)。

    规则3.6 除了头文件或编译开关等特殊标识定义,宏定义不能使用下划线„_‟开头和结尾。
    说明:一般来说,‟_‟开头、结尾的宏都是一些内部的定义,

    建议3.1 用正确的反义词组命名具有互斥意义的变量或相反动作的函数等。

    建议3.2 尽量避免名字中出现数字编号,除非逻辑上的确需要编号。

    建议3.3 标识符前不应添加模块、项目、产品、部门的名称作为前缀。
    说明:很多已有代码中已经习惯在文件名中增加模块名,这种写法类似匈牙利命名法,导致文件名不
    可读,,不利于维护和移植。

    建议3.4 平台/驱动等适配代码的标识符命名风格保持和平台/驱动一致。
    说明:涉及到外购芯片以及配套的驱动,这部分的代码变动(包括为产品做适配的新增代码),应该
    保持原有的风格。

    建议3.5 重构/修改部分代码时,应保持和原有代码的命名风格一致。
    说明:根据源代码现有的风格继续编写代码,有利于保持总体一致。

    建议3.6 文件命名统一采用小写字符。
    说明:因为不同系统对文件名大小写处理会不同(如MS的DOS、Windows系统不区分大小写,但是Linux
    系统则区分),所以代码文件命名建议统一采用全小写字母命名。

    建议3.7 不建议使用匈牙利命名法。
    说明:变量命名需要说明的是变量的含义,而不是变量的类型。在变量命名前增加类型说明,反而降低了变量的可读性;更麻烦的问题是,如果修改了变量的类型定义,那么所有使用该变量的地方都需要修改。

    建议3.8 使用名词或者形容词+名词方式命名变量。

    建议3.9 函数命名应以函数要执行的动作命名,一般采用动词或者动词+名词的结构。

    建议3.10 函数指针除了前缀,其他按照函数的命名规则命名。

    4 变量

    原则4.1 一个变量只有一个功能,不能把一个变量用作多种用途。
    说明:一个变量只用来表示一个特定功能,不能把一个变量作多种用途,即同一变量取值不同时,其代表的意义也不同。

    原则4.2 结构功能单一;不要设计面面俱到的数据结构。
    说明:相关的一组信息才是构成一个结构体的基础,结构的定义应该可以明确的描述一个对象,而不是一组相关性不强的数据的集合。

    原则4.3 不用或者少用全局变量。
    说明:单个文件内部可以使用static的全局变量,可以将其理解为类的私有成员变量。

    规则4.1 防止局部变量与全局变量同名。
    说明:尽管局部变量和全局变量的作用域不同而不会发生语法错误,但容易使人误解。

    规则4.2 通讯过程中使用的结构,必须注意字节序。
    说明:通讯报文中,字节序是一个重要的问题,我司设备使用的cpu类型复杂多样,大小端、32位/64位的处理器也都有,如果结构会在报文交互过程中使用,必须考虑字节序问题。

    规则4.3 严禁使用未经初始化的变量作为右值。
    说明:坚持建议4.3(在首次使用前初始化变量,初始化的地方离使用的地方越近越好。)可以有效避免未初始化错误。

    建议4.1 构造仅有一个模块或函数可以修改、创建,而其余有关模块或函数只访问的全局变量,防止多个不同模块或函数都可以修改、创建同一全局变量的现象。
    说明:降低全局变量耦合度。

    建议4.2 使用面向接口编程思想,通过API访问数据:如果本模块的数据需要对外部模块开放,应提供接口函数来设置、获取,同时注意全局数据的访问互斥。
    说明:避免直接暴露内部数据给外部模型使用,是防止模块间耦合最简单有效的方法。

    建议4.3 在首次使用前初始化变量,初始化的地方离使用的地方越近越好。
    说明:未初始化变量是C和C++程序中错误的常见来源。在变量首次使用前确保正确初始化。
    在较好的方案中,变量的定义和初始化要做到亲密无间。

    建议4.4 明确全局变量的初始化顺序,避免跨模块的初始化依赖。
    说明:系统启动阶段,使用全局变量前,要考虑到该全局变量在什么时候初始化,使用全局变量和初始化全局变量,两者之间的时序关系,谁先谁后,一定要分析清楚,不然后果往往是低级而又灾难性的。

    建议4.5 尽量减少没有必要的数据类型默认转换与强制转换。
    说明:当进行数据类型强制转换时,其数据的意义、转换后的取值等都有可能发生变化,而这些细节若考虑不周,就很有可能留下隐患。

    5 宏、常量##

    规则5.1 用宏定义表达式时,要使用完备的括号。
    说明:因为宏只是简单的代码替换,不会像函数一样先将参数计算后,再传递。

    规则5.2 将宏所定义的多条表达式放在大括号中。
    说明:更好的方法是多条语句写成do while(0)的方式。

    规则5.3 使用宏时,不允许参数发生变化。

    规则5.4 不允许直接使用魔鬼数字。
    说明:使用魔鬼数字的弊端:代码难以理解;如果一个有含义的数字多处使用,一旦需要修改这个数值,代价惨重。

    建议5.1 除非必要,应尽可能使用函数代替宏。
    说明:宏对比函数,有一些明显的缺点:1.宏缺乏类型检查,不如函数调用检查严格。2.宏展开可能会产生意想不到的副作用,如#define SQUARE(a) (a) * (a)这样的定义,如果是SQUARE(i++),就会导致i被加两次;如果是函数调用double square(double a) {return a * a;}则不会有此副作用。3.以宏形式写的代码难以调试难以打断点,不利于定位问题。4.宏如果调用的很多,会造成代码空间的浪费,不如函数空间效率高。

    建议5.2 常量建议使用const定义代替宏。
    说明:尽量用编译器而不用预处理,因为#define经常被认为好象不是语言本身的一部分。

    建议5.3 宏定义中尽量不使用return、goto、continue、break等改变程序流程的语句。
    说明:如果在宏定义中使用这些改变流程的语句,很容易引起资源泄漏问题,使用者很难自己察觉。

    6 质量保证##

    原则6.1 代码质量保证优先原则
    (1)正确性,指程序要实现设计要求的功能。
    (2)简洁性,指程序易于理解并且易于实现。
    (3)可维护性,指程序被修改的能力,包括纠错、改进、新需求或功能规格变化的适应能力。
    (4)可靠性,指程序在给定时间间隔和环境条件下,按设计要求成功运行程序的概率。
    (5)代码可测试性,指软件发现故障并隔离、定位故障的能力,以及在一定的时间和成本前提下,进行测试设计、测试执行的能力。
    (6)代码性能高效,指是尽可能少地占用系统资源,包括内存和执行时间。
    (7)可移植性,指为了在原来设计的特定环境之外运行,对系统进行修改的能力。
    (8)个人表达方式/个人方便性,指个人编程习惯。

    原则6.2 要时刻注意易混淆的操作符。
    说明:包括易混淆和的易用错操作符

    原则6.3 必须了解编译系统的内存分配方式,特别是编译系统对不同类型的变量的内存分配规则,如局部变量在何处分配、静态变量在何处分配等。

    原则6.4 不仅关注接口,同样要关注实现。
    说明:这个原则看似和“面向接口”编程思想相悖,但是实现往往会影响接口,函数所能实现的功能,除了和调用者传递的参数相关,往往还受制于其他隐含约束,如:物理内存的限制,网络状况,具体看“抽象漏洞原则”。

    规则6.1 禁止内存操作越界。
    说明:内存操作主要是指对数组、指针、内存地址等的操作。内存操作越界是软件系统主要错误之一,后果往往非常严重,所以当我们进行这些操作时一定要仔细小心。
    坚持下列措施可以避免内存越界:
     数组的大小要考虑最大情况,避免数组分配空间不够。
     避免使用危险函数sprintf /vsprintf/strcpy/strcat/gets操作字符串,使用相对安全的函数snprintf/strncpy/strncat/fgets代替。
     使用memcpy/memset时一定要确保长度不要越界
     字符串考虑最后的’\0’, 确保所有字符串是以’\0’结束
     指针加减操作时,考虑指针类型长度
     数组下标进行检查
     使用时sizeof或者strlen计算结构/字符串长度,避免手工计算

    规则6.2 禁止内存泄漏。
    说明:内存和资源(包括定时器/文件句柄/Socket/队列/信号量/GUI等各种资源)泄漏是常见的错误。
    坚持下列措施可以避免内存泄漏:
     异常出口处检查内存、定时器/文件句柄/Socket/队列/信号量/GUI等资源是否全部释放
     删除结构指针时,必须从底层向上层顺序删除
     使用指针数组时,确保在释放数组时,数组中的每个元素指针是否已经提前被释放了
     避免重复分配内存
     小心使用有return、break语句的宏,确保前面资源已经释放
     检查队列中每个成员是否释放

    规则6.3 禁止引用已经释放的内存空间。
    说明:在实际编程过程中,稍不留心就会出现在一个模块中释放了某个内存块,而另一模块在随后的某个时刻又使用了它。要防止这种情况发生。
    坚持下列措施可以避免引用已经释放的内存空间:
     内存释放后,把指针置为NULL;使用内存指针前进行非空判断。
     耦合度较强的模块互相调用时,一定要仔细考虑其调用关系,防止已经删除的对象被再次使用。
     避免操作已发送消息的内存。
     自动存储对象的地址不应赋值给其他的在第一个对象已经停止存在后仍然保持的对象(具有更大作用域的对象或者静态对象或者从一个函数返回的对象)

    规则6.4 编程时,要防止差1错误。
    说明:此类错误一般是由于把“<=”误写成“<”或“>=”误写成“>”等造成的,由此引起的后果,很多情况下是很严重的,所以编程时,一定要在这些地方小心。当编完程序后,应对这些操作符进行彻底检查。使用变量时要注意其边界值的情况。

    规则6.5 所有的if … else if结构应该由else子句结束 ;switch语句必须有default分支。

    建议6.1 函数中分配的内存,在函数退出之前要释放。
    说明:有很多函数申请内存,保存在数据结构中,要在申请处加上注释,说明在何处释放。

    建议6.2 if语句尽量加上else分支,对没有else分支的语句要小心对待。

    建议6.3 不要滥用goto语句。
    说明:goto语句会破坏程序的结构性,所以除非确实需要,最好不使用goto语句。

    建议6.4 时刻注意表达式是否会上溢、下溢。

    7 程序效率

    原则7.1 在保证软件系统的正确性、简洁、可维护性、可靠性及可测性的前提下,提高代码效率。
    说明:不能一味地追求代码效率,而对软件的正确、简洁、可维护性、可靠性及可测性造成影响。

    原则7.2 通过对数据结构、程序算法的优化来提高效率。

    建议7.1 将不变条件的计算移到循环体外。
    说明:将循环中与循环无关,不是每次循环都要做的操作,移到循环外部执行。

    建议7.2 对于多维大数组,避免来回跳跃式访问数组成员。

    建议7.3 创建资源库,以减少分配对象的开销。
    说明:例如,使用线程池机制,避免线程频繁创建、销毁的系统调用;使用内存池,对于频繁申请、释放的小块内存,一次性申请一个大块的内存,当系统申请内存时,从内存池获取小块内存,使用完毕再释放到内存池中,避免内存申请释放的频繁系统调用.

    建议7.4 将多次被调用的 “小函数”改为inline函数或者宏实现。
    说明: 如果编译器支持inline,可以采用inline函数。否则可以采用宏。

    8 注释

    原则8.1 优秀的代码可以自我解释,不通过注释即可轻易读懂。
    说明:优秀的代码不写注释也可轻易读懂,注释无法把糟糕的代码变好,需要很多注释来解释的代码往往存在坏味道,需要重构。

    原则8.2 注释的内容要清楚、明了,含义准确,防止注释二义性。
    说明:有歧义的注释反而会导致维护者更难看懂代码,正如带两块表反而不知道准确时间。

    原则8.3 在代码的功能、意图层次上进行注释,即注释解释代码难以直接表达的意图,而不是重复描述代码。
    说明:注释的目的是解释代码的目的、功能和采用的方法,提供代码以外的信息,帮助读者理解代码,防止没必要的重复注释信息。

    规则8.1 修改代码时,维护代码周边的所有注释,以保证注释与代码的一致性。不再有用的注释要删除。
    说明:不要将无用的代码留在注释中,随时可以从源代码配置库中找回代码;即使只是想暂时排除代码,也要留个标注,不然可能会忘记处理它。

    规则8.2 文件头部应进行注释,注释必须列出:版权说明、版本号、生成日期、作者姓名、工号、内容、功能说明、与其它文件的关系、修改日志等,头文件的注释中还应有函数功能简要说明。
    说明:通常头文件要对功能和用法作简单说明,源文件包含了更多的实现细节或算法讨论。

    规则8.3 函数声明处注释描述函数功能、性能及用法,包括输入和输出参数、函数返回值、可重入的要求等;定义处详细描述函数功能和实现要点,如实现的简要步骤、实现的理由、设计约束等。
    说明:重要的、复杂的函数,提供外部使用的接口函数应编写详细的注释。

    规则8.4 全局变量要有较详细的注释,包括对其功能、取值范围以及存取时注意事项等的说明。

    规则8.5 注释应放在其代码上方相邻位置或右方,不可放在下面。如放于上方则需与其上面的代码用空行隔开,且与下方代码缩进相同。

    规则8.6 对于switch语句下的case语句,如果因为特殊情况需要处理完一个case后进入下一个case处理,必须在该case语句处理完、下一个case语句前加上明确的注释。
    说明:这样比较清楚程序编写者的意图,有效防止无故遗漏break语句。

    规则8.7 避免在注释中使用缩写,除非是业界通用或子系统内标准化的缩写。

    规则8.8 同一产品或项目组统一注释风格。

    建议8.1 避免在一行代码或表达式的中间插入注释。
    说明:除非必要,不应在代码或表达中间插入注释,否则容易使代码可理解性变差。

    建议8.2 注释应考虑程序易读及外观排版的因素,使用的语言若是中、英兼有的,建议多使用中文,除非能用非常流利准确的英文表达。对于有外籍员工的,由产品确定注释语言。
    说明:注释语言不统一,影响程序易读性和外观排版,出于对维护人员的考虑,建议使用中文。

    建议8.3 文件头、函数头、全局常量变量、类型定义的注释格式采用工具可识别的格式。
    说明:采用工具可识别的注释格式,例如doxygen格式,方便工具导出注释形成帮助文档。

    9 排版与格式

    规则9.1 程序块采用缩进风格编写,每级缩进为4个空格。
    说明:当前各种编辑器/IDE都支持TAB键自动转空格输入,需要打开相关功能并设置相关功能。

    规则9.2 相对独立的程序块之间、变量说明之后必须加空行。

    规则9.3 一条语句不能过长,如不能拆分需要分行写。一行到底多少字符换行比较合适,产品可以自行确定。
    说明:对于目前大多数的PC来说,132比较合适(80/132是VTY常见的行宽值);对于新PC宽屏显示器较多的产品来说,可以设置更大的值。

    规则9.4 多个短语句(包括赋值语句)不允许写在同一行内,即一行只写一条语句。

    规则9.5 if、for、do、while、case、switch、default等语句独占一行。
    说明:执行语句必须用缩进风格写,属于if、for、do、while、case、switch、default等下一个缩进级别;

    规则9.6 在两个以上的关键字、变量、常量进行对等操作时,它们之间的操作符之前、之后或者前后要加空格;进行非对等操作时,如果是关系密切的立即操作符(如->),后不应加空格。
    说明:采用这种松散方式编写代码的目的是使代码更加清晰。

    建议9.1 注释符(包括„/‟„//‟„/‟)与注释内容之间要用一个空格进行分隔。
    说明:这样可以使注释的内容部分更清晰。

    建议9.2 源程序中关系较为紧密的代码应尽可能相邻。

    10 表达式

    规则10.1 表达式的值在标准所允许的任何运算次序下都应该是相同的。
    说明:除了少数操作符(函数调用操作符 ( )、&&、| |、? : 和 , (逗号)) 之外,子表达式所依据的运算次序是未指定的并会随时更改。注意,运算次序的问题不能使用括号来解决,因为这不是优先级的问题。

    建议10.1 函数调用不要作为另一个函数的参数使用,否则对于代码的调试、阅读都不利。
    说明:如下代码不合理,仅用于说明当函数作为参数时,由于参数压栈次数不是代码可以控制的,可能造成未知的输出。

    建议10.2 赋值语句不要写在if等语句中,或者作为函数的参数使用。
    说明:因为if语句中,会根据条件依次判断,如果前一个条件已经可以判定整个条件,则后续条件语句不会再运行,所以可能导致期望的部分赋值没有得到运行。

    建议10.3 用括号明确表达式的操作顺序,避免过分依赖默认优先级。
    说明:使用括号强调所使用的操作符,防止因默认的优先级与设计思想不符而导致程序出错;同时使得代码更为清晰可读,然而过多的括号会分散代码使其降低了可读性。下面是如何使用括号的建议。

    建议10.4 赋值操作符不能使用在产生布尔值的表达式上。
    说明:如果布尔值表达式需要赋值操作,那么赋值操作必须在操作数之外分别进行。这可以帮助避免=和= =的混淆,帮助我们静态地检查错误。

    11 代码编辑、编译

    规则11.1 使用编译器的最高告警级别,理解所有的告警,通过修改代码而不是降低告警级别来消除所有告警。
    说明:编译器是你的朋友,如果它发出某个告警,这经常说明你的代码中存在潜在的问题。

    规则11.2 在产品软件(项目组)中,要统一编译开关、静态检查选项以及相应告警清除策略。
    说明:如果必须禁用某个告警,应尽可能单独局部禁用,并且编写一个清晰的注释,说明为什么屏蔽。

    规则11.3 本地构建工具(如PC-Lint)的配置应该和持续集成的一致。
    说明:两者一致,避免经过本地构建的代码在持续集成上构建失败。

    规则11.4 使用版本控制(配置管理)系统,及时签入通过本地构建的代码,确保签入的代码不会影响构建成功。
    说明:及时签入代码降低集成难度。

    建议11.1 要小心地使用编辑器提供的块拷贝功能编程。

    12 可测性

    原则12.1 模块划分清晰,接口明确,耦合性小,有明确输入和输出,否则单元测试实施困难。
    说明:单元测试实施依赖于:
     模块间的接口定义清楚、完整、稳定;
     模块功能的有明确的验收条件(包括:预置条件、输入和预期结果);
     模块内部的关键状态和关键数据可以查询,可以修改;
     模块原子功能的入口唯一;
     模块原子功能的出口唯一;
     依赖集中处理:和模块相关的全局变量尽量的少,或者采用某种封装形式。

    规则12.1 在同一项目组或产品组内,要有一套统一的为集成测试与系统联调准备的调测开关及相应打印函数,并且要有详细的说明。
    说明:本规则是针对项目组或产品组的。代码至始至终只有一份代码,不存在开发版本和测试版本的说法。测试与最终发行的版本是通过编译开关的不同来实现的。并且编译开关要规范统一。统一使用编译开关来实现测试版本与发行版本的区别,一般不允许再定义其它新的编译开关。

    规则12.2 在同一项目组或产品组内,调测打印的日志要有统一的规定。
    说明:统一的调测日志记录便于集成测试,具体包括:
     统一的日志分类以及日志级别;
     通过命令行、网管等方式可以配置和改变日志输出的内容和格式;
     在关键分支要记录日志,日志建议不要记录在原子函数中,否则难以定位;
     调试日志记录的内容需要包括文件名/模块名、代码行号、函数名、被调用函数名、错误码、错误发生的环境等。

    规则12.3 使用断言记录内部假设。
    说明:断言是对某种内部模块的假设条件进行检查,如果假设不成立,说明存在编程、设计错误。断言可以对在系统中隐藏很深,用其它手段极难发现的问题进行定位,从而缩短软件问题定位时间,提高系统的可测性。

    规则12.4 不能用断言来检查运行时错误。
    说明:断言是用来处理内部编程或设计是否符合假设;不能处理对于可能会发生的且必须处理的情况要写防错程序,而不是断言。如某模块收到其它模块或链路上的消息后,要对消息的合理性进行检查,此过程为正常的错误检查,不能用断言来实现。

    13 安全性

    原则13.1 对用户输入进行检查。
    说明:不能假定用户输入都是合法的,因为难以保证不存在恶意用户,即使是合法用户也可能由于误用误操作而产生非法输入。用户输入通常需要经过检验以保证安全。

    规则13.1 确保所有字符串是以NULL结束。
    说明:C语言中‟\0‟作为字符串的结束符,即NULL结束符。标准字符串处理函数(如strcpy()、strlen())依赖NULL结束符来确定字符串的长度。没有正确使用NULL结束字符串会导致缓冲区溢出和其它未定义的行为。

    规则13.2 不要将边界不明确的字符串写到固定长度的数组中。
    说明:边界不明确的字符串(如来自gets()、getenv()、scanf()的字符串),长度可能大于目标数组长度,直接拷贝到固定长度的数组中容易导致缓冲区溢出。

    规则13.3 避免整数溢出。
    说明:当一个整数被增加超过其最大值时会发生整数上溢,被减小小于其最小值时会发生整数下溢。带符号和无符号的数都有可能发生溢出。

    规则13.4 避免符号错误。
    说明:有时从带符号整型转换到无符号整型会发生符号错误,符号错误并不丢失数据,但数据失去了原来的含义

    规则13.5:避免截断错误。
    说明:将一个较大整型转换为较小整型,并且该数的原值超出较小类型的表示范围,就会发生截断错误,原值的低位被保留而高位被丢弃。截断错误会引起数据丢失。

    规则13.6:确保格式字符和参数匹配。
    说明:使用格式化字符串应该小心,确保格式字符和参数之间的匹配,保留数量和数据类型。格式字符和参数之间的不匹配会导致未定义的行为。大多数情况下,不正确的格式化字符串会导致程序异常终止。

    规则13.7 避免将用户输入作为格式化字符串的一部分或者全部。
    说明:调用格式化I/O函数时,不要直接或者间接将用户输入作为格式化字符串的一部分或者全部。攻击者对一个格式化字符串拥有部分或完全控制,存在以下风险:进程崩溃、查看栈的内容、改写内存、甚至执行任意代码。

    规则13.8 避免使用strlen()计算二进制数据的长度。
    说明:strlen()函数用于计算字符串的长度,它返回字符串中第一个NULL结束符之前的字符的数量。因此用strlen()处理文件I/O函数读取的内容时要小心,因为这些内容可能是二进制也可能是文本。

    规则13.9 使用int类型变量来接受字符I/O函数的返回值。
    说明:字符I/O函数fgetc()、getc()和getchar()都从一个流读取一个字符,并把它以int值的形式返回。如果这个流到达了文件尾或者发生读取错误,函数返回EOF。fputc()、putc()、putchar()和ungetc()也返回一个字符或EOF。如果这些I/O函数的返回值需要与EOF进行比较,不要将返回值转换为char类型。因为char是有符号8位的值,int是32位的值。如果getchar()返回的字符的ASCII值为0xFF,转换为char类型后将被解释为EOF。因为这个值被有符号扩展为0xFFFFFFFF(EOF的值)执行比较。

    规则13.10 防止命令注入。
    说明:C99函数system()通过调用一个系统定义的命令解析器(如UNIX的shell,Windows的CMD.exe)来执行一个指定的程序/命令。类似的还有POSIX的函数popen()。如果system()的参数由用户的输入组成,恶意用户可以通过构造恶意输入,改变system()调用的行为。

    14 单元测试

    规则14.1 在编写代码的同时,或者编写代码前,编写单元测试用例验证软件设计/编码的正确。

    建议14.1 单元测试关注单元的行为而不是实现,避免针对函数的测试。
    说明:应该将被测单元看做一个被测的整体,根据实际资源、进度和质量风险,权衡代码覆盖、打桩工作量、补充测试用例的难度、被测对象的稳定程度等,一般情况下建议关注模块/组件的测试,尽量避免针对函数的测试。尽管有时候单个用例只能专注于对某个具体函数的测试,但我们关注的应该是函数的行为而不是其具体实现细节。

    15 可移植性

    规则15.1 不能定义、重定义或取消定义标准库/平台中保留的标识符、宏和函数。

    建议15.1 不使用与硬件或操作系统关系很大的语句,而使用建议的标准语句,以提高软件的可移植性和可重用性。
    说明:使用标准的数据类型,有利于程序的移植。

    推荐阅读材料:
    C语言接口与实现
    敏捷软件开发
    函数式语言的优势
    抽象漏洞原则
    代码大全
    doxygen中文手册
    代码整洁之道
    google c++编程规范
    汽车业c语言使用规范(misra)

    2018.1.27

    更多相关内容
  • 华为C&C 语言安全编程规范_V3.1,华为c语言通用编程规范v3.2,C,C++源码.rar,华为C&C 语言安全编程规范_V3.1.pdf
  • 华为C&C 语言安全编程规范_V3.1,华为c语言通用编程规范v3.2,C,C++源码.zip
  • 华为C编程规范原文详情:link. 一、代码总体原则 1、清晰第一 **清晰性是易于维护、易于重构的程序必需具备的特征。** ”程序必须为阅读它的人而编写,只是顺便用于机器执行“。 ”编写程序应该以人为本,计算机第...

    华为C编程规范原文详情:link.

    一、代码总体原则

    1、清晰第一

    **清晰性是易于维护、易于重构的程序必需具备的特征。**
    	”程序必须为阅读它的人而编写,只是顺便用于机器执行“。
    	”编写程序应该以人为本,计算机第二“。
    一般情况下,代码的可阅读性高于性能,只有确定性能是瓶颈时,才应该主动优化。
    

    2、简洁为美

    **简洁就是易于理解并且易于实现。**
    	代码越长越难以看懂,也就越容易在修改时引入错误。
    	废弃的代码(没有被调用的函数和全局变量)要及时清除,重复代码应该尽可能提炼成函数。
    

    3、选择合适的风格,与代码原有风格保持一致

    **如果重构/修改其他风格的代码时,比较明智的做法是根据现有代码的现有风格继续编写代码。**
    

    二、术语定义

    **原则:**编程时必须坚持的指导思想。
    **规则:**编程时强制必须遵守的约定。
    **建议:**编程时必须加以考虑的约定。
    **说明:**对此原则/规则/建议进行必要的解释。
    **示例:**对此原则/规则/建议从正反两个方面给出例子。
    **延伸阅读材料:**建议进一步参考材料。
    

    三、具体规范介绍

    1、头文件

    对于C语言来说,头文件的设计体现了大部分的系统设计。不合理的头文件布局是编译时间过长的根因,不合理的头文件实际上是不合理的设计。
    	在一个设计良好的系统中,修改一个文件,只需要重新编译数个,甚至是一个文件。
    	我们应该倾向于减少包含头文件,尤其是在头文件中包含头文件,以控制改动代码后的编译时间。
    

    原则1.1 头文件中适合放置接口的声明,不适合放置实现。

    **说明:**头文件是模块(Module)或单元(Unit)的对外接口。头文件中应放置对外部的声明,如对外提供的函数声明、宏定义、类型定义等。内部使用的函数(相当于类的私有方法)声明不应放在头文件中。 内部使用的宏、枚举、结构定义不应放入头文件中。 变量定义不应放在头文件中,应放在.c文件中。变量的声明尽量不要放在头文件中,亦即尽量不要使用全局变量作为接口。
    

    原则1.2 头文件应当职责单一

    **说明:**头文件过于复杂,依赖过于复杂是导致编译时间过长的主要原因。很多现有代码中头文件过大, 职责过多,再加上循环依赖的问题,可能导致为了在.c中使用一个宏,而包含十几个头文件。
    

    原则1.3 头文件应向稳定的方向包含

    **说明:**头文件的包含关系是一种依赖,一般来说,应当让不稳定的模块依赖稳定的模块,从而当不稳定的模块发生变化时,不会影响(编译)稳定的模块。
    依赖的方向应该是:产品依赖于平台,平台依赖于标准库。
    除了不稳定的模块依赖于稳定的模块外,更好的方式是两个模块共同依赖于接口。
    

    规则1.1 每一个.c文件应有一个同名.h文件,用于声明需要对外公开的接口。

    **说明:**如果一个.c文件不需要对外公布任何接口,则其就不应当存在,除非它是程序的入口,如main 函数所在的文件。
    

    规则1.2 禁止头文件循环依赖。

    **说明:**头文件循环依赖,指a.h包含b.h,b.h包含c.h,c.h包含a.h之类导致任何一个头文件修改,都导致所有包含了a.h/b.h/c.h的代码全部重新编译一遍。而如果是单向依赖,如a.h包含b.h,b.h包含 c.h,而c.h不包含任何头文件,则修改a.h不会导致包含了b.h/c.h的源代码重新编译。
    

    规则1.3 .c/.h文件禁止包含用不到的头文件。

    规则1.4 头文件应当自包含。

    **说明:**简单的说,自包含就是任意一个头文件均可独立编译。如果一个文件包含某个头文件,还要包含另外一个头文件才能工作的话,就会增加交流障碍,给这个头文件的用户增添不必要的负担。
    

    规则1.5 总是编写内部#include保护符(#define 保护)。

    所有头文件都应当使用#define 防止头文件被多重包含,命名格式为FILENAME_H,为了保证唯一性,更好的命名是PROJECTNAME_PATH_FILENAME_H。 
    **注:**没有在宏最前面加上“_",即使用FILENAME_H代替_FILENAME_H_,是因为一般以"_"和”__"开头的标识符为系统保留或者标准库使用,在有些静态检查工具中,若全局可见的标识符以"_"开头会给出告警。
    定义包含保护符时,应该遵守如下规则: 1)保护符使用唯一名称; 2)不要在受保护部分的前后放置代码或者注释。
    例外情况:头文件的版权声明部分以及头文件的整体注释部分(如阐述此头文件的开发背景、使用注意事项等)可以放在保护符(#ifndef XX_H)前面。
    
    示例:假定VOS工程的timer模块的timer.h,其目录为VOS/include/timer/timer.h,应按如下方式保护: 
    #ifndef VOS_INCLUDE_TIMER_TIMER_H  
    #define VOS_INCLUDE_TIMER_TIMER_H  
    ...  
    #endif 
    也可以使用如下简单方式保护: 
    #ifndef TIMER_H  
    #define TIMER_H 
    ..  
    #endif 
    

    规则1.6 禁止在头文件中定义变量。

    规则1.7 只能通过包含头文件的方式使用其他.c提供的接口,禁止在.c中通过extern的方式使用外部函数接口、变量。

     **说明:**若a.c使用了b.c定义的foo()函数,则应当在b.h中声明extern int foo(int input);并在a.c中通过#include <b.h>来使用foo。禁止通过在a.c中直接写extern int foo(int input);来使用foo,后面这种写法容易在foo改变时可能导致声明和定义不一致。
    

    规则1.8 禁止在extern "C"中包含头文件。

    **说明:**在extern "C"中包含头文件,会导致extern "C"嵌套,Visual Studio对extern "C"嵌套层次有限制,嵌套层次太多会编译错误。 在extern "C"中包含头文件,可能会导致被包含头文件的原有意图遭到破坏。例如,存在a.h和b.h两个头文件。
    

    建议1.1 一个模块通常包含多个.c文件,建议放在同一个目录下,目录名即为模块名。为方便外部使 用者,建议每一个模块提供一个.h,文件名为目录名。

    建议1.2 如果一个模块包含多个子模块,则建议每一个子模块提供一个对外的.h,文件名为子模块名。

    **说明:**降低接口使用者的编写难度。

    建议1.3 头文件不要使用非习惯用法的扩展名,如.inc。

    **说明:**目前很多产品中使用了.inc作为头文件扩展名,这不符合c语言的习惯用法。
    

    建议1.4 同一产品统一包含头文件排列方式。

    **说明:**常见的包含头文件排列方式:功能块排序、文件名升序、稳定度排序。
    
    示例1:
    以升序方式排列头文件可以避免头文件被重复包含,如:
    #include <a.h>
    #include <b.h>
    #include <c/d.h>
    #include <c/e.h>
    #include <f.h>
    示例2:
    以稳定度排序,建议将不稳定的头文件放在前面,如把产品的头文件放在平台的头文件前面,如下:
    #include <product.h>
    #include <platform.h>
    

    2、函数

    函数设计的精髓:编写整洁函数,同时把代码有效组织起来。
    

    原则2.1 一个函数仅完成一件功能。

    **说明:**一个函数实现多个功能给开发、使用、维护都带来很大的困难。
    

    原则2.2 重复代码应该尽可能提炼成函数。

    **说明:**重复代码提炼成函数可以带来维护成本的降低。
    

    规则2.1 避免函数过长,新增函数不超过50行(非空非注释行)。

    **说明:**本规则仅对新增函数做要求,对已有函数修改时,建议不增加代码行。过长的函数往往意味着函数功能不单一,过于复杂。
    函数的有效代码行数,即NBNC(非空非注释行)应当在[1,50]区间。
    例外:某些实现算法的函数,由于算法的聚合性与功能的全面性,可能会超过50行。
    

    规则2.2 避免函数的代码块嵌套过深,新增函数的代码块嵌套不超过4层。

    说明:本规则仅对新增函数做要求,对已有的代码建议不增加嵌套层次。优秀代码参考值:[1, 4]。
    

    规则2.3 可重入函数应避免使用共享变量;若需要使用,则应通过互斥手段(关中断、信号量)对其加以保护。

    说明:可重入函数是指可能被多个任务并发调用的函数。在多任务操作系统中,函数具有可重入性是多个任务可以共用此函数的必要条件。共享变量指的全局变量和static变量。 
    

    规则2.4 对参数的合法性检查,由调用者负责还是由接口函数负责,应在项目组/模块内统一规定。 缺省由调用者负责。

    规则2.5 对函数的错误返回码要全面处理。

    规则2.6 设计高扇入,合理扇出(小于7)的函数。

    说明:扇出是指一个函数直接调用(控制)其它函数的数目,而扇入是指有多少上级函数调用它。
    

    规则2.7 废弃代码(没有被调用的函数和变量)要及时清除。

    说明:程序中的废弃代码不仅占用额外的空间,而且还常常影响程序的功能与性能,很可能给程序的 测试、维护等造成不必要的麻烦。
    

    建议2.1 函数不变参数使用const。

    说明:不变的值更易于理解/跟踪和分析,把const作为默认选项,在编译时会对其进行检查,使代码 更牢固/更安全。
    

    建议2.2 函数应避免使用全局变量、静态局部变量和I/O操作,不可避免的地方应集中使用。

    建议2.3 检查函数所有非参数输入的有效性,如数据文件、公共变量等。

    说明:函数的输入主要有两种:一种是参数输入;另一种是全局变量、数据文件的输入,即非参数输 入。函数在使用输入参数之前,应进行有效性检查。
    

    建议2.4 函数的参数个数不超过5个。

    说明:函数的参数过多,会使得该函数易于受外部(其他部分的代码)变化的影响,从而影响维护工作。函数的参数过多同时也会增大测试的工作量。
    函数的参数个数不要超过5个,如果超过了建议拆分为不同函数。
    

    建议2.5 除打印类函数外,不要使用可变长参函数。

    说明:可变长参函数的处理过程比较复杂容易引入错误,而且性能也比较低,使用过多的可变长参函 数将导致函数的维护难度大大增加。
    

    建议2.6 在源文件范围内声明和定义的所有函数,除非外部可见,否则应该增加static关键字。

    说明:如果一个函数只是在同一文件中的其他地方调用,那么就用static声明。使用static确保只是在声明它的文件中是可见的,并且避免了和其他文件或库中的相同标识符发生混淆的可能性。 建议定义一个STATIC宏,在调试阶段,将STATIC定义为static,版本发布时,改为空,以便于后续的打热补丁等操作。
    
    #ifdef _DEBUG
    #define STATIC static
    #else
    #define STATIC
    #endif
    

    3、标识符命名与定义

    目前比较使用的如下几种命名风格:
    	**unix like风格:**单词用小写字母,每个单词直接用下划线„_‟分割,例如text_mutex,kernel_text_address。
    	**Windows风格:**大小写字母混用,单词连在一起,每个单词首字母大写。不过Windows风格如果遇到大写专有用语时会有些别扭,例如命名一个读取RFC文本的函数,命令为ReadRFCText,看起来就没有unix like的read_rfc_text清晰了。
    	**匈牙利命名法:**匈牙利命名主要包括三个部分:基本类型、一个或更多的前缀、一个限定词。匈牙利命名法存在较多的争议。
    对标识符定 义主要是为了让团队的代码看起来尽可能统一,有利于代码的后续阅读和修改,产品可以根据自己的实际需要指定命名风格。
    

    原则3.1 标识符的命名要清晰、明了,有明确含义,同时使用完整的单词或大家基本可以理解的缩写,避免使人产生误解。

    说明:尽可能给出描述性名称,不要节约空间,让别人很快理解你的代码更重要。
    
    示例:好的命名:
      int error_number; 
      int number_of_completed_connection; 
    不好的命名:使用模糊的缩写或随意的字符:
      int n; 
      int nerr; 
      int n_comp_conns;
    

    原则3.2 除了常见的通用缩写以外,不使用单词缩写,不得使用汉语拼音。

    规则3.1 产品/项目组内部应保持统一的命名风格。

    建议3.1 用正确的反义词组命名具有互斥意义的变量或相反动作的函数等。

    建议3.2 尽量避免名字中出现数字编号,除非逻辑上的确需要编号。

    建议3.3 标识符前不应添加模块、项目、产品、部门的名称作为前缀。

    **说明:**很多已有代码中已经习惯在文件名中增加模块名,这种写法类似匈牙利命名法,导致文件名不可读,并且带来带来如下问题: 
    

    • 第一眼看到的是模块名,而不是真正的文件功能,阻碍阅读;
    • 文件名太长;
    • 文件名和模块绑定,不利于维护和移植。若foo.c进行重构后,从a模块挪到b模块,若foo.c中有模块名,则需要将文件名从a_module_foo.c改为b_module_foo.c

    建议3.4 平台/驱动等适配代码的标识符命名风格保持和平台/驱动一致。

    说明:涉及到外购芯片以及配套的驱动,这部分的代码变动(包括为产品做适配的新增代码),应该保持原有的风格。
    

    建议3.5 重构/修改部分代码时,应保持和原有代码的命名风格一致。

    3.2 文件命名规则

    建议3.6 文件命名统一采用小写字符。

    3.3 变量命名规则

    规则3.2 全局变量应增加“g_”前缀。

    规则3.3 静态变量应增加“s_”前缀。

    说明:增加g_前缀或者s_前缀,原因如下:首先,全局变量十分危险,通过前缀使得全局变量更加醒目,促使开发人员对这些变量的使用更加小 心。 其次,从根本上说,应当尽量不使用全局变量,增加g_和s_前缀,会使得全局变量的名字显得很丑陋,从而促使开发人员尽量少使用全局变量。
    

    规则3.4 禁止使用单字节命名变量,但允许定义i、j、k作为局部循环变量。

    建议3.7 不建议使用匈牙利命名法。

    建议3.8 使用名词或者形容词+名词方式命名变量。

    3.4 函数命名规则

    建议3.9 函数命名应以函数要执行的动作命名,一般采用动词或者动词+名词的结构。

    示例:找到当前进程的当前目录
      DWORD GetCurrentDirectory( DWORD BufferLength, LPTSTR Buffer );
    

    建议3.10 函数指针除了前缀,其他按照函数的命名规则命名。

    3.5 宏的命名规则

    规则3.5 对于数值或者字符串等等常量的定义,建议采用全大写字母,单词之间加下划线„_‟的方式命 名(枚举同样建议使用此方式定义)。

    规则3.6 除了头文件或编译开关等特殊标识定义,宏定义不能使用下划线„_‟开头和结尾。

    4、变量

    原则4.1 一个变量只有一个功能,不能把一个变量用作多种用途。

    原则4.2 结构功能单一;不要设计面面俱到的数据结构。

    说明:相关的一组信息才是构成一个结构体的基础,结构的定义应该可以明确的描述一个对象,而不是一组相关性不强的数据的集合。	
    

    原则4.3 不用或者少用全局变量。

    规则4.1 防止局部变量与全局变量同名。

    规则4.2 通讯过程中使用的结构,必须注意字节序。

    规则4.3 严禁使用未经初始化的变量作为右值。

    建议4.1 构造仅有一个模块或函数可以修改、创建,而其余有关模块或函数只访问的全局变量,防止多个不同模块或函数都可以修改、创建同一全局变量的现象。

    说明:降低全局变量耦合度。
    

    建议4.2 使用面向接口编程思想,通过API访问数据:如果本模块的数据需要对外部模块开放,应提供接口函数来设置、获取,同时注意全局数据的访问互斥。

    建议4.3 在首次使用前初始化变量,初始化的地方离使用的地方越近越好。

    建议4.4 明确全局变量的初始化顺序,避免跨模块的初始化依赖。

    说明:系统启动阶段,使用全局变量前,要考虑到该全局变量在什么时候初始化,使用全局变量和初 始化全局变量,两者之间的时序关系,谁先谁后,一定要分析清楚,不然后果往往是低级而又灾难性的。
    

    建议4.5 尽量减少没有必要的数据类型默认转换与强制转换。

    5、宏、常量

    规则5.1 用宏定义表达式时,要使用完备的括号。

    说明:因为宏只是简单的代码替换,不会像函数一样先将参数计算后,再传递。
    
    示例:如下定义的宏都存在一定的风险
    #define RECTANGLE_AREA(a, b) a * b
    #define RECTANGLE_AREA(a, b) (a * b)
    #define RECTANGLE_AREA(a, b) (a) * (b)
    正确的定义应为:
    #define RECTANGLE_AREA(a, b) ((a) * (b))
    

    规则5.2 将宏所定义的多条表达式放在大括号中。

    说明:更好的方法是多条语句写成do while(0)的方式。
    
    必须这样定义才能避免各种问题:
    #define FOO(x) do { \
    	printf("arg is %s\n", x); \
    	do_something_useful(x); \
    } while(0)do-while(0)方式定义宏,完全不用担心使用者如何使用宏,也不用给使用者加什么约束。
    

    规则5.3 使用宏时,不允许参数发生变化。

    规则5.4 不允许直接使用魔鬼数字。

    **说明:**使用魔鬼数字的弊端:代码难以理解;如果一个有含义的数字多处使用,一旦需要修改这个数值,代价惨重。
    **解决途径:** 对于局部使用的唯一含义的魔鬼数字,可以在代码周围增加说明注释,也可以定义局部const变量,变量命名自注释。 
    对于广泛使用的数字,必须定义const全局变量/宏;同样变量/宏命名应是自注释的。
    

    建议5.1 除非必要,应尽可能使用函数代替宏。

    说明:宏对比函数,有一些明显的缺点: 
    1) 宏缺乏类型检查,不如函数调用检查严格。
    2) 宏展开可能会产生意想不到的副作用,如#define SQUARE(a) (a) * (a)这样的定义,如果是SQUARE(i++),就会导致i被加两次;如果是函数调用double square(double a) {return a * a;}则不会有此副作用。 
    3) 以宏形式写的代码难以调试难以打断点,不利于定位问题。 
    4) 宏如果调用的很多,会造成代码空间的浪费,不如函数空间效率高。
    

    建议5.2 常量建议使用const定义代替宏。

    说明:“尽量用编译器而不用预处理”,因为#define经常被认为好象不是语言本身的一部分。
    

    建议5.3 宏定义中尽量不使用return、goto、continue、break等改变程序流程的语句。

    6、质量保证

    原则6.1 代码质量保证优先原则

    (1)正确性,指程序要实现设计要求的功能。 
    (2)简洁性,指程序易于理解并且易于实现。 
    (3)可维护性,指程序被修改的能力,包括纠错、改进、新需求或功能规格变化的适应能力。 
    (4)可靠性,指程序在给定时间间隔和环境条件下,按设计要求成功运行程序的概率。 
    (5)代码可测试性,指软件发现故障并隔离、定位故障的能力,以及在一定的时间和成本前提下,进行测试设计、测试执行的能力。 
    (6)代码性能高效,指是尽可能少地占用系统资源,包括内存和执行时间。 
    (7)可移植性,指为了在原来设计的特定环境之外运行,对系统进行修改的能力。 
    (8)个人表达方式/个人方便性,指个人编程习惯。
    

    原则6.2 要时刻注意易混淆的操作符。

    原则6.3 必须了解编译系统的内存分配方式,特别是编译系统对不同类型的变量的内存分配规则,如局部变量在何处分配、静态变量在何处分配等。

    原则6.4 不仅关注接口,同样要关注实现。

    规则6.1 禁止内存操作越界。

    规则6.2 禁止内存泄漏。

    规则6.3 禁止引用已经释放的内存空间。

    规则6.4 编程时,要防止差1错误。

    说明:此类错误一般是由于把“<=”误写成“<”或“>=”误写成“>”等造成的,由此引起的后果,很多情况下是很严重的,所以编程时,一定要在这些地方小心。当编完程序后,应对这些操作符进行彻底检查。使用变量时要注意其边界值的情况。
    

    规则6.5 所有的if … else if结构应该由else子句结束 ;switch语句必须有default分支。

    建议6.1 函数中分配的内存,在函数退出之前要释放。

    建议6.2 if语句尽量加上else分支,对没有else分支的语句要小心对待。

    建议6.3 不要滥用goto语句。

    说明:goto语句会破坏程序的结构性,所以除非确实需要,最好不使用goto语句。
    

    建议6.4 时刻注意表达式是否会上溢、下溢。

    示例:如下程序将造成变量下溢。
    unsigned char size ;while (size-- >= 0) // 将出现下溢
    {
     ... // program code
    } 当size等于0时,再减不会小于0,而是0xFF,故程序是一个死循环。应如下修改。
    char size; // 从unsigned char 改为charwhile (size-- >= 0)
    {
     ... // program code
    }
    

    7、程序效率

    示例:如下程序将造成变量下溢。
    unsigned char size ;while (size-- >= 0) // 将出现下溢
    {
     ... // program code
    } 当size等于0时,再减不会小于0,而是0xFF,故程序是一个死循环。应如下修改。
    char size; // 从unsigned char 改为charwhile (size-- >= 0)
    {
     ... // program code 
    }
    

    原则7.1 在保证软件系统的正确性、简洁、可维护性、可靠性及可测性的前提下,提高代码效率。

    本章节后面所有的规则和建议,都应在不影响前述可读性等质量属性的前提下实施。 
    说明:不能一味地追求代码效率,而对软件的正确、简洁、可维护性、可靠性及可测性造成影响。
    

    原则7.2 通过对数据结构、程序算法的优化来提高效率。

    建议7.1 将不变条件的计算移到循环体外。

    建议7.2 对于多维大数组,避免来回跳跃式访问数组成员。

    建议7.3 创建资源库,以减少分配对象的开销。

    说明:例如,使用线程池机制,避免线程频繁创建、销毁的系统调用;使用内存池,对于频繁申请、释放的小块内存,一次性申请一个大块的内存,当系统申请内存时,从内存池获取小块内存,使用完毕再释放到内存池中,避免内存申请释放的频繁系统调用
    

    建议7.4 将多次被调用的 “小函数”改为inline函数或者宏实现。

    说明: 如果编译器支持inline,可以采用inline函数。否则可以采用宏。在做这种优化的时候一定要注意下面inline函数的优点:其一编译时不用展开,代码SIZE小。其二可以加断点,易于定位问题,例如对于引用计数加减的时候。其三函数编译时,编译器会做语法检查。
    

    8、注释

    原则8.1 优秀的代码可以自我解释,不通过注释即可轻易读懂。

    说明:优秀的代码不写注释也可轻易读懂,注释无法把糟糕的代码变好,需要很多注释来解释的代码 往往存在坏味道,需要重构。
    

    原则8.2 注释的内容要清楚、明了,含义准确,防止注释二义性。

    原则8.3 在代码的功能、意图层次上进行注释,即注释解释代码难以直接表达的意图,而不是重复描 述代码。

    说明:注释的目的是解释代码的目的、功能和采用的方法,提供代码以外的信息,帮助读者理解代码, 防止没必要的重复注释信息。
    

    规则8.1 修改代码时,维护代码周边的所有注释,以保证注释与代码的一致性。不再有用的注释要删除。

    规则8.2 文件头部应进行注释,注释必须列出:版权说明、版本号、生成日期、作者姓名、工号、内容、功能说明、与其它文件的关系、修改日志等,头文件的注释中还应有函数功能简要说明。

    说明:通常头文件要对功能和用法作简单说明,源文件包含了更多的实现细节或算法讨论。
    

    规则8.3 函数声明处注释描述函数功能、性能及用法,包括输入和输出参数、函数返回值、可重入的要求等;定义处详细描述函数功能和实现要点,如实现的简要步骤、实现的理由、设计约束等。

    说明:重要的、复杂的函数,提供外部使用的接口函数应编写详细的注释。
    

    规则8.4 全局变量要有较详细的注释,包括对其功能、取值范围以及存取时注意事项等的说明

    规则8.5 注释应放在其代码上方相邻位置或右方,不可放在下面。如放于上方则需与其上面的代码用空行隔开,且与下方代码缩进相同。

    规则8.6 对于switch语句下的case语句,如果因为特殊情况需要处理完一个case后进入下一个case处 理,必须在该case语句处理完、下一个case语句前加上明确的注释。

    说明:这样比较清楚程序编写者的意图,有效防止无故遗漏break语句。
    

    规则8.7 避免在注释中使用缩写,除非是业界通用或子系统内标准化的缩写。

    规则8.8 同一产品或项目组统一注释风格。

    建议8.1 避免在一行代码或表达式的中间插入注释。

    建议8.2 注释应考虑程序易读及外观排版的因素,使用的语言若是中、英兼有的,建议多使用中文, 除非能用非常流利准确的英文表达。对于有外籍员工的,由产品确定注释语言。

    建议8.3 文件头、函数头、全局常量变量、类型定义的注释格式采用工具可识别的格式。

    说明:采用工具可识别的注释格式,例如doxygen格式,方便工具导出注释形成帮助文档。
    

    9、排版与格式

    规则9.1 程序块采用缩进风格编写,每级缩进为4个空格。

    规则9.2 相对独立的程序块之间、变量说明之后必须加空行。

    规则9.3 一条语句不能过长,如不能拆分需要分行写。一行到底多少字符换行比较合适,产品可以自行确定。

    换行时有如下建议: 
    1)换行时要增加一级缩进,使代码可读性更好; 
    2)低优先级操作符处划分新行;换行时操作符应该也放下来,放在新行首; 
    3)换行时建议一个完整的语句放在一行,不要根据字符数断行
    

    规则9.4 多个短语句(包括赋值语句)不允许写在同一行内,即一行只写一条语句。

    规则9.5 if、for、do、while、case、switch、default等语句独占一行。

    规则9.6 在两个以上的关键字、变量、常量进行对等操作时,它们之间的操作符之前、之后或者前后 要加空格;进行非对等操作时,如果是关系密切的立即操作符(如->),后不应加空格。

    说明:采用这种松散方式编写代码的目的是使代码更加清晰。
    

    建议9.1 注释符(包括„/‟„//‟„/‟)与注释内容之间要用一个空格进行分隔。

    建议9.2 源程序中关系较为紧密的代码应尽可能相邻。

    10、表达式

    规则10.1 表达式的值在标准所允许的任何运算次序下都应该是相同的。

    将复合表达式分开写成若干个简单表达式,明确表达式的运算次序,就可以有效消除非预期副作用。
    

    建议10.1 函数调用不要作为另一个函数的参数使用,否则对于代码的调试、阅读都不利。

    说明:如下代码不合理,仅用于说明当函数作为参数时,由于参数压栈次数不是代码可以控制的,可能造成未知的输出,使用断点调试起来也比较麻烦,阅读起来也不舒服,所以不要为了节约代码行,而写这 种代码。
    
    int g_var; 
    int fun1() 
    { 
        g_var += 10; 
        return g_var; 
    } 
    int fun2() 
    { 
        g_var += 100; 
        return g_var; 
    } 
    int main(int argc, char *argv[], char *envp[]) 
    { 
        g_var = 1; 
        printf("func1: %d, func2: %d\n", fun1(), fun2()); 
        g_var = 1; 
        printf("func2: %d, func1: %d\n", fun2(), fun1());
    }
    

    建议10.2 赋值语句不要写在if等语句中,或者作为函数的参数使用。

    建议10.4 赋值操作符不能使用在产生布尔值的表达式上。

    11、代码编辑、编译

    规则11.1 使用编译器的最高告警级别,理解所有的告警,通过修改代码而不是降低告警级别来消除所有告警。

    说明:编译器是你的朋友,如果它发出某个告警,这经常说明你的代码中存在潜在的问题。
    

    规则11.2 在产品软件(项目组)中,要统一编译开关、静态检查选项以及相应告警清除策略。

    说明:如果必须禁用某个告警,应尽可能单独局部禁用,并且编写一个清晰的注释,说明为什么屏蔽。某些语句经编译/静态检查产生告警,但如果你认为它是正确的,那么应通过某种手段去掉告警信息。
    

    规则11.3 本地构建工具(如PC-Lint)的配置应该和持续集成的一致。

    说明:两者一致,避免经过本地构建的代码在持续集成上构建失败。
    

    规则11.4 使用版本控制(配置管理)系统,及时签入通过本地构建的代码,确保签入的代码不会影响构建成功。

    说明:及时签入代码降低集成难度。
    

    建议11.1 要小心地使用编辑器提供的块拷贝功能编程。

    12、可测性

    原则12.1 模块划分清晰,接口明确,耦合性小,有明确输入和输出,否则单元测试实施困难。

    说明:单元测试实施依赖于: 
    1)模块间的接口定义清楚、完整、稳定; 
    2)模块功能的有明确的验收条件(包括:预置条件、输入和预期结果); 
    3)模块内部的关键状态和关键数据可以查询,可以修改; 
    4)模块原子功能的入口唯一; 
    5)模块原子功能的出口唯一; 
    6)依赖集中处理:和模块相关的全局变量尽量的少,或者采用某种封装形式。
    

    规则12.1 在同一项目组或产品组内,要有一套统一的为集成测试与系统联调准备的调测开关及相应打印函数,并且要有详细的说明。

    规则12.2 在同一项目组或产品组内,调测打印的日志要有统一的规定。

    说明:统一的调测日志记录便于集成测试,具体包括: 
    1)统一的日志分类以及日志级别; 
    2)通过命令行、网管等方式可以配置和改变日志输出的内容和格式; 
    3)在关键分支要记录日志,日志建议不要记录在原子函数中,否则难以定位; 
    4)调试日志记录的内容需要包括文件名/模块名、代码行号、函数名、被调用函数名、错误码、错误发生的环境等。
    

    规则12.3 使用断言记录内部假设。

    说明:断言是对某种内部模块的假设条件进行检查,如果假设不成立,说明存在编程、设计错误。断言可以对在系统中隐藏很深,用其它手段极难发现的问题进行定位,从而缩短软件问题定位时间,提高系统的可测性。
    

    规则12.4 不能用断言来检查运行时错误。

    说明:断言是用来处理内部编程或设计是否符合假设;不能处理对于可能会发生的且必须处理的情况要写防错程序,而不是断言。如某模块收到其它模块或链路上的消息后,要对消息的合理性进行检查,此过程为正常的错误检查,不能用断言来实现。 
    断言的使用是有条件的。断言只能用于程序内部逻辑的条件判断,而不能用于对外部输入数据的判断,因为在网上实际运行时,是完全有可能出现外部输入非法数据的情况。
    

    建议12.1 为单元测试和系统故障注入测试准备好方法和通道。

    13、安全性

    原则13.1 对用户输入进行检查。

    13.1 字符串操作安全

    规则13.1 确保所有字符串是以NULL结束。

    规则13.2 不要将边界不明确的字符串写到固定长度的数组中。

    示例:
    char buff[256];
    char *editor = getenv("EDITOR");
    if (editor != NULL) 
    {
        strcpy(buff, editor);
    }
    上述代码读取环境变量"EDITOR"的值,如果成功则拷贝到缓冲区buff中。而从环境变量获取到的字符
    串长度是不确定的,把它们拷贝到固定长度的数组中很可能导致缓冲区溢出。
    正确写法:计算字符串的实际长度,使用malloc分配指定长度的内存
    char *buff;
    char *editor = getenv("EDITOR");
    if (editor != NULL) 
    {
        buff = malloc(strlen(editor) + 1);
        if (buff != NULL) 
        {
            strcpy(buff, editor);
        }
    } 
    

    13.2 整数安全

    规则13.3 避免整数溢出。

    说明:当一个整数被增加超过其最大值时会发生整数上溢,被减小小于其最小值时会发生整数下溢。带符号和无符号的数都有可能发生溢出。
    

    规则13.4 避免符号错误。

    说明:有时从带符号整型转换到无符号整型会发生符号错误,符号错误并不丢失数据,但数据失去了原来的含义。
    

    规则13.5 避免截断错误。

    说明:将一个较大整型转换为较小整型,并且该数的原值超出较小类型的表示范围,就会发生截断错误,原值的低位被保留而高位被丢弃。截断错误会引起数据丢失。
    

    13.3 格式化输出安全

    规则13.6 确保格式字符和参数匹配。
    规则13.7 避免将用户输入作为格式化字符串的一部分或者全部。

    13.4 文件I/O安全

    规则13.8 避免使用strlen()计算二进制数据的长度。
    规则13.9 使用int类型变量来接受字符I/O函数的返回值。
    说明:字符I/O函数fgetc()、getc()和getchar()都从一个流读取一个字符,并把它以int值的形式返回。如果这个流到达了文件尾或者发生读取错误,函数返回EOF。fputc()、putc()、putchar()和ungetc()也返回一个字符或EOF。

    13.5 其它

    规则13.10 防止命令注入。

    14 、单元测试

    规则14.1 在编写代码的同时,或者编写代码前,编写单元测试用例验证软件设计/编码的正确。

    建议14.1 单元测试关注单元的行为而不是实现,避免针对函数的测试。

    15、可移植性

    规则15.1 不能定义、重定义或取消定义标准库/平台中保留的标识符、宏和函数。

    建议15.1 不使用与硬件或操作系统关系很大的语句,而使用建议的标准语句,以提高软件的可移植性和可重用性。

    建议15.2 除非为了满足特殊需求,避免使用嵌入式汇编。

    说明:程序中嵌入式汇编,一般都对可移植性有较大的影响。
    

    16、业界编程规范

    (1)google C++编程指南

    目标:
    增强代码一致性,创建通用的、必需的习惯用语和模式可以使代码更加容易理解C++是一门包含大量高级特性的巨型语言,某些情况下,我们会限制甚至禁止使用某些特性使代码简化,避免可能导致的各种问题
    包含的内容:
    头文件、命名规则、注释、语言特性的使用规则、编码格式
    特点:
    强调理解基础上的遵循,一个规则通常明确说明其优点、缺点,并举很多例子,让读者在理解的基础上遵循,不像规章制度那样生硬和抽象,实际上读起来更像一个教程。比如:禁止使用C++异常, 花了一页纸的篇幅来解释使用和不使用的优缺点,非常容易理解
    推荐语:
    读起来非常舒服,抛开编程规范,拿来作为理解学习C++也是不错的。

    (2)汽车业C语言使用规范(MISRA)

    目标:
    因为编译器、编程人员理解、C语言本等原因,完全放开使用C语言存在一些风险,因此制定这个规范的目标为了促进C语言的最为安全的使用而定义一些规则。
    特点:
    规则都是针对的是C语言本身缺陷或容易被误解的点,如:自动变量如果不初始化就使用会出现随机值、不同类型数据赋值容易出现的隐式转换;没有包含诸如注释、变量名、编码格式等统一编程风格的内容。
    推荐语:
    对C的缺点了如指掌,可以帮助更好的掌握C语言,避免编程陷阱,提高程序可靠性。

    展开全文
  • C语言编程规范(pdf)

    2011-03-09 13:07:35
    好的编程规范使得程序可读性,可维护性更佳,不过不一定所有人都用一样的规范编码,可以选择自己觉得好的一种就可以
  • C语言编程规范

    目录

    规范说明

    (1)代码总体原则 —— 清晰第一
    清晰性是易于维护、易于重构的程序必需具备的特征。一般情况下,代码的可阅读性高于性能,只有确定性能是瓶颈时,才应该主动优化。
    (2)代码总体原则 —— 简洁为美
    简洁就是易于理解并且易于实现。代码越长越难以看懂,也就越容易在修改时引入错误。
    (3)代码总体原则 ——代码风格
    选择合适的风格,与代码原有风格保持一致 。 在公司已有编码规范的指导下,审慎地编排代码以使代码尽可能清晰。

    一、头文件

    对于C语言来说,头文件的设计体现了大部分的系统设计。不合理的头文件布局是编译时间过长的根因。合理的头文件划分体现了系统设计的思想。

    原则1.1 头文件中适合放置接口的声明,不适合放置实现。

    说明:
    头文件是模块(Module)或单元(Unit)的对外接口。头文件中应放置对外部的声明,如对外提供的函数声明、宏定义、类型定义等。
    内部使用的函数声明不应放在头文件中。
    内部使用的宏、枚举、结构定义不应放入头文件中。
    变量定义不应放在头文件中,应放在.c文件中。
    变量的声明尽量不要放在头文件中,亦即尽量不要使用全局变量作为接口
    变量是模块或单元的内部实现细节,不应通过在头文件中声明的方式直接暴露给外部,应通过函数接口的方式进行对外暴露。即使必须使用全局变量,也只应当在.c中定义全局变量,在.h中仅声明变量为全局的。

    原则1.2 头文件应当职责单一

    原则1.3 头文件应向稳定的方向包含

    说明:
    头文件的包含关系是一种依赖,一般来说,应当让不稳定的模块依赖稳定的模块,从而当不稳定的模块发生变化时,不会影响(编译)稳定的模块。

    规则1.1 每一个.c文件应有一个同名.h文件,用于声明需要对外公开的接口。

    说明:
    如果一个.c文件不需要对外公布任何接口,则其就不应当存在,除非它是程序的入口,如main函数所在的文件。
    另外,一旦把私有定义、声明放到独立的头文件中,就无法从技术上避免别人include,难以保证这些定义最后真的只是私有的。
    本规则反过来并不一定成立。有些特别简单的头文件,如命令ID定义头文件,不需要有对应的.c存在。

    规则1.2 禁止头文件循环依赖

    规则1.3 .c/.h文件禁止包含用不到的头文件

    规则1.4 头文件应当自包含

    说明:简单的说,自包含就是任意一个头文件均可独立编译。如果一个文件包含某个头文件,还要包含另外一个头文件才能工作的话,就会增加交流障碍,给这个头文件的用户增添不必要的负担。
    注意:该规则需要与“.c/.h文件禁止包含用不到的头文件”规则一起使用,不能为了让a.h自包含,而在a.h中包含不必要的头文件。a.h要刚刚可以自包含,不能在a.h中多包含任何满足自包含之外的其他头文件。

    规则1.5 总是编写内部#include保护符(#define 保护)

    说明:多次包含一个头文件可以通过认真的设计来避免。如果不能做到这一点,就需要采取阻止头文件内容被包含多于一次的机制。通常的手段是为每个文件配置一个宏,当头文件第一次被包含时就定义这个宏,并在头文件被再次包含时使用它以排除文件内容。

    规则1.6 禁止在头文件中定义变量

    说明:在头文件中定义变量,将会由于头文件被其他.c文件包含而导致变量重复定义。

    规则1.7 只能通过包含头文件的方式使用其他.c提供的接口,禁止在.c中通过extern的方式使用外部函数接口、变量

    规则1.8 禁止在extern "C"中包含头文件

    建议1.1 一个模块通常包含多个.c文件,建议放在同一个目录下,目录名即为模块名
    建议1.2 如果一个模块包含多个子模块,则建议每一个子模块提供一个对外的.h,文件名为子模块名
    建议1.3 同一产品统一包含头文件排列方式

    二、函数

    函数设计的精髓:编写整洁函数,同时把代码有效组织起来。
    整洁函数要求:代码简单直接、不隐藏设计者的意图、用干净利落的抽象和直截了当的控制语句将函数有机组织起来。
    代码的有效组织包括:逻辑层组织和物理层组织两个方面。逻辑层,主要是把不同功能的函数通过某种联系组织起来,主要关注模块间的接口,也就是模块的架构。物理层,无论使用什么样的目录或者名字空间等,需要把函数用一种标准的方法组织起来。例如:设计良好的目录结构、函数名字、文件组织等,这样可以方便查找。

    原则2.1 一个函数仅完成一件功能

    原则2.2 重复代码应该尽可能提炼成函数

    规则2.1 避免函数过长,新增函数不超过50行(非空非注释行)

    规则2.2 避免函数的代码块嵌套过深,新增函数的代码块嵌套不超过4层

    说明:本规则仅对新增函数做要求,对已有的代码建议不增加嵌套层次。函数的代码块嵌套深度指的是函数中的代码控制块(例如:if、for、while、switch等)之间互相包含的深度。每级嵌套都会增加阅读代码时的脑力消耗,因为需要在脑子里维护一个“栈”(比如,进入条件语句、进入循环)。应该做进一步的功能分解,从而避免使代码的阅读者一次记住太多的上下文。

    规则2.3 可重入函数应避免使用共享变量;若需要使用,则应通过互斥手段(关中断、信号量)对其加以保护

    说明:可重入函数是指可能被多个任务并发调用的函数。在多任务操作系统中,函数具有可重入性是多个任务可以共用此函数的必要条件。共享变量指的全局变量和static变量。
    编写C语言的可重入函数时,不应使用static局部变量,否则必须经过特殊处理,才能使函数具有可重入性。

    规则2.4 对参数的合法性检查,由调用者负责还是由接口函数负责,应在项目组/模块内应统一规定。缺省由调用者负责

    规则2.5 对函数的错误返回码要全面处理。

    规则2.6 设计高扇入,合理扇出(小于7)的函数。

    说明:
    扇出是指一个函数直接调用其它函数的数目,而扇入是指有多少上级函数调用它。
    扇出过大,表明函数过分复杂,需要控制和协调过多的下级函数;而扇出过小,例如:总是1,表明函数的调用层次可能过多,这样不利于程序阅读和函数结构的分析,并且程序运行时会对系统资源如堆栈空间等造成压力。通常函数比较合理的扇出(调度函数除外)通常是3~5。
    扇出太大,一般是由于缺乏中间层次,可适当增加中间层次的函数。扇出太小,可把下级函数进一步分解多个函数,或合并到上级函数中。当然分解或合并函数时,不能改变要实现的功能,也不能违背函数间的独立性。
    扇入越大,表明使用此函数的上级函数越多,这样的函数使用效率高,但不能违背函数间的独立性而单纯地追求高扇入。公共模块中的函数及底层函数应该有较高的扇入。
    较良好的软件结构通常是顶层函数的扇出较高,中层函数的扇出较少,而底层函数则扇入到公共模块中

    规则2.7 废弃代码(没有被调用的函数和变量)要及时清除

    建议2.1 函数不变参数使用cons

    说明:不变的值更易于理解/跟踪和分析,把const作为默认选项,在编译时会对其进行检查,使代码更牢固/更安全。

    建议2.2 函数应避免使用全局变量、静态局部变量和I/O操作,不可避免的地方应集中使用

    说明:带有内部“存储器”的函数的功能可能是不可预测的,因为它的输出可能取决于内部存储器(如某标记)的状态。这样的函数既不易于理解又不利于测试和维护。

    建议2.3 检查函数所有非参数输入的有效性,如数据文件、公共变量等
    建议2.4 函数的参数个数不超过5个
    建议2.5 除打印类函数外,不要使用可变长参函数
    建议2.6 源文件范围内声明和定义的所有函数,除非外部可见,否则应该增加static关键字

    说明:如果一个函数只是在同一文件中的其他地方调用,那么就用static声明。使用static确保只是在声明它的文件中是可见的,并且避免了和其他文件或库中的相同标识符发生混淆的可能性。
    建议定义一个STATIC宏,在调试阶段,将STATIC定义为static,版本发布时,改为空,以便于后续的打热补丁等操作。
    #ifdef _DEBUG
    #define STATIC static
    #else
    #define STATIC
    #endif

    三、标识符命名与定义

    原则3.1 标识符的命名要清晰、明了,有明确含义,同时使用完整的单词或大家基本可以理解的缩写,避免使人产生误解

    原则3.2 除了常见的通用缩写以外,不使用单词缩写,不得使用汉语拼音

    一些常见可以缩写的例子: 
    argument 可缩写为 arg 
    buffer 可缩写为 buff 
    clock 可缩写为 clk 
    command 可缩写为 cmd 
    compare 可缩写为 cmp 
    configuration 可缩写为 cfg 
    device 可缩写为 dev 
    error 可缩写为 err 
    hexadecimal 可缩写为 hex 
    increment 可缩写为 inc 
    initialize 可缩写为 init 
    maximum 可缩写为 max 
    message 可缩写为 msg 
    minimum 可缩写为 min 
    parameter 可缩写为 para 
    previous 可缩写为 prev 
    register 可缩写为 reg 
    semaphore 可缩写为 sem 
    statistic 可缩写为 stat 
    synchronize 可缩写为 sync 
    temp 可缩写为 tmp  
    
    

    规则3.1 产品/项目组内部应保持统一的命名风格

    建议3.1 用正确的反义词组命名具有互斥意义的变量或相反动作的函数等
    建议3.2 尽量避免名字中出现数字编号,除非逻辑上的确需要编号
    建议3.3 平台/驱动等适配代码的标识符命名风格保持和平台/驱动一致
    建议3.4 重构/修改部分代码时,应保持和原有代码的命名风格一致
    建议3.5 文件命名统一采用小写字符

    规则3.2 全局变量应增加“g_”前缀

    规则3.3 静态变量应增加“s_”前缀

    规则3.4 禁止使用单字节命名变量,但允许定义i、j、k作为局部循环变量

    建议3.6 不建议使用匈牙利命名法
    建议3.7 使用名词或者形容词+名词方式命名变量
    建议3.8 函数命名应以函数要执行的动作命名,一般采用动词或者动词+名词的结构
    建议3.9 函数指针除了前缀,其他按照函数的命名规则命名

    规则3.5 对于数值或者字符串等等常量的定义,建议采用全大写字母,单词之间加下划线”_”的方式命名(枚举同样建议使用此方式定义)

    规则3.6 除了头文件或编译开关等特殊标识定义,宏定义不能使用下划线”_”开头和结尾

    四、变量

    原则4.1 一个变量只有一个功能,不能把一个变量用作多种用途

    原则4.2 结构功能单一;不要设计面面俱到的数据结构

    原则4.3 不用或者少用全局变量

    说明:单个文件内部可以使用static的全局变量,可以将其理解为类的私有成员变量。全局变量应该是模块的私有数据,不能作用对外的接口使用,使用static类型定义,可以有效防止外部文件的非正常访问。

    规则4.1 防止局部变量与全局变量同名

    规则4.2 通讯过程中使用的结构,必须注意字节序

    说明:通讯报文中,字节序是一个重要的问题,cpu类型复杂多样,大小端、32位/64位的处理器也都有,如果结构会在报文交互过程中使用,必须考虑字节序问题。由于位域在不同字节序下,表现看起来差别更大,所以更需要注意。对于这种跨平台的交互,数据成员发送前,都应该进行主机序到网络序的转换;接收时,也必须进行网络序到主机序的转换。

    规则4.3 严禁使用未经初始化的变量作为右值

    建议4.1 构造仅有一个模块或函数可以修改、创建,而其余有关模块或函数只访问的全局变量,防止多个不同模块或函数都可以修改、创建同一全局变量的现象
    建议4.2 使用面向接口编程思想,通过API访问数据:如果本模块的数据需要对外部模块开放,应提供接口函数来设置、获取,同时注意全局数据的访问互斥

    说明:避免直接暴露内部数据给外部模型使用,是防止模块间耦合最简单有效的方法。

    建议4.3 在首次使用前初始化变量,初始化的地方离使用的地方越近越好
    建议4.4 明确全局变量的初始化顺序,避免跨模块的初始化依赖
    建议4.5 尽量减少没有必要的数据类型默认转换与强制转换

    五、宏、常量

    规则5.1 用宏定义表达式时,要使用完备的括号

    说明:因为宏只是简单的代码替换,不会像函数一样先将参数计算后,再传递。
    示例:
    在这里插入图片描述

    规则5.2 将宏所定义的多条表达式放在大括号中

    说明:更好的方法是多条语句写成do while(0)的方式

    规则5.3 使用宏时,不允许参数发生变化

    规则5.4 不允许直接使用魔鬼数字

    说明:使用魔鬼数字的弊端:代码难以理解;如果一个有含义的数字多处使用,一旦需要修改这个数值,代价惨重。使用明确的物理状态或物理意义的名称能增加信息,并能提供单一的维护点。
    解决途径:对于局部使用的唯一含义的魔鬼数字,可以在代码周围增加说明注释,也可以定义局部const变量,变量命名自注释。
    对于广泛使用的数字,必须定义const全局变量/宏;同样变量/宏命名应是自注释的。
    0作为一个特殊的数字,作为一般默认值使用没有歧义时,不用特别定义。

    建议5.1 除非必要,应尽可能使用函数代替宏

    说明:宏对比函数,有一些明显的缺点
    宏缺乏类型检查,不如函数调用检查严格。
    宏展开可能会产生意想不到的副作用,如#define SQUARE(a) (a) * (a)这样的定义,如果是SQUARE(i++),就会导致i被加两次;如果是函数调用double square(double a) {return a * a;}则不会有此副作用。
    以宏形式写的代码难以调试难以打断点,不利于定位问题。
    宏如果调用的很多,会造成代码空间的浪费,不如函数空间效率高。

    建议5.2 常量建议使用const定义代替宏
    建议5.3 宏定义中尽量不使用return、goto、continue、break等改变程序流程的语句

    六、质量保证

    原则6.1 代码质量保证优先原则

    说明:
    1、正确性,指程序要实现设计要求的功能。
    2、简洁性,指程序易于理解并且易于实现。
    3、可维护性,指程序被修改的能力,包括纠错、改进、新需求或功能规格变化的适应能力。
    4、可靠性,指程序在给定时间间隔和环境条件下,按设计要求成功运行程序的概率。
    5、可测试性,指软件发现故障并隔离、定位故障的能力,以及在一定的时间和成本前提下, 进行测试设计、测试执行的能力。
    6、性能高效,指是尽可能少地占用系统资源,包括内存和执行时间。
    7、可移植性,指为了在原来设计的特定环境之外运行,对系统进行修改的能力。
    8、个人表达方式/个人方便性,指个人编程习惯。

    原则6.2 要时刻注意易混淆的操作符

    规则6.1 禁止内存操作越界

    坚持下列措施可以避免内存越界
    (1)数组的大小要考虑最大情况,避免数组分配空间不够;
    (2)避免使用危险函数sprintf /vsprintf/strcpy/strcat/gets;
    (3)操作字符串,使用相对安全的函数snprintf/strncpy/strncat/fgets代替;
    (4)使用memcpy/memset时一定要确保长度不要越界字符串考虑最后的’\0’,确保所有字符串是以’\0’结束指针加减操作时,考虑指针类型长度数组下标进行检查使用时sizeof或者strlen计算结构/字符串长度,避免手工计算。

    规则6.2 禁止内存泄漏

    坚持下列措施可以避免内存泄漏
    (1)异常出口处检查内存、定时器/文件句柄/Socket/队列/信号量/GUI等资源是否全部释放;
    (2)删除结构指针时,必须从底层向上层顺序删除;
    (3)使用指针数组时,确保在释放数组时,数组中的每个元素指针是否已经提前被释放了;
    (4)避免重复分配内存;
    (5)小心使用有return、break语句的宏,确保前面资源已经释放;
    (6)检查队列中每个成员是否释放。

    规则6.3 禁止引用已经释放的内存空间

    坚持下列措施可以避免引用已经释放的内存空间
    (1)内存释放后,把指针置为NULL,使用内存指针前进行非空判断。
    (2)耦合度较强的模块互相调用时,要仔细考虑其调用关系,防止已经删除的对象被再次使用。避免操作已发送消息的内存。

    规则6.4 编程时,要防止差1错误

    实例:
    在这里插入图片描述

    规则6.5 所有的if … else if结构应该由else子句结束;switch语句必须有default分支。

    建议6.1 函数中分配的内存,在函数退出之前要释放
    建议6.2 不要滥用goto语句

    说明:goto语句会破坏程序的结构性,所以除非确实需要,最好不使用goto语句。可以利用goto语句方面退出多重循环;同一个函数体内部存在大量相同的逻辑但又不方便封装成函数的情况下,譬如反复执行文件操作,对文件操作失败以后的处理部分代码(譬如关闭文件句柄,释放动态申请的内存等等),一般会放在该函数体的最后部分,再需要的地方就
    goto到那里,这样代码反而变得清晰简洁。实际也可以封装成函数或者封装成宏,但是这么做会让代码变得没那么直接明了。

    建议6.3 时刻注意表达式是否会上溢、下溢

    七、程序效率

    原则7.1 在保证软件系统的正确性、简洁、可维护性、可靠性及可测性的前提下,提高代码效率。本章节后面所有规则和建议,都应在不影响前述可读性等质量属性的前提下实施

    原则7.2 通过对数据结构、程序算法的优化来提高效率。

    建议7.1 将不变条件的计算移到循环体外。
    建议7.2 对于多维大数组,避免来回跳跃式访问数组成员。
    建议7.3 创建资源库,以减少分配对象的开销

    说明:例如,使用线程池机制,避免线程频繁创建、销毁的系统调用;使用内存池,对于频繁申请、释放的小块内存,一次性申请一个大块的内存,当系统申请内存时,从内存池获取小块内存,使用完毕再释放到内存池中,避免内存申请释放的频繁系统调用。

    建议7.4 将多次被调用的 “小函数”改为inline函数或者宏实现

    说明:如果编译器支持inline,可以采用inline函数。在做这种优化的时候一定要注意下面inline函数的优点:其一编译时不用展开,代码SIZE小。其二可以加断点,易于定位问题,例如对于引用计数加减的时候。其三函数编译时,编译器会做语法检查。

    八、注释

    原则8.1 优秀的代码可以自我解释,不通过注释即可轻易读懂

    原则8.2 注释的内容要清楚、明了,含义准确,防止注释二义性

    原则8.3 在代码的功能、意图层次上进行注释,即注释解释代码难以直接表达的意图,而不是重复描述代码

    规则8.1 修改代码时,维护代码周边的所有注释,以保证注释与代码的一致性。不再有用的注释要删除

    规则8.2 函数声明处注释描述函数功能、性能及用法,包括输入和输出参数、函数返回值、可重入的要求等;定义处详细描述函数功能和实现要点,如实现的简要步骤、实现的理由、设计约束等

    规则8.3 全局变量要有较详细注释,包括对其功能、取值范围以及存取时注意事项等说明

    规则8.4 避免在注释中使用缩写,除非是业界通用或子系统内标准化的缩写

    规则8.5 进行DEBUG修改代码时,添加注释中应加入BUG id

    九、排版与格式

    规则9.1 程序块采用缩进风格编写,每级缩进为4个空格

    规则9.2 相对独立的程序块之间、变量说明之后必须加空行

    规则9.3 一条语句不能过长,如不能拆分需要分行写

    规则9.4 多个短语句(包括赋值语句)不允许写在同一行内,即一行只写一条语句

    规则9.5 在两个以上的关键字、变量、常量进行对等操作时,它们之间的操作符之前、之后或者前后要加空格;进行非对等操作时,如果是关系密切的立即操作符(如->),后不应加空格

    例子:
    在这里插入图片描述

    10、表达式

    规则10.1 表达式的值在标准所允许的任何运算次序下都应该是相同的

    说明:说明:除了少数操作符(函数调用操作符 ( )、&&、| |、? : 和 , (逗号))之外,子表达式所依据的运算次序是未指定的并会随时更改。注意,运算次序的问题不能使用括号来解决,因为这不是优先级的问题。将复合表达式分开写成若干个简单表达式,明确表达式的运算次序,就可以有效消除非预期副作用。
    示例:
    1、自增或自减操作符
    x = b[i] + i++;
    b[i] 的运算是先于还是后于 i ++ 的运算,表达式会产生不同的结果,把自增运算做为单独的语句,可以避免这个问题。
    x = b[i] + i;
    i ++;

    2﹑函数参数
    说明:函数参数通常从右到左压栈,但函数参数的计算次序不一定与压栈次序相同。
    示例:x = func( i++, i); 应该修改代码明确先计算第一个参数:i++; x = func(i, i);
    在这里插入图片描述

    3、函数指针
    说明:函数参数和函数自身地址的计算次序未定义。
    p->task_start_fn(p++);
    求函数地址p与计算p++无关,结果是任意值。必须单独计算p++:
    p->task_start_fn§;
    p++;

    4﹑函数调用
    在这里插入图片描述
    编译器可能先计算fun1(),也可能先计算fun2(),由于x的结果依赖于函数fun1()/fun2()的计算次序(fun1()/fun2()被调用时修改和使用了同一个全局变量),则上面的代码存在问题。
    应该修改代码明确fun1/ fun2的计算次序:
    int x = fun1();
    x = x + fun2();

    5、嵌套赋值语句

    说明:表达式中嵌套的赋值可以产生附加的副作用。不给这种能导致对运算次序的依赖提供任何机会的最好做法是,不要在表达式中嵌套赋值语句。

    6、volatile访问

    说明:限定符volatile表示可能被其它途径更改的变量,例如硬件自动更新的寄存器。编译器不会优化对volatile变量的读取。

    示例:下面的写法可能无法实现作者预期的功能:
    /* volume变量被定义为volatile类型*/
    UINT16 x = ( volume << 3 ) | volume;

    /* 在计算了其中一个子表达式的时候,volume的值可能已经被其它程序或硬件改变,导致另外一个子表达式的计算结果非预期,可能无法实现作者预期的功能 */
    所以说volatile并不是可以解决所有的问题,例如:线程同步

    7.避免把复合表达式与数学表达式混淆
      
    例如:if(a < b < c)
      看着有点懵
      有些人会认为是if((a < b) && (b < c))
      然而呢,是这样的
      If((a < b) < c)

    建议10.1 函数调用不要作为另一个函数的参数使用,否则对于代码的调试、阅读都不利
    建议10.2 赋值语句不要写在if等语句中,或者作为函数的参数使用
    建议10.3 用括号明确表达式的操作顺序,避免过分依赖默认优先级
    建议10.4 赋值操作符不能使用在产生布尔值的表达式上

    11、安全性

    原则11.1 对用户输入进行检查

    说明:不能假定用户输入都是合法的,因为难以保证不存在恶意用户,即使是合法用户也可能由于误用误操作而产生非法输入。用户输入通常需要经过检验以保证安全,特别是以下场景:
    1、用户输入作为循环条件
    2、用户输入作为数组下标
    3、用户输入作为内存分配的尺寸参数
    4、用户输入作为格式化字符串
    5、用户输入作为业务数据(如作为命令执行参数、拼装sql语句、以特定格式持久化)
    这些情况下如果不对用户数据做合法性验证,很可能导致DOS、内存越界、格式化字符串漏洞、命令注入、SQL注入、缓冲区溢出、数据破坏等问题。
    6、可采取以下措施对用户输入检查:
    7、用户输入作为数值的,做数值范围检查
    8、用户输入是字符串的,检查字符串长度
    9、用户输入作为格式化字符串的,检查关键字“%”
    10、用户输入作为业务数据,对关键字进行检查、转义

    规则11.1 确保所有字符串是以NULL结束

    说明:C语言中‟\0‟作为字符串的结束符,即NULL结束符。标准字符串处理函数(如strcpy()、strlen())依赖NULL结束符来确定字符串的长度。没有正确使用NULL结束字符串会导致缓冲区溢出和其它未定义的行为。
    为了避免缓冲区溢出,常常会用相对安全的限制字符数量的字符串操作函数代替一些危险函数。如:
    用strncpy()代替strcpy()
    用strncat()代替strcat()
    用snprintf()代替sprintf()
    用fgets()代替gets()
    用vsprintf代替vsnprintf

    规则11.2 不要将边界不明确的字符串写到固定长度的数组中

    说明:边界不明确的字符串(如来自gets()、getenv()、scanf()的字符串),长度可能大于目标数组长度,直接拷贝到固定长度的数组中容易导致缓冲区溢出。
    在这里插入图片描述
    正确写法:计算字符串的实际长度,使用malloc分配指定长度的内存
    在这里插入图片描述

    规则11.3 避免整数溢出

    规则11.4 避免符号错误

    说明:有时从带符号整型转换到无符号整型会发生符号错误,符号错误并不丢失数据,但数据失去了原来的含义。
    带符号整型转换到无符号整型,最高位(high-order bit)会丧失其作为符号位的功能。如果该带符号整数的值非负,那么转换后值不变;如果该带符号整数的值为负,那么转换后的结果通常是一个非常大的正数。

    规则11.5:避免截断错误

    说明:将一个较大整型转换为较小整型,并且该数的原值超出较小类型的表示范围,就会发生截断错误,原值的低位被保留而高位被丢弃。截断错误会引起数据丢失。使用截断后的变量进行内存操作,很可能会引发问题。

    规则11.6:确保格式字符和参数匹配

    规则11.7 避免将用户输入作为格式化字符串的一部分或者全部

    说明:调用格式化I/O函数时,不要直接或者间接将用户输入作为格式化字符串的一部分或者全部。攻击者对一个格式化字符串拥有部分或完全控制,存在以下风险:进程崩溃、查看栈的内容、改写内存、甚至执行任意代码

    规则11.8 避免使用strlen()计算二进制数据的长度

    说明:strlen()函数用于计算字符串的长度,它返回字符串中第一个NULL结束符之前的字符的数量。因此用strlen()处理文件I/O函数读取的内容时要小心,因为这些内容可能是二进制也可能是文本.

    规则11.9 使用int类型变量来接受字符I/O函数的返回值

    说明:字符I/O函数fgetc()、getc()和getchar()都从一个流读取一个字符,并把它以int值的形式返回。如果这个流到达了文件尾或者发生读取错误,函数返回EOF。fputc()、putc()、putchar()和ungetc()也返回一个字符或EOF。
    如果这些I/O函数的返回值需要与EOF进行比较,不要将返回值转换为char类型。因为char
    是有符号8位的值,int是32位的值。如果getchar()返回的字符的ASCII值为0xFF,转换为char类型后将被解释为EOF。因为这个值被有符号扩展为0xFFFFFFFF(EOF的值)执行比较。

    确做法:使用int类型的变量接受getchar()的返回值。
    对于sizeof(int) == sizeof(char)的平台,用int接收返回值也可能无法与EOF区分,这时要用feof()和ferror()检测文件尾和文件错误。

    规则11.10 防止命令注入

    说明:C99函数system()通过调用一个系统定义的命令解析器(如UNIX的shell,Windows的CMD.exe)来执行一个指定的程序/命令。类似的还有POSIX的函数popen()。 如果system()
    的参数由用户的输入组成,恶意用户可以通过构造恶意输入,改变system()调用的行为。
    示例:system(sprintf(“any_exe %s”, input)); 如果恶意用户输入参数: happy; useradd attacker
    最终shell将字符串“any_exe happy; useradd attacker”解释为两条独立的命令:
    正确做法:使用POSIX函数execve()代替system()。
    在这里插入图片描述

    参考试题1

    单选题

    1.对于如下1和2的描述,以下哪种说法是正确的∶ (D)
    1.堆内存释放后可以再访问,没有安全风险;
    2.栈内存释放后可以再访问,没有安全风险
    A. 只有1对
    B.只有2对
    C.1,2都对
    D.1.2都不对

    2.选择出正确的宏定义: (C)
    A.#define RECT_AREA( a, b ) ( a * b )
    B.#define RECT_AREA( a, b ) ( a )+ ( b )
    C.#define RECT_AREA( a, b ) ( ( a ) * ( b ) )
    D.#define RECT_AREA( a, b ) a * b

    3.下列关于头文件或宏的描述错误的是 (C)
    A.每一个.c文件应有一个同名.h文件,用于声明需要对外公开的接口,同时.c/.h文件禁止包含用不到的头文件
    B.禁止在头文件中定义变量
    C.应尽可能使用函数代替宏,建议使用宏代替const定义常量
    D.使用宏时,不允许参数发生变化

    4.如果a.c包含了头文件a.h , a.h包含了头文件b.h , b.c也包含了b.h,那么当b.h发生改变时,哪些文件将会被重新编j译 (D)
    A.b.c
    B.a.h
    C.a.c和a.h
    D.a.c和b.c

    5.关于程序效率的规范要求,哪一项描述是错误的 (A)
    A.应该把执行概率较大的分支放在if else判断后面处理
    B.将循环中与循环无关,不是每次循环都要做的操作,移到循环外部执行
    C.创建资源库(线程池、内存池),以减少分配对象的开销
    D.将多次被调用的“小函数”改为inline函数或者宏实现

    6.关于命名正确的是 (C)
    A.#define EXAMPLE_O_TEST_ #define EXAMPLE_1_TEST_
    B.代码文件命名建议统-采用首字母大写其余字母小写的形式命名
    C. int error_number; int number_of_completed_connection;
    D.DWORD GetJinchengMulu( DWORD BufferLength,LPTSTR Buffer );

    7.以下哪种措施不可以避免内存操f作越界 (D)
    A.数组的大小要考虑最大情况,避免数组分配空间不够
    B.字符串考虑最后的’\O’,确保所有字符串是以\0’结束
    C.指针加减操作时,考虑指针类型长度
    D.通过手工计算结构/字符串长度

    8.下面说法措误的是 (D)
    A.程序中要及时清除存储在可复用资源中的敏感信息
    B.删除或修改没有效果的代码
    C.删除或修改没有使用到的变量或值
    D.非动态申请的内存也可以使用free手动释放

    9.关于const的描述错误的是 (C)
    A. const intA=1;定义了一个整型常量A,值为1,在任何时候值都不可变B.const char*A=“abc”;指针A指向的内容不可变
    C. char * const A=“abc”;指针A指向的内容不可变.
    D.const char * const A="abc” ;指针A和指向的内容都不可变

    10.下面说法正确的是 (B)
    .A一些存在的代码(声明或表达式),即使它被执行后,也不会对代码的结粜或数据的状态产生任何的彰响,所以可以不用理会这些代码
    B.在使用像memcpy、strcpy、stncpy、sscanfO)、sprintf)、snprintfO和wcstombs0这样的函数时,必须检查源地髦和昌的地址是适存在震要的内存区域
    C.调用格式化1/O函数时,可以直接或者间接将用户输入作为格式化字符串的一部分
    D.申请内存的函数可以使用malloc、calloc及 alloca

    11.对于字符串"abcdefghi0"使用多大字符数组进行存储最为合适(B)
    A.10.0
    B.11.0
    C.12.0
    D.20.0

    12 空

    13.下列关于整数的操作正确的是: ( INT32表示32位有符号整数,UINT32表示32位无符号整数,NT8表示8位有符号整数) (A)
    A.INT32 Func(UINT32 ui1,UINT32 uf2,UINT32* ret) ( if( NULL m= ret ) ( returnERROR ,} if(UINT_MAX- uil) < ui2)(return ERROR; } else { *ret = uil+ ui2; } return OK;)
    B.INT32 Func(INT32 si1, INT32 5i2,INT32 *ret) ( if ( NUL mm ret ) { return ERROR } 'ret = si1 * si2,return OK:}
    C.INT32 Func(UINT32 ul, INT8 *ret) 【 if( NULL me ret ) { return ERROR; } ret =(INT8)ul; return (OK);}
    D.#define BUF_SZE 10 int main(int argc, char
    argv(D) ( int length; char buf(BUF_SIZE if (argc != 3) ( return -1: } len
    gth = atoi(argv[1]); if (length < BUF_SIZE) ( memcpy(but, argv[2], length); printf("Data copiedin “T; ) else ( printf(Too many dataln”); })

    14.如果函数foo仅在a.c内可见,则应当如下声明 (B)
    A.在a.c的头部声明int foo(void);
    B.在a.c的头部声明static int foo(void);
    C.在a.h的头部声明static int foo(void);
    D.在a.c的头部声明extern int foo(void);

    15.以下关于安全编码的说法,错误的是 (D)
    A.无论是明文口令还是密文口令,都应该禁止保存在日志文件中
    B.删除或修改一些即使执行后、也不会有任何效果的代码
    C.程序在运行时应该只分配能完成其任务的最小权限
    D.程序执行任务完毕时,应该尽快收回其权限,对于权限的撤销顺序可以不用考虑

    16 空

    17.需要对指定申请内存大小的整数值进行合法性校验,是因为∶ ( C)
    1.使用0字节长度去申请内存的行为是没有定义的,在引用内存申请函数返回的地址时会引发不可预知或不能立即发现的问题﹔
    2.使用负数长度去审请内存,负数会被当成一个很大的无符号整数,从而导数因申请内存过大而出现失败
    A.只有1对
    B.只有2对
    C.1,2都对
    D.1,2都不对

    18.下列哪种方式产生的随机数是不安全、容易被预测的 (A)
    A.C99的rand()
    B.Unix/Linux下读取/dev/random文件
    C. Windows使用随机数生成函数
    D.以上选项都不对

    19.针对函数的设计的说法,下面哪个是正确的 (B)
    A.应尽量设计多用途面面俱到的函数;函数的参数个数可以超过5个
    B.在源文件范围内声明和定义的所有函数,除非外部可见,否则应该增加static关键字
    C.设计高扇入,台理扇出(小于7)的函数;新增函数的代码块嵌套不超过5层
    D.函数中的冗余代码只要不影响函数的效率,不需要删除

    20.下面哪个算法是禁止使用的弱加密算法
    A.SHA2
    B.RSA
    C.DSA
    D.DES

    不定向选择题

    21.以下说法错误的有 (C,D)
    A.必须对指定申请内存大小的整数值进行合法性校验
    B.禁止重复释放内存
    C.堆内存释放不可以再访问,栈内存释放后可以再访问
    D.为了使用方便,建议使用alloca函数申请内存

    22.如果a.c只对外提供一个void bar()函数作为接口,而bar函数的实现部分需要使用b.c中的void foo()函数,以下做法中错误的是 (A)
    A.在b.c中声明extern void foo(),在a.c中声明extern void foo()
    B.在b.h中声明extern void foo(),在a.c中声明extern void foo()
    C.在b.h中声明extern void foo(),在a.c中#include b.h
    D.在b.h中声明extern void foo(),在a.h中#include b.h

    23.关变量,正确的说法是 (A,C,D)
    A.若全局变量仅在单个C文件中访问,则可以将这个变量修改为静态全局变量,以降低模块间的耦合度
    B.若全局或全仅由单个函数访问,则可以将这个变量改为该函数的静态局部变量,以降低模块间的耦合度
    C.设计和使用访问动态全局变量、静态全局变量、静态局部变量的函数时,需要考虑重入问题
    D.函教内部定义的自动变量过大,可能会导致堆栈溢出

    24.坚持下列那些措施可以避免内存泄露 (A,B,C,D)
    A.异常出口处检查内存、定时器/文件句柄/Socket/队列/信号量/GUI等资源是否全郜释放
    B.删除结构指针时,必须从底层向上层顺序删除
    C.使用指针数组时,确保在释放数组时,数组中的每个元素指针是否已经提前被释放了
    D.避免重复分配内存

    25.如果不使用常量,直接在程序中填写数字或字符串,将会有哪些麻烦(A,B,C)
    A.程序的可读性(可理解性)变差。程序员自己会忘记那些数字或字符串是什么意思,维护人员则更加不知它们从何处来、表示什么
    B.如果要修改数字或字符串,则会在很多地方改动,既麻烦又容易出错
    C.在程序的很多地方轮入同样的数字威字符串,难保不发生书写错误
    D.程序无法编译

    26.对于防止头文件被重复包含,描术正在的有
    A.多次包含一个头文件可以请过认真的设计来避免.如果不能做到这一点,就需要采取阻止头文件内容被包含多余一次的机制
    B.防止重复包含的通常手段是:为每个来件配置1个宏,当头文件第一次被包含时就定义这个宏,并在头文件被再次包含时使用它以排除文件内容
    C.保护宏的名称要唯一,不能重复
    D.不应在防止重复包含的保护宏外部故置代码

    27.下列定义中正确的是 (A,B,D)
    A.char *a = "abcd ";
    B.char a[] = “abcd”;
    C.char a[4]= “abcd”;
    D.char a[ ]= " ";

    28.以下说法正确的有 (A,B,C,D)
    A.在引用内存之前需要注意是否该内存已被成功初始化
    B.在访问内存前需要注意该内存是杏有效,是杏已在其他地方被释放过C.放内存时需要注意是否是已释放过的内存,注意不要重复释放
    D.释放内存时需要注意不要释放非动态申请的内存

    29.关于全局变弹的注释,哪些描述是正确的 (A,B,D)
    A.全局变量要有较详细的注释
    B.对全局变量注释,可包括功能、.取值范围等
    C.全局变量应该是本模块内使用的,用法自己自然是知遵的,可以不加注释
    D.对全局变属注释需包括对该全局变量存取时的注意事琐等

    30.以下哪些输入不可信输入 (A,B,C,D)
    A.用户键盘输入
    B.配置文件输入
    C.环境变量
    D.网络数值

    31.对于整数溢出问题,下面说法正确的是 (A,B,C)
    A.无符号整数运算时↓要保证结果不能出现反转
    B.有符号整数运算时,要保证结果不能出现溢出
    C. 整型转换时避免出现截断危险
    D.整數溢出一 般只会导致逻辑错误,不会产生安全问题

    32.编码时应该使用下面哪些函数来安全运行程序 (C,D)
    A. popen()
    B. system()
    C. exec()系列函数
    D. CreateProcess()

    33.安全编程规范中,安全用途的随机数产生方式,推荐的有 (A,B,D)
    A. Unix/Linux下采取建议读取/dev/random文件来获取真随机数 B.Windows推荐使用随机数生成函数CryptGenRandom0
    C.其他平台可以使用srandom()+random0的方式
    D.开源组件openssl或华为自主封装的iPSl组件

    34.多线程、多进程设计中,什么情况下必须加锁保护 (A,C,D)
    A访问全局变量
    B.调用函数
    C.访问静态变量
    D.操作共享内存

    35.存储在可复用资源中的敏感信息如果没有正确的清除则很有可能被低权限用户或者攻击者所获取和利用。因此敏惑信怠在可复用资源中保存应该讵循存储时问最短原则。可复用资源包括以下几个方面 (A,B,C,D)
    A.堆( heap )
    B.栈(stack )
    C.数据段( data segment]
    D.数据库的映射缓存

    36.关于变量的内存分配区城及作用域的描述,以下说法正确的有 (B,D)
    A.全局变量存放在内存的静态存储区域,在整个工程文件内部有效
    B.静态全局变里存放在内存的静态存储区域,只在定义它的文件内有效
    C.静态局部变量存放在内存的栈区,只在定义它的函数内存效,只是程序仅分配一次内存,函数返回后,该变量不会消失
    D.局部变量存放在内存的栈区,在定义它的函数内有效,但是函数返回后失效

    37.下列代码片断哪些不符合安全编码规范要求 (A)
    A.unsigned int add(unsigned int a, unsigned int b) ( unsigned int sum; sum = a + b; return sum; }
    B. int foo(char* str) ( size_t len = strlen(str); if (len > = MAX_BUF_SIZE - 1) { return -1; | char buf = (char)malloc(len+1); if (buf == NULL) ( return -1: } stncpy[(buf,str, len); buflen] =’ 10’ ;/"对 buf的其它处理*/ free(buf; buf = NULL;}
    C.int file_ops(char* file_name] { FILE fp: fp = fopen(file_name, 'w"): if fp == NULL) ( return -1;}产其他处理*/ if (fclose(fp) ! = 0) { retun -1; } if (remove(file_name) != 0) ( return -1; ] return 0; }
    D. int id_gen0 { enum flen = 12); char idlen]; int r, int num; r = rand0; num = snprintf(id, len “ID%-d”, ri;产生成1D其他处理}

    38.文件I/O安全中,以下哪些是正确的做法 (A,B,C)
    A.使用int类型来接收字符输入/输出函数的返回值
    B.创建文件时,指定了合适的访问权限
    C.文件名称使用白名单字符,杜绝出现"…/…"之类的目录跨越符号
    D.访问时始终使用文件名代替文件描述符,以避免竞争条件

    39.一条语句过长且不能拆分时,应该换行,换行时应参考的规则有 (A,D)
    A.换行时,新行要增加─级缩进,使代码可读性更好
    B.低优先级操作符处划分新行;换行时操作符不应该放下来,放在行尾
    C.换行时应该按照单词换行,在最接近一行的边界处换行
    D.换行是建议一个完整的语句放在一行,不要根据字符数断行

    40.以下关于宏全编码的说法,王确的是 (A,B,D)
    A无论是明文口令还是密文口令,都应该禁止保存在日志文件中
    B.程序在运行时应该只分配能完成其任务的最小权限
    C程序执行任务完毕时,应该尽快收回其权限,对于权限的撤销顺序可以不用考虑
    D如果没有充分考虑字符串的“\0”结束符,则很可能会导致缓冲区溢出等安全漏洞

    41.输入校验中如果没有特殊要求,应当首先考虑来用“白名单”校验形式
    (√)

    42.sizeof(“abcdef”)的输出值是7,strlen(" abcdef”)的输出值是6 (√)

    43.内存释放后,把指针置为NULL;使用内存指针前进行津空判断 (√)

    44.允许返回函数中定义的局部指针变量 (×)

    45.所有的if … else if结构应该由else子句结束; switch语句必须有default分支 (√)

    46.全局变量应增加“S_”前缀 (×)

    47.通讯过程中使用的结构,必须注意字节序 (√)

    48.使用strcpy会有缓冲区溢出的风险,使用strncpy就能避免该风险 (×)

    49.抛出异常时,为了提高传递效率,应该抛出指针 (×)

    50.可以用rand()函数产生用于安全用途的随机数 (×)

    51.把整型表达式比较赋值为一种更大类型之前必须用这种更大类型对它进行求值 (√)

    52.格式化输出函数的格式化参数和实参类型必须匹配 (√)

    53.访问时始终使用文件名代替文件描述符,以避免竞争条件 (×)

    54.对于局部使用的唯一含义的魔鬼数字,可以在代码用困增加说明注释,也可以定义局部const变量 (√)

    56.可重入函数应避免使用共享变量;若需要使用,则必须通过互斥手段((关中断、信号量)对其加以保护 (√)

    57.产品不能使用MD5算法对口令进行加密,原因是MD5算法是对称加密算法 (×)

    58.对于字符数组或动态内存分配,可以不预留’\0’结束符,因为程序员知道该内存区大小,操作时以大小作为边界条件就不会出错 (×)

    59.一个变量只有一个功能,不能把一个变量用作多种用途 (√)

    60.位操作符(~、>>、<<、&、^、)应该只用于无符号整型操作数 (√)

    展开全文
  • 0 规范制订说明 ............................................................................................................................16 业界编程规范 ..................................................
  • 作为程序开发者,避免不了阅读别人代码,那么就会涉及到到一门语言的编程规范。规范虽然不是语言本身的硬性要求,但是已经是每一个语言使用者约定俗成的一个规范。按照编程规范编写的代码,至少在代码阅读时,给人一...

    前言

    作为程序开发者,避免不了阅读别人代码,那么就会涉及到到一门语言的编程规范。规范虽然不是语言本身的硬性要求,但是已经是每一个语言使用者约定俗成的一个规范。按照编程规范编写的代码,至少在代码阅读时,给人一种愉悦的心情,特别是强迫症患者。另一方面,统一的编程风格,可以减少编写错误,利于后期维护。

    因为最近又开始进行纯C语言的开发,并且是基于SDK的开发,所以添加的每一行代码都应该与原来风格保持一致,不能因为一颗老鼠屎坏了一锅汤。一个良好的编程规范也可以看出编程人员的细心程度与代码质量。之前待过的两家公司,也都有各自总结的编程规范,但都不约而同的一致,适用本公司的软件开发。这几天有幸可以参阅华为技术有限公司的C语言编程规范,相比之下,写的更加详细。至少接触到了,在这个编程规范中体现了,并且还扩充了很多,我觉得有必要归纳总结,一遍日后查阅。先是学习规范,然后再积累规范,最后才是依规范编写。

    《C语言编程规范》-华为技术规范

    代码总体原则

    1、清晰第一

    清晰性是易于维护、易于重构的程序必需具备的特征。代码首先是给人读的,好的代码应当可以像文章一样发声朗诵出来。

    2. 简洁为美

    简洁就是易于理解并且易于实现。代码越长越难以看懂,也就越容易在修改时引入错误。写的代码越多,意味着出错的地方越多,也就意味着代码的可靠性越低。因此,我们提倡大家通过编写简洁明了的代码来提升代码可靠性。废弃的代码(没有被调用的函数和全局变量)要及时清除,重复代码应该尽可能提炼成函数。

    3. 选择合适的风格,与代码原有的风格保持一致

    产品所有人共同分享同一种风格所带来的好处,远远超出为了统一而付出的代价。在公司已有编码规范的指导下,审慎地编排代码以使代码尽可能清晰,是一项非常重要的技能。如果重构/修改其他风格的代码时,比较明智的做法是根据现有代码的现有风格继续编写代码,或者使用格式转换工具进行转换成公司内部风格。

    一、 头文件

    原则1.1 头文件中适合放置接口的声明,不适合放置实现。

    说明:头文件是模块(Module)或单元(Unit)的对外接口。头文件中应放置对外部的声明,如对外提供的函数声明、宏定义、类型定义等。

    原则1.2 头文件应当职责单一。

    说明:头文件过于复杂,依赖过于复杂是导致编译时间过长的主要原因。很多现有代码中头文件过大,职责过多,再加上循环依赖的问题,可能导致为了在.c中使用一个宏,而包含十几个头文件。

    原则1.3 头文件应向稳定的方向包含。

    说明:头文件的包含关系是一种依赖,一般来说,应当让不稳定的模块依赖稳定的模块,从而当不稳定的模块发生变化时,不会影响(编译)稳定的模块。

    规则1.1 每一个.c文件应有一个同名.h文件,用于声明需要对外公开的接口。

    说明:如果一个.c文件不需要对外公布任何接口,则其就不应当存在,除非它是程序的入口,如main函数所在的文件。

    规则1.2 禁止头文件循环依赖。

    说明:头文件循环依赖,指a.h包含b.h,b.h包含c.h,c.h包含a.h之类导致任何一个头文件修改,都

    导致所有包含了a.h/b.h/c.h的代码全部重新编译一遍。而如果是单向依赖,如a.h包含b.h,b.h包含

    c.h,而c.h不包含任何头文件,则修改a.h不会导致包含了b.h/c.h的源代码重新编译。

    规则1.3 .c/.h文件禁止包含用不到的头文件。

    说明:很多系统中头文件包含关系复杂,开发人员为了省事起见,可能不会去一一钻研,直接包含一切想到的头文件,甚至有些产品干脆发布了一个god.h,其中包含了所有头文件,然后发布给各个项目组使用,这种只图一时省事的做法,导致整个系统的编译时间进一步恶化,并对后来人的维护造成了巨大的麻烦。

    规则1.4 头文件应当自包含。

    说明:简单的说,自包含就是任意一个头文件均可独立编译。如果一个文件包含某个头文件,还要包含另外一个头文件才能工作的话,就会增加交流障碍,给这个头文件的用户增添不必要的负担。

    规则1.5 总是编写内部#include保护符(#define 保护)。

    说明:多次包含一个头文件可以通过认真的设计来避免。如果不能做到这一点,就需要采取阻止头文件内容被包含多于一次的机制。

    注:没有在宏最前面加上“_",即使用FILENAME_H代替_FILENAME_H_,是因为一般以"_"和”__"开头的标识符为系统保留或者标准库使用,在有些静态检查工具中,若全局可见的标识符以"_"开头会给出告警。

    定义包含保护符时,应该遵守如下规则:

    1)保护符使用唯一名称;

    2)不要在受保护部分的前后放置代码或者注释。

    规则1.6 禁止在头文件中定义变量。

    说明:在头文件中定义变量,将会由于头文件被其他.c文件包含而导致变量重复定义。

    规则1.7 只能通过包含头文件的方式使用其他.c提供的接口,禁止在.c中通过extern的方式使用外部函数接口、变量。

    说明:若a.c使用了b.c定义的foo()函数,则应当在b.h中声明extern int foo(int input);并在a.c中通过#include <b.h>来使用foo。禁止通过在a.c中直接写extern int foo(int input);来使用foo,后面这种写法容易在foo改变时可能导致声明和定义不一致。这一点我们因为图方便经常犯的。

    规则1.8 禁止在extern "C"中包含头文件。

    说明:在extern "C"中包含头文件,会导致extern "C"嵌套,Visual Studio对extern "C"嵌套层次有限制,嵌套层次太多会编译错误。

     

    建议1.1 一个模块通常包含多个.c文件,建议放在同一个目录下,目录名即为模块名。为方便外部使用者,建议每一个模块提供一个.h,文件名为目录名。

    建议1.2 如果一个模块包含多个子模块,则建议每一个子模块提供一个对外的.h,文件名为子模块名。

    建议1.3 头文件不要使用非习惯用法的扩展名,如.inc。

    建议1.4 同一产品统一包含头文件排列方式。

     

    二、 函数

    原则2.1 一个函数仅完成一件功能。

    说明:一个函数实现多个功能给开发、使用、维护都带来很大的困难。

    原则2.2 重复代码应该尽可能提炼成函数。

    说明:重复代码提炼成函数可以带来维护成本的降低。

     

    规则2.1 避免函数过长,新增函数不超过50行(非空非注释行)。

    说明:本规则仅对新增函数做要求,对已有函数修改时,建议不增加代码行。

    规则2.2 避免函数的代码块嵌套过深,新增函数的代码块嵌套不超过4层。

    说明:本规则仅对新增函数做要求,对已有的代码建议不增加嵌套层次。

    规则2.3 可重入函数应避免使用共享变量;若需要使用,则应通过互斥手段(关中断、信号量)对其加以保护。

    规则2.4 对参数的合法性检查,由调用者负责还是由接口函数负责,应在项目组/模块内应统一规定。缺省由调用者负责。

    规则2.5 对函数的错误返回码要全面处理。

    规则2.6 设计高扇入,合理扇出(小于7)的函数。

    说明:扇出是指一个函数直接调用(控制)其它函数的数目,而扇入是指有多少上级函数调用它。如下图:

    规则2.7 废弃代码(没有被调用的函数和变量)要及时清除。

    建议2.1 函数不变参数使用const。

    说明:不变的值更易于理解/跟踪和分析,把const作为默认选项,在编译时会对其进行检查,使代码更牢固/更安全。

    建议2.2 函数应避免使用全局变量、静态局部变量和I/O操作,不可避免的地方应集中使用。

    建议2.3 检查函数所有非参数输入的有效性,如数据文件、公共变量等。

    说明:函数的输入主要有两种:一种是参数输入;另一种是全局变量、数据文件的输入,即非参数输入。函数在使用输入参数之前,应进行有效性检查。

    建议2.4 函数的参数个数不超过5个。

    建议2.5 除打印类函数外,不要使用可变长参函数。

    建议2.6 在源文件范围内声明和定义的所有函数,除非外部可见,否则应该增加static关键字。

     

    三、 标识符命名与定义

    目前比较常用的如下几种命名风格:

    unix like风格:单词用小写字母,每个单词直接用下划线„_‟分割,例如text_mutex,kernel_text_address。

    Windows风格:大小写字母混用,单词连在一起,每个单词首字母大写。不过Windows风格如果遇到大写专有用语时会有些别扭,例如命名一个读取RFC文本的函数,命令为ReadRFCText,看起来就没有unix like的read_rfc_text清晰了。

    原则3.1 标识符的命名要清晰、明了,有明确含义,同时使用完整的单词或大家基本可以理解的缩写,避免使人产生误解。

    原则3.2 除了常见的通用缩写以外,不使用单词缩写,不得使用汉语拼音。

    建议3.1 产品/项目组内部应保持统一的命名风格。

    建议3.2 尽量避免名字中出现数字编号,除非逻辑上的确需要编号。

    建议3.3 标识符前不应添加模块、项目、产品、部门的名称作为前缀。

    建议3.4 平台/驱动等适配代码的标识符命名风格保持和平台/驱动一致。

    建议3.5 重构/修改部分代码时,应保持和原有代码的命名风格一致。

    建议3.6 文件命名统一采用小写字符。

    规则3.2 全局变量应增加“g_”前缀。

    规则3.3 静态变量应增加“s_”前缀。

    规则3.4 禁止使用单字节命名变量,但允许定义i、j、k作为局部循环变量。

    建议3.7 不建议使用匈牙利命名法。

    说明:变量命名需要说明的是变量的含义,而不是变量的类型。在变量命名前增加类型说明,反而降低了变量的可读性;更麻烦的问题是,如果修改了变量的类型定义,那么所有使用该变量的地方都需要修改。

    建议3.8 使用名词或者形容词+名词方式命名变量。

    建议3.9 函数命名应以函数要执行的动作命名,一般采用动词或者动词+名词的结构。

    建议3.10 函数指针除了前缀,其他按照函数的命名规则命名。

    规则3.5 对于数值或者字符串等等常量的定义,建议采用全大写字母,单词之间加下划线„_‟的方式命名(枚举同样建议使用此方式定义)。

    规则3.6 除了头文件或编译开关等特殊标识定义,宏定义不能使用下划线„_‟开头和结尾。

     

    四、变量

    原则4.1 一个变量只有一个功能,不能把一个变量用作多种用途。

    原则4.2 结构功能单一;不要设计面面俱到的数据结构。

    原则4.3 不用或者少用全局变量。

    规则4.1 防止局部变量与全局变量同名。

    规则4.2 通讯过程中使用的结构,必须注意字节序。

    规则4.3 严禁使用未经初始化的变量作为右值。

    建议4.1 构造仅有一个模块或函数可以修改、创建,而其余有关模块或函数只访问的全局变量,防止多个不同模块或函数都可以修改、创建同一全局变量的现象。

    建议4.2 使用面向接口编程思想,通过API访问数据:如果本模块的数据需要对外部模块开放,应提供接口函数来设置、获取,同时注意全局数据的访问互斥。

    建议4.3 在首次使用前初始化变量,初始化的地方离使用的地方越近越好。

    建议4.4 明确全局变量的初始化顺序,避免跨模块的初始化依赖。

    说明:系统启动阶段,使用全局变量前,要考虑到该全局变量在什么时候初始化,使用全局变量和初始化全局变量,两者之间的时序关系,谁先谁后,一定要分析清楚,不然后果往往是低级而又灾难性的。

    建议4.5 尽量减少没有必要的数据类型默认转换与强制转换。

    说明:当进行数据类型强制转换时,其数据的意义、转换后的取值等都有可能发生变化,而这些细节若考虑不周,就很有可能留下隐患。

     

    五、 宏、常量

    规则5.1 用宏定义表达式时,要使用完备的括号。

    说明:因为宏只是简单的代码替换,不会像函数一样先将参数计算后,再传递。

    规则5.2 将宏所定义的多条表达式放在大括号中。

    说明:更好的方法是多条语句写成do while(0)的方式。

    规则5.3 使用宏时,不允许参数发生变化。

    规则5.4 不允许直接使用魔鬼数字。

    说明:使用魔鬼数字的弊端:代码难以理解;如果一个有含义的数字多处使用,一旦需要修改这个数值,代价惨重。使用明确的物理状态或物理意义的名称能增加信息,并能提供单一的维护点。

    建议5.1 除非必要,应尽可能使用函数代替宏。

    说明:宏对比函数,有一些明显的缺点:宏缺乏类型检查,不如函数调用检查严格。

    建议5.2 常量建议使用const定义代替宏。

    建议5.3 宏定义中尽量不使用return、goto、continue、break等改变程序流程的语句。

     

    六、质量保证

    原则6.1 代码质量保证优先原则

    (1)正确性,指程序要实现设计要求的功能。

    (2)简洁性,指程序易于理解并且易于实现。

    (3)可维护性,指程序被修改的能力,包括纠错、改进、新需求或功能规格变化的适应能力。

    (4)可靠性,指程序在给定时间间隔和环境条件下,按设计要求成功运行程序的概率。

    (5)代码可测试性,指软件发现故障并隔离、定位故障的能力,以及在一定的时间和成本前提下,进

    行测试设计、测试执行的能力。

    (6)代码性能高效,指是尽可能少地占用系统资源,包括内存和执行时间。

    (7)可移植性,指为了在原来设计的特定环境之外运行,对系统进行修改的能力。

    (8)个人表达方式/个人方便性,指个人编程习惯。

    原则6.2 要时刻注意易混淆的操作符。比如说一些符号特性、计算优先级。

    原则6.3 必须了解编译系统的内存分配方式,特别是编译系统对不同类型的变量的内存分配规则,如局部变量在何处分配、静态变量在何处分配等。

    原则6.4 不仅关注接口,同样要关注实现。

    说明:这个原则看似和“面向接口”编程思想相悖,但是实现往往会影响接口,函数所能实现的功能,除了和调用者传递的参数相关,往往还受制于其他隐含约束,如:物理内存的限制,网络状况,具体看“抽象漏洞原则”。

    规则6.1 禁止内存操作越界。

    坚持下列措施可以避免内存越界:

    • 数组的大小要考虑最大情况,避免数组分配空间不够。
    • 避免使用危险函数sprintf /vsprintf/strcpy/strcat/gets操作字符串,使用相对安全的函数snprintf/strncpy/strncat/fgets代替。
    • 使用memcpy/memset时一定要确保长度不要越界
    • 字符串考虑最后的’\0’, 确保所有字符串是以’\0’结束
    • 指针加减操作时,考虑指针类型长度
    • 数组下标进行检查
    • 使用时sizeof或者strlen计算结构/字符串长度,避免手工计算

    规则6.2 禁止内存泄漏

    坚持下列措施可以避免内存泄漏:

    • 异常出口处检查内存、定时器/文件句柄/Socket/队列/信号量/GUI等资源是否全部释放
    • 删除结构指针时,必须从底层向上层顺序删除
    • 使用指针数组时,确保在释放数组时,数组中的每个元素指针是否已经提前被释放了
    • 避免重复分配内存
    • 小心使用有return、break语句的宏,确保前面资源已经释放
    • 检查队列中每个成员是否释放

    规则6.3 禁止引用已经释放的内存空间。

    • 坚持下列措施可以避免引用已经释放的内存空间:
    • 内存释放后,把指针置为NULL;使用内存指针前进行非空判断。
    • 耦合度较强的模块互相调用时,一定要仔细考虑其调用关系,防止已经删除的对象被再次使用。
    • 避免操作已发送消息的内存。
    • 自动存储对象的地址不应赋值给其他的在第一个对象已经停止存在后仍然保持的对象(具有更大作用域的对象或者静态对象或者从一个函数返回的对象)

    规则6.4 编程时,要防止差1错误。

    说明:此类错误一般是由于把“<=”误写成“<”或“>=”误写成“>”等造成的,由此引起的后果,很多情况下是很严重的,所以编程时,一定要在这些地方小心。当编完程序后,应对这些操作符进行彻底检查。使用变量时要注意其边界值的情况。

    建议6.1 函数中分配的内存,在函数退出之前要释放。

    说明:有很多函数申请内存,保存在数据结构中,要在申请处加上注释,说明在何处释放。

    建议6.2 if语句尽量加上else分支,对没有else分支的语句要小心对待。

    建议6.3 不要滥用goto语句。

    说明:goto语句会破坏程序的结构性,所以除非确实需要,最好不使用goto语句。

    建议6.4 时刻注意表达式是否会上溢、下溢。

     

    七、 程序效率

    原则7.1 在保证软件系统的正确性、简洁、可维护性、可靠性及可测性的前提下,提高代码效率。

    原则7.2 通过对数据结构、程序算法的优化来提高效率。

    建议7.1 将不变条件的计算移到循环体外。

    建议7.2 对于多维大数组,避免来回跳跃式访问数组成员。

    建议7.3 创建资源库,以减少分配对象的开销。

    建议7.4 将多次被调用的 “小函数”改为inline函数或者宏实现。

     

    八、 注释

    原则8.1 优秀的代码可以自我解释,不通过注释即可轻易读懂。

    说明:优秀的代码不写注释也可轻易读懂,注释无法把糟糕的代码变好,需要很多注释来解释的代码往往存在坏味道,需要重构。

    原则8.2 注释的内容要清楚、明了,含义准确,防止注释二义性。

    原则8.3 在代码的功能、意图层次上进行注释,即注释解释代码难以直接表达的意图,而不是重复描述代码。

    规则8.1 修改代码时,维护代码周边的所有注释,以保证注释与代码的一致性。不再有用的注释要删除。

    规则8.2 文件头部应进行注释,注释必须列出:版权说明、版本号、生成日期、作者姓名、工号、内容、功能说明、与其它文件的关系、修改日志等,头文件的注释中还应有函数功能简要说明。

    规则8.3 函数声明处注释描述函数功能、性能及用法,包括输入和输出参数、函数返回值、可重入的要求等;定义处详细描述函数功能和实现要点,如实现的简要步骤、实现的理由、设计约束等。

    规则8.4 全局变量要有较详细的注释,包括对其功能、取值范围以及存取时注意事项等的说明。

    规则8.5 注释应放在其代码上方相邻位置或右方,不可放在下面。如放于上方则需与其上面的代码用空行隔开,且与下方代码缩进相同。

    规则8.6 对于switch语句下的case语句,如果因为特殊情况需要处理完一个case后进入下一个case处理,必须在该case语句处理完、下一个case语句前加上明确的注释。

    规则8.7 避免在注释中使用缩写,除非是业界通用或子系统内标准化的缩写。

    规则8.8 同一产品或项目组统一注释风格。

    建议8.1 避免在一行代码或表达式的中间插入注释。

    建议8.2 注释应考虑程序易读及外观排版的因素,使用的语言若是中、英兼有的,建议多使用中文,除非能用非常流利准确的英文表达。对于有外籍员工的,由产品确定注释语言。

    建议8.3 文件头、函数头、全局常量变量、类型定义的注释格式采用工具可识别的格式。

    说明:采用工具可识别的注释格式,例如doxygen格式,方便工具导出注释形成帮助文档。

    九、 排版与格式

    规则9.1 程序块采用缩进风格编写,每级缩进为4个空格。

    说明:当前各种编辑器/IDE都支持TAB键自动转空格输入,需要打开相关功能并设置相关功能。编辑器/IDE如果有显示TAB的功能也应该打开,方便及时纠正输入错误。

    规则9.2 相对独立的程序块之间、变量说明之后必须加空行。

    规则9.3 一条语句不能过长,如不能拆分需要分行写。一行到底多少字符换行比较合适,产品可以自行确定。

    换行时有如下建议:

    • 换行时要增加一级缩进,使代码可读性更好;
    • 低优先级操作符处划分新行;换行时操作符应该也放下来,放在新行首;
    • 换行时建议一个完整的语句放在一行,不要根据字符数断行

    规则9.4 多个短语句(包括赋值语句)不允许写在同一行内,即一行只写一条语句。

    规则9.5 if、for、do、while、case、switch、default等语句独占一行。

    规则9.6 在两个以上的关键字、变量、常量进行对等操作时,它们之间的操作符之前、之后或者前后要加空格;进行非对等操作时,如果是关系密切的立即操作符(如->),后不应加空格。

    建议9.1 注释符(包括„/*‟„//‟„*/‟)与注释内容之间要用一个空格进行分隔。

    建议9.2 源程序中关系较为紧密的代码应尽可能相邻。

     

    十、 表达式

    规则10.1 表达式的值在标准所允许的任何运算次序下都应该是相同的。

    建议10.1 函数调用不要作为另一个函数的参数使用,否则对于代码的调试、阅读都不利。

    建议10.2 赋值语句不要写在if等语句中,或者作为函数的参数使用。

    建议10.4 赋值操作符不能使用在产生布尔值的表达式上。

    十一、 代码编辑、编译

    规则11.1 使用编译器的最高告警级别,理解所有的告警,通过修改代码而不是降低告警级别来消除所有告警。

    规则11.2 在产品软件(项目组)中,要统一编译开关、静态检查选项以及相应告警清除策略。

    规则11.3 本地构建工具(如PC-Lint)的配置应该和持续集成的一致。

    规则11.4 使用版本控制(配置管理)系统,及时签入通过本地构建的代码,确保签入的代码不会影响构建成功。

    建议11.1 要小心地使用编辑器提供的块拷贝功能编程。

     

    十二、 可测性

    原则12.1 模块划分清晰,接口明确,耦合性小,有明确输入和输出,否则单元测试实施困难。

    说明:单元测试实施依赖于:

    • 模块间的接口定义清楚、完整、稳定;
    • 模块功能的有明确的验收条件(包括:预置条件、输入和预期结果);
    • 模块内部的关键状态和关键数据可以查询,可以修改;
    • 模块原子功能的入口唯一;
    • 模块原子功能的出口唯一;
    • 依赖集中处理:和模块相关的全局变量尽量的少,或者采用某种封装形式。

    规则12.1 在同一项目组或产品组内,要有一套统一的为集成测试与系统联调准备的调测开关及相应打印函数,并且要有详细的说明。

    规则12.2 在同一项目组或产品组内,调测打印的日志要有统一的规定。

    说明:统一的调测日志记录便于集成测试,具体包括:

    • 统一的日志分类以及日志级别;
    • 通过命令行、网管等方式可以配置和改变日志输出的内容和格式;
    • 在关键分支要记录日志,日志建议不要记录在原子函数中,否则难以定位;
    • 调试日志记录的内容需要包括文件名/模块名、代码行号、函数名、被调用函数名、错误码、错误发生的环境等。

    规则12.3 使用断言记录内部假设。

    规则12.4 不能用断言来检查运行时错误。

    说明:断言是用来处理内部编程或设计是否符合假设;不能处理对于可能会发生的且必须处理的情况要写防错程序,而不是断言。如某模块收到其它模块或链路上的消息后,要对消息的合理性进行检查,此过程为正常的错误检查,不能用断言来实现。

    建议12.1 为单元测试和系统故障注入测试准备好方法和通道。

     

    十三、 安全性

    原则13.1 对用户输入进行检查。

    说明:不能假定用户输入都是合法的,因为难以保证不存在恶意用户,即使是合法用户也可能由于误用误操作而产生非法输入。用户输入通常需要经过检验以保证安全,特别是以下场景:

    • 用户输入作为循环条件
    • 用户输入作为数组下标
    • 用户输入作为内存分配的尺寸参数
    • 用户输入作为格式化字符串
    • 用户输入作为业务数据(如作为命令执行参数、拼装sql语句、以特定格式持久化)

    这些情况下如果不对用户数据做合法性验证,很可能导致DOS、内存越界、格式化字符串漏洞、命令注入、SQL注入、缓冲区溢出、数据破坏等问题。

    可采取以下措施对用户输入检查:

    • 用户输入作为数值的,做数值范围检查
    • 用户输入是字符串的,检查字符串长度
    • 用户输入作为格式化字符串的,检查关键字“%”
    • 用户输入作为业务数据,对关键字进行检查、转义

    规则13.1 确保所有字符串是以NULL结束。

    说明: C语言中‟\0‟作为字符串的结束符,即NULL结束符。标准字符串处理函数(如strcpy()、 strlen())

    依赖NULL结束符来确定字符串的长度。没有正确使用NULL结束字符串会导致缓冲区溢出和其它未定义

    的行为。

    为了避免缓冲区溢出,常常会用相对安全的限制字符数量的字符串操作函数代替一些危险函数。如:

    • 用strncpy()代替strcpy()
    • 用strncat()代替strcat()
    • 用snprintf()代替sprintf()
    • 用fgets()代替gets()

    这些函数会截断超出指定限制的字符串,但是要注意它们并不能保证目标字符串总是以NULL结尾。如果源字符串的前n个字符中不存在NULL字符,目标字符串就不是以NULL结尾。

    规则13.2 不要将边界不明确的字符串写到固定长度的数组中。

    说明:边界不明确的字符串(如来自gets()、getenv()、scanf()的字符串),长度可能大于目标数组长度,直接拷贝到固定长度的数组中容易导致缓冲区溢出。

    规则13.3 避免整数溢出。

    说明:当一个整数被增加超过其最大值时会发生整数上溢,被减小小于其最小值时会发生整数下溢。带符号和无符号的数都有可能发生溢出。

    规则13.4 避免符号错误。

    说明:有时从带符号整型转换到无符号整型会发生符号错误,符号错误并不丢失数据,但数据失去了原来的含义。

    带符号整型转换到无符号整型,最高位(high-order bit)会丧失其作为符号位的功能。如果该带符号整数的值非负,那么转换后值不变;如果该带符号整数的值为负,那么转换后的结果通常是一个非常大的正数。

    规则13.5:避免截断错误。

    说明:将一个较大整型转换为较小整型,并且该数的原值超出较小类型的表示范围,就会发生截断错误,原值的低位被保留而高位被丢弃。截断错误会引起数据丢失。使用截断后的变量进行内存操作,很可能会引发问题。

    规则13.6:确保格式字符和参数匹配。

    说明:使用格式化字符串应该小心,确保格式字符和参数之间的匹配,保留数量和数据类型。格式字符和参数之间的不匹配会导致未定义的行为。大多数情况下,不正确的格式化字符串会导致程序异常终止。

    规则13.7 避免将用户输入作为格式化字符串的一部分或者全部。

    说明:调用格式化I/O函数时,不要直接或者间接将用户输入作为格式化字符串的一部分或者全部。攻击者对一个格式化字符串拥有部分或完全控制,存在以下风险:进程崩溃、查看栈的内容、改写内存、甚至执行任意代码。

    规则13.8 避免使用strlen()计算二进制数据的长度。

    说明:strlen()函数用于计算字符串的长度,它返回字符串中第一个NULL结束符之前的字符的数量。因此用strlen()处理文件I/O函数读取的内容时要小心,因为这些内容可能是二进制也可能是文本。

    规则13.9 使用int类型变量来接受字符I/O函数的返回值。

    规则13.10 防止命令注入。

    说明:C99函数system()通过调用一个系统定义的命令解析器(如UNIX的shell,Windows的CMD.exe)来执行一个指定的程序/命令。类似的还有POSIX的函数popen()。

     

    十四、 单元测试

    规则14.1 在编写代码的同时,或者编写代码前,编写单元测试用例验证软件设计/编码的正确。

    建议14.1 单元测试关注单元的行为而不是实现,避免针对函数的测试。

    说明:应该将被测单元看做一个被测的整体,根据实际资源、进度和质量风险,权衡代码覆盖、打桩工作量、补充测试用例的难度、被测对象的稳定程度等,一般情况下建议关注模块/组件的测试,尽量避免针对函数的测试。尽管有时候单个用例只能专注于对某个具体函数的测试,但我们关注的应该是函数的行为而不是其具体实现细节。

     

    十五、 可移植性

    规则15.1 不能定义、重定义或取消定义标准库/平台中保留的标识符、宏和函数。

    建议15.1 不使用与硬件或操作系统关系很大的语句,而使用建议的标准语句,以提高软件的可移植性和可重用性。

    建议15.2 除非为了满足特殊需求,避免使用嵌入式汇编。

    说明:程序中嵌入式汇编,一般都对可移植性有较大的影响。

    展开全文
  • 华为C语言编程规范

    千次阅读 2019-07-14 15:35:07
    对于C语言来说,头文件的设计体现了大部分的系统设计。 原则1.1 头文件中适合放置接口的声明,不适合放置实现。 说明:头文件是模块(Module)或单元(Unit)的对外接口。头文件中应放置对外部的声明,如对外提供的...
  • C语言编程规范—标识符的命名规则。
  • C语言编程规范 — 变量、常量、表达式、控制语句的总结归纳。
  • 高质量C++/C 编程指南文件状态 文件标识:[ ] 草稿文件 当前版本: 1.0[√] 正式文件 作 者: 林锐 博士[ ] 更改正式文件 完成日期: 2001 年7 月 24 日高质量C++/C 编程指南,v1....
  • C语言编程规范 clean code

    千次阅读 2021-02-20 09:05:12
    参考该规范之前,希望您具有相应的C语言基础能力,而不是通过该文档来学习C语言。 了解C语言的ISO标准; 熟知C语言的基本语言特性; 了解C语言的标准库; 总体原则 代码需要在保证功能正确的前提下,满足可读、可...
  • C语言编程规范

    千次阅读 2018-07-08 09:26:45
    16业界编程规范 代码总体原则 1、清晰第一 ​ 清晰性是易于维护、易于重构的程序必需具备的特征。 代码首先是给人读的,一般情况下,代码的可阅读性高于性能,只有确定性能是瓶颈时,才...
  • C语言编程规范

    千次阅读 2014-11-21 14:02:02
    为提高产品代码质量,指导广大软件开发人员...代码,编程规范修订工作组分析、总结了我司的各种典型编码问题,并参考了业界编程规范近年来的 成果,重新对我司1999年版编程规范进行了梳理、优化、刷新,编写了本规范。
  • 3.5 命名规范必须与所使用的系统风格保持一致, 并在同一项目中统一, 比如采用UNIX的全小写加下划线的 风格或大小写混排的方式, 不要使用大小写与下划线混排的方式, 用作特殊标识如标识成员变量或全局 变量的m_...
  • 本规范主要借鉴了一些大公司(华为,百度,腾讯,阿里巴巴,谷歌,苹果,微软,ARM,ST.....)的编程规范和风格,吸收它们的共性和个别优秀的地方。 本人曾经创造过自己编程风格,也曾想创造出世界上最完美的编程...
  • 华为代码规范

    2020-03-08 23:38:32
    华为代码规范总体原则1 头文件2 函数3 标识符命名与定义4 变量5 宏、常量6 质量保证7 程序效率8 注释9 排版与格式10 表达式11 代码编辑、编译12 可测性13 安全性14 单元测试15 可移植性 总体原则 1、清晰第一 2、...
  • 华为FPGA设计设计规范

    千次阅读 2020-05-28 23:10:00
    最近看到了一份华为早年的FPGA设计规范文档,分享给大家。前言本部门所承担的FPGA设计任务主要是两方面的作用:系统的原型实现和ASIC的原型验证。编写本流程的目的是:在于规范整个设计流...
  • 华为名师揭秘编程界“网红”Python

    万次阅读 多人点赞 2021-05-04 10:09:57
    2.4 Python为何受人追捧2.5 Python并不是十全十美2.6 Python与其他语言对比分析3 Python广泛的应用领域3.1 Python与人工智能3.2 Python与大数据3.3 Python与爬虫3.4 Python与网站开发3.5 Python与数据分析4 如何成长...
  • 这是因为当不小心将两个等号写为一个等号时,变量在前面就会导致编译不通,从而快速发现错误(这种写法属于华为公司内部的一条编程规范). 2.10.4 逻辑运算符与逻辑表达式 逻辑运算符!、&&、ll依次为逻辑非、逻辑与、...
  • 嵌入式 Linux C 代码规范和风格

    千次阅读 2022-05-05 17:53:30
    前言第一章 规范说明第二章 排版格式和注释2.1 排版2.1.1 代码缩进2.1.2 括号与空格2.1.3 代码行相关规范2.2 注释2.2.1 注释风格2.2.2 文件信息注释2.2.3 函数的注释第三章 标识符命名3.1 命名规则3.2 文件命名3.3 ...
  • 一位深圳的球友,上周去华为的面经,准备不充分,最后就是面试没通过。昨晚上,咱们通了电话,一起总结了这次面试失败的原因。失败的原因有三点:1.自我介绍现场发挥(临时组织语言,时间太短)2.八股文背的太少3....
  • 什么是变量 在内存中开辟特定大小的空间,用来保存数据 所有的变量,本质都是要在内存中的某个位置开辟空间的! 变量的定义与声明 ...很不幸,在C语言中,初始化和赋值的差别不是很大,唯一可能会出现的差..
  • 华为、腾讯C++编码规范

    千次阅读 2019-03-23 10:49:34
    一:华为公司程序设计风格 1. 排版 1.1 程序块要采用缩进风格编写, 缩进的空格数为4个。 说明: 对于由开发工具自动生成的代码可以有不一致。 1.2 相对独立的程序块之间、变量说明之后必须加空行...
  • 华为代码规范文档

    千次阅读 2013-07-12 19:18:50
    代码规范文档                 目录 1 概述............................................................................................................................................

空空如也

空空如也

1 2 3 4 5 ... 14
收藏数 279
精华内容 111
热门标签
关键字:

华为c语言编程规范v3.2