C语言编程规范v1.0

上传人:黑** 文档编号:55772470 上传时间:2022-02-18 格式:DOCX 页数:56 大小:74.24KB
收藏 版权申诉 举报 下载
C语言编程规范v1.0_第1页
第1页 / 共56页
C语言编程规范v1.0_第2页
第2页 / 共56页
C语言编程规范v1.0_第3页
第3页 / 共56页
资源描述:

《C语言编程规范v1.0》由会员分享,可在线阅读,更多相关《C语言编程规范v1.0(56页珍藏版)》请在装配图网上搜索。

1、深圳市吉祥腾达科技有限公司 C语言编程规范 修订记录 版本 修改原因/内容 作者 口期 批准 VI. 0 初始版本:参考业界普遍使用的原则、 规则、建议。 鲁超军 2015-09-23 原则2.1 一个函数仅完成一件功能。 说明:一个函数实现多个功能给开发、使用、维护都带来很大的困难。将没有关联或者关联 很弱的语句放到同一函数中,会导致函数职责不明确,难以理解,难以测试和改动。 案例:realloco在标准C语言中,realloc是一个典型的不良设计。这个函数基本功能是重新 分配内存,但它承担了太多的其他任务:如

2、果传入的指针参数为NULL就分配内存,如果传 入的大小参数为0就释放内存,如果可行则就地重新分配,如果不行则移到其他地方分配。 如果没有足够可用的内存用来完成重新分配(扩大原来的内存块或者分配新的内存块),则 返回NULL,而原来的内存块保持不变。这个函数不易扩展,容易导致问题。例如下面代码 容易导致内存泄漏: char * buffer = (char *)malloc(XXX_SIZE); buffer = (char *)realloc(buffer, NEW_SIZE); 如果没有足够可用的内存用来完成重新分配,函数返回为NULL,导致buffer原来指向的内 存被丢失

3、。 延伸阅读材料:《敏捷软件开发:原则、模式与实践》第八章,单一职责原则(SRP) 原则2.2重复代码应该尽可能提炼成函数。 说明:重复代码提炼成函数可以带来维护成本的降低。在“代码能用就不改”的指导原则之 下,大量的烟囱式设计及其实现充斥着各产品代码之中。新需求增加带来的代码拷贝和修改, 随着时间的迁移,产品中堆砌着许多类似或者重复的代码。 当一段代码重复两次时,即应考虑消除重复,当代码重复超过三次时,应当立刻着手消除重 复。一般情况下,可以通过提炼函数的形式消除重复代码。 规则2.1避免函数过长,新增函数不超过50行(非空非注释行)。 说明:本规则仅对新增函数做要求,对已

4、有函数修改时,建议不增加代码行。 过长的函数往往意味着函数功能不单一,过于复杂(参见原则2.1: 一个函数只完成一个功 能)。 函数的有效代码行数,即NBNC (非空非注释行)应当在[1, 50呕间。 例外:某些实现算法的函数,由于算法的聚合性与功能的全面性,可能会超过50行。 延伸阅读材料:业界普遍认为一个函数的代码行不要超过一个屏幕,避免来回翻页影响阅读; 一般的代码度量工具建议都对此进行检查,例如Logiscope的函数度量: "Number of Statement"(函数中的可执行语句数)建议不超过20行,QAC建议一个函数中 的所有行数(包括注释和空白行)不超过50

5、行。 规则2.2避免函数的代码块嵌套过深,新增函数的代码块嵌套不超过4层。 说明:本规则仅对新增函数做要求,对已有的代码建议不增加嵌套层次。函数的代码块嵌套 深度指的是函数中的代码控制块(例如:if、for、whiles switch等)之间互相包含的深度。 每级嵌套都会增加阅读代码时的脑力消耗,因为需要在脑子里维护一个“栈”(比如,进入 条件语句、进入循环)。应该做进一步的功能分解,从而避免使代码的阅读者一次记住太多 的上下文。优秀代码参考值:[1,4]。 规则2.3可重入函数应避免使用共享变量;若需要使用,则应通过互斥手段(关中断、信号 量)对其加以保护。 说明:可重入函

6、数是指可能被多个任务并发调用的函数。在多任务操作系统中,函数具有可 重入性是多个任务可以共用此函数的必要条件。共享变量指的全局变量和static变量。 编写C语言的可重入函数时,不应使用static局部变量,否则必须经过特殊处理,才能使函 数具有可重入性。 示例:函数square_exam返回g_exam平方值。那么如下函数不具有可重入性。 int g_exam: unsigned int example( int para ) ( unsigned int temp; g_exam = para; // (**) temp 二 square_exam (); retu

7、rn temp; } 此函数若被多个线程调用的话,其结果可能是未知的,因为当(**)语句刚执行完后,另外 一个使用本函数的线程可能正好被激活,那么当新激活的线程执行到此函数时,将使g_exam 赋于另一个不同的para值,所以当控制重新回到“temp =square_exam ()”后,计算出的temp 很可能不是预想中的结果。此函数应如下改进。 int g_exam; unsigned int example( int para ) ( unsigned int temp; [申请信号量操作] //若申请不到“信号量。说明另外的进程正处于 g.exam = para;

8、//给g_exam赋值并计算其平方过程中(即正在使用此 temp二square_exani( ); //信号),本进程必须等待其释放信号后,才可继 [释放信号量操作]//续执行。其它线程必须等待本线程释放信号量后 //才能再使用本信号。 return temp; 规则2.4对参数的合法性检查,由调用者负责还是由接口函数负责,应在项目组/模块内应 统一规定。缺省由调用者负责。 说明:对于模块间接口函数的参数的合法性检查这一问题,往往有两个极端现象,即:要么 是调用者和被调用者对参数均不作合法性检查,结果就遗漏了合法性检查这一必要的处理过 程,造成问题隐患;要么就是调用者和被调用

9、者均对参数进行合法性检查,这种情况虽不会 造成问题,但产生了冗余代码,降低了效率。 规则2.5对函数的错误返回码要全面处理。 说明:一个函数(标准库中的函数/第三方库函数/用户定义的函数)能够提供一些指示错误 发生的方法。这可以通过使用错误标记、特殊的返回数据或者其他手段,不管什么时候函数 提供了这样的机制,调用程序应该在函数返回时立刻检查错误指示。 示例:下面的代码导致宕机 FILE *fp = fopen( /writeAlarmLastTime, log", "r"); if(fp = NULL) ( return; } char buff [128]= fs

10、canf (fp,

11、 (fp, buff) = EOF) 检查函数fscanf的返回值,确保读到数据 ( fclose (fp); return; } fclose(fp); long fileTime = getAlarmTime (buff) ; //解析获职最新的告警时间; 规则2.6设计高扇入,合理扇出(小于7)的函数。 说明:扇出是指一个函数直接调用其它函数的数目,而扇入是指有多少上级函数调用它。 扇出过大,表明函数过分复杂,需要控制和协调过多的下级函数;而扇出过小,例如:总是 1,表明函数的调用层次可能过多,这样不利于程序阅读和函数结构的分析,并且程序运行 时会对系统资源如堆栈空

12、间等造成压力。通常函数比较合理的扇出(调度函数除外)通常是 3~5。 扇出太大,一般是由于缺乏中间层次,可适当增加中间层次的函数。扇出太小,可把下级函 数进一步分解多个函数,或合并到上级函数中。当然分解或合并函数时,不能改变要实现的 功能,也不能违背函数间的独立性。 扇入越大,表明使用此函数的上级函数越多,这样的函数使用效率高,但不能违背函数间的 独立性而单纯地追求高扇入。公共模块中的函数及底层函数应该有较高的扇入。 较良好的软件结构通常是顶层函数的扇出较高,中层函数的扇出较少,而底层函数则扇入到 公共模块中。 规则2.7废弃代码(没有被调用的函数和变量)要及时清除。 说明

13、:程序中的废弃代码不仅占用额外的空间,而且还常常影响程序的功能与性能,很可能 给程序的测试、维护等造成不必要的麻烦。(代码一经生成即是成本) 建议2.1函数不变参数使用consto 说明:不变的值更易于理解/跟踪和分析,把const作为默认选项,在编译时会对其进行检 查,使代码更牢固/更安全。 示例: C99标准7.21.4.4中strncmp的例子,不变参数声明为consto int strncmp(const char *s1, const char *s2, register size_t n) ( register unsigned chatr ul, u2; w

14、hile (n— > 0) { ul = (unsigned char) *sl++; u2 = (unsigned char) *s2++; if (ul != u2) { return ul — u2; } if* (ul == ' \0') { return 0: } } return 0; } 延伸阅读:pc-lint 8.0 的帮助材料(pc-lint.pdf) 11.4 const Checking 建议2.2函数应避免使用全局变量、静态局部变量和I/O操作,不可避免的地方应集中使用。 说明:带有内部“存储器”的函数的功能可能是不可预测的,因为它的

15、输出可能取决于内部 存储器(如某标记)的状态。这样的函数既不易于理解又不利于测试和维护。在C语言中, 函数的static局部变量是函数的内部存储器,有可能使函数的功能不可预测,然而,当某函 数的返回值为指针类型时,则必须是static的局部变量的地址作为返回值,若为auto类,则 返回为错针。 示例:如下函数,其返回值(即功能)是不可预测的。 unsigned int integer_sum Q unsigned int base ) ( unsigned int index; static unsigned int sum = 0; / 注意,是static类型的。 //

16、若改为冲七歧型,则函数即变为可预测。 for (index = 1; index <= base; index++) ( sum += index: } return sum; } 延伸阅读材料:erlang语言中关于dirty的概念,函数式语言的优势。 建议2.3检查函数所有非参数输入的有效性,如数据文件、公共变量等。 说明:函数的输入主要有两种:一种是参数输入;另一种是全局变量、数据文件的输入,即 非参数输入。函数在使用输入参数之前,应进行有效性检查。 示例:下面的代码导致宕机 hr = root_node->get_first_child(&log_item)

17、; // list, xml 为空,导致读出log_item为空 • ■ . • • hr 二 log_item->get_next_sibling(&media_next_node) : .// log_item为空,导致宕机 正确写法:确保读出的内容非空。 hr = root_node->get_first_child(&log_item); • ■ • • • if (log_item = NULL) //确保读出的内容非空 ( return retValue; } hr 二 log_item->get_next_sibling(&media_next_node);

18、 建议2.4函数的参数个数不超过5个。 说明:函数的参数过多,会使得该函数易于受外部(其他部分的代码)变化的影响,从而影 响维护工作。函数的参数过多同时也会增大测试的工作量。函数的参数个数不要超过5个, 如果超过了建议拆分为不同函数。 建议2.5除打印类函数外,不要使用可变长参函数。 说明:可变长参函数的处理过程比较复杂容易引入错误,而且性能也比较低,使用过多的可 变长参函数将导致函数的维护难度大大增加。 建议2.6源文件范围内声明和定义的所有函数,除非外部可见,否则应该增加static关键字。 说明:如果一个函数只是在同一文件中的其他地方调用,那么就用static声明。使用s

19、tatic 确保只是在声明它的文件中是可见的,并且避免了和其他文件或库中的相同标识符发生混淆 的可能性。 建议定义一个STATIC宏,在调试阶段,将STATIC定义为static,版本发布时,改为空,以便 于后续的打热补丁等操作。 #ifdef _DEBUG #define STATIC static #else #define STATIC ftendif 3标识符命名与定义 3.1通用命名规则 原则3.1标识符的命名要清晰、明了,有明确含义,同时使用完整的单词或大家基本可以理 解的缩写,避免使人产生误解。 说明:尽可能给出描述性名称,不要节约空间,让别人很快理解

20、你的代码更重要。 示例:好的命名: int error_number; int number_of_completed_connection; 不好的命名:使用模糊的缩写或随意的字符: int n; int nerr; int n_comp_conns; 原则3.2除了常见的通用缩写以外,不使用单词缩写,不得使用汉语拼音。 说明:较短的单词可通过去掉“元音”形成缩写,较长的单词可取单词的头几个字母形成缩 写,一些单词有大家公认的缩写,常用单词的缩写必须统一。协议中的单词的缩写与协议保 持一致。对于某个系统使用的专用缩写应该在注视或者某处做统一说明。 一些常见可以缩写的

21、例子: argument可缩写为arg buffer可缩写为buff clock可缩写为elk command可缩写为emd compare可缩写为emp configuration 可缩写为 cfg device可缩写为dev error可缩写为err hexadecimal 可缩写为 hex increment可缩写为inc initialize 可缩写为 init maximum 可缩写为 max message可缩写为msg minimum 可缩写为 min parameter 可缩写为 para previous可缩写为prev register可缩

22、写为reg semaphore 可缩写为 sem statistic 可缩写为 stat synchronize 可缩写为 sync temp可缩写为tmp 规则3.1产品/项目组内部应保持统一的命名风格。 说明:Unix like和windows like风格均有其拥楚,产品应根据自己的部署平台,选择其中一 种,并在产品内部保持一致。 例外:即使产品之前使用匈牙利命名法,新代码也不应当使用。 建议3.1用正确的反义词组命名具有互斥意义的变量或相反动作的函数等。 示例: add/remove get/release open/close source/target

23、 up/down begin/end create/destroy increment/decrement put/get min/max old/new show/hide send/receive insert/delete add/delete first/1 a st lock/unlock sta rt/sto p n ext/p revious source/destination copy/paste 建议3.2尽量避免名字中出现数字编号,除非逻辑上的确需要编号。 示例:如下命名,使人产生疑惑。 #define EXAMPLE_O_TEST_ #

24、define EXAMPLE_1_TEST_ 应改为有意义的单词命名 #define EXAMPLE_UNIT_TEST_ #define EXAMPLE_ASSERT_TEST_ 建议3.3平台/驱动等适配代码的标识符命名风格保持和平台/驱动一致。 说明:涉及到外购芯片以及配套的驱动,这部分的代码变动(包括为产品做适配的新增代码), 应该保持原有的风格。 建议3.4重构/修改部分代码时,应保持和原有代码的命名风格一致。 说明:根据源代码现有的风格继续编写代码,有利于保持总体一致。 3.2文件命名规则 建议3.5文件命名统一采用小写字符。 说明:因为不同系统对文件名大小写

25、处理会不同(如MS的DOS、Windows系统不区分大小 写,但是Linux系统则区分),所以代码文件命名建议统一采用全小写字母命名。 3.3变量命名规则 规则3.2全局变量应增加“g_”前缀。 规则3.3静态变量应增加“s_”前缀。 说明:增加g_前缀或者s_前缀,原因如下: 首先,全局变量十分危险,通过前缀使得全局变量更加醒目,促使开发人员对这些变量的使 用更加小心。 规则3.4禁止使用单字节命名变量,但允许定义i、j、k作为局部循环变量。 建议3.6不建议使用匈牙利命名法。 说明:变量命名需要说明的是变量的含义,而不是变量的类型。在变量命名前增加类型说明, 反而降低

26、了变量的可读性;更麻烦的问题是,如果修改了变量的类型定义,那么所有使用该 变量的地方都需要修改。 匈牙利命名法源于微软,然而却被很多人以讹传讹的使用。而现在即使是微软也不再推荐使 用匈牙利命名法。历来对匈牙利命名法的一大诟病,就是导致了变量名难以阅读,这和本规 范的指导思想也有冲突,所以本规范特意强调,变量命名不应采用匈牙利命名法,而应想法 使变量名为一个有意义的词或词组,方便代码的阅读。 建议3.7使用名词或者形容词+名词方式命名变量。 3.4函数命名规则 建议3.8函数命名应以函数要执行的动作命名,一般采用动词或者动词+名词的结构。 示例:找到当前进程的当前目录 DW

27、ORD GetCurrentDirectory( DWORD BufferLength, LPTSTR Buffer); 建议3.9函数指针除了前缀,其他按照函数的命名规则命名。 3.5宏的命名规则 规则3.5对于数值或者字符串等等常量的定义,建议采用全大写字母,单词之间加下划线〃一〃 的方式命名(枚举同样建议使用此方式定义)。 示例:#define PI_ROUNDED 3.14 规则3.6除了头文件或编译开关等特殊标识定义,宏定义不能使用下划线开头和结尾。 说明:一般来说,开头、结尾的宏都是一些内部的定义,ISO/IEC 9899 (俗称C99)中有 如下的描述(6.10.

28、8 Predefined macro names): None of these macro names, nor the identifier defined, shall be the subject of a ftdefine o r a #undef preprocessing directive. Any other predefined macro names shall begin with a le ading underscore followed by an uppercase letter or a second underscore. 延伸阅读材料:《代码大全第

29、2版》(Steve McConnell著金戈/汤凌/陈硕/张菲译电子工业出 版社2006年3月)“第11章变量命的力量“。 4变量 原则4.1 一个变量只有一个功能,不能把一个变量用作多种用途。 说明:一个变量只用来表示一个特定功能,不能把一个变量作多种用途,即同一变量取值不 同时,其代表的意义也不同。 市例:具有两种功能的反例 WORD DelRelTimeQue (void) { WORD Locate; Locate = 3; Locate = DeleteFromQue (Locate) ; /* Locate具有两种功能:位置和函数DeleteFromQue的退

30、 回值*/ return Locate; } 正确做法:使用两个变量 WORD DelRelTimeQue (void) { WORD Ret; WORD Locate; Locale = 3: ReX = DeleteFromQne(Locate); return Ret; } 原则4.2结构功能单一;不要设计面面俱到的数据结构。 说明:相关的一组信息才是构成一个结构体的基础,结构的定义应该可以明确的描述一个对 象,而不是一组相关性不强的数据的集合。 设计结构时应力争使结构代表一种现实事务的抽象,而不是同时代表多种。结构中的各元素 应代表同一事务的不同侧面

31、,而不应把描述没有关系或关系很弱的不同事务的元素放到同一 结构中。 示例:如下结构不太清晰、合理。 typedef struct STUDENT_STRU { unsigned char name[32]; /* student's name */ unsigned char age; /* student's age */ unsigned char sex; /* student's sex, as follows */ /*0-FEMALE; 1-MALE */ unsigned char teacher_name[32]; /* the student teacher

32、's name */ unsigned char teacher_sex; /* his teacher sex */ } STUDENT; 若改为如下,会更合理些。 typedef struct TEACHER_STRU ( unsigned char name[32]; /* teacher name */ unsigned char sex; /* teacher sex, as follows */ /*0-FEMALE; 1-MALE */ unsigned int teacherjnd; /* teacher index */ } TEACHER; typed

33、ef struct STUDENT_STRU ( unsigned char name[32]; /* student's name */ unsigned char age; /* student's age */ unsigned char sex; /* student's sex, as follows */ /*0-FEMALE; 1-MALE */ unsigned int teacherjnd; /* his teacher index */ } STUDENT; 原则4.3不用或者少用全局变量。 说明:单个文件内部可以使用static的全局变量,可以将其理解为

34、类的私有成员变量。全局 变量应该是模块的私有数据,不能作用对外的接口使用,使用static类型定义,可以有效防 止外部文件的非正常访问。 规则4.1防止局部变量与全局变量同名。 说明:尽管局部变量和全局变量的作用域不同而不会发生语法错误,但容易使人误解。 规则4.2通讯过程中使用的结构,必须注意字节序。 说明:通讯报文中,字节序是一个重要的问题,cpu类型复杂多样,大小端、32位/64位的 处理器也都有,如果结构会在报文交互过程中使用,必须考虑字节序问题。由于位域在不同 字节序下,表现看起来差别更大,所以更需要注意。对于这种跨平台的交互,数据成员发送 前,都应该进行主机序到网络

35、序的转换;接收时,也必须进行网络序到主机序的转换。 规则4.3严禁使用未经初始化的变量作为右值。 说明:坚持建议4.3 (在首次使用前初始化变量,初始化的地方离使用的地方越近越好。)可 以有效避免未初始化错误。 建议4.1构造仅有一个模块或函数可以修改、创建,而其余有关模块或函数只访问的全局变 量,防止多个不同模块或函数都可以修改、创建同一全局变量的现象。 说明:降低全局变量耦合度。 建议4.2使用面向接口编程思想,通过API访问数据:如果本模块的数据需要对外部模块开 放,应提供接口函数来设置、获取,同时注意全局数据的访问互斥。 说明:避免直接暴露内部数据给外部模型使用,是防

36、止模块间耦合最简单有效的方法。 定义的接口应该有比较明确的意义,比如一个风扇管理功能模块,有自动和手动工作模式, 那么设置、查询工作模块就可以定义接口为SetFanWorkMode, GetFanWorkMode;查询转速 就可以定义为GetFanSpeed;风扇支持节能功能开关,可以定义EnabletFanSavePower等等。 建议4.3在首次使用前初始化变量,初始化的地方离使用的地方越近越好。 说明:未初始化变量是C和C++程序中错误的常见来源。在变量首次使用前确保正确初始化。 在较好的方案中,变量的定义和初始化要做到亲密无间。 示例: 〃不可取的初始化:无意义的初始化

37、 int speedup_factor = 0; if (condition) { speedup_factor = 2; } else ( speedup_factor = -1; } 〃不可取的初始化:初始化和声明分离 int speedup_factor; if (condition) 目录 目录 1 简介 2 0规范制订说明 2 0.1代码总体原则 2 0.2术语定义 2 1头文件 3 1.1背景 3 I. 2术语定义: 3 2函数 8 2.1背景 8 3标识符命名与定义 13 3.1通用命名规则 13 3.2文件命名规则 15

38、3.3变量命名规则 15 3.4函数命名规则 16 3.5宏的命名规则 16 4变量 16 5宏、常量 19 6质量保证 22 7程序效率 25 8注释 26 9排版与格式 28 10表达式 29 11安全性 31 II. 1字符串操作安全 32 11.2整数安全 33 11.3格式化输出安全 34 11.4文件I/O安全 36 11.5 其它 37 speedup_factor = 2; } else { speedup_factor = -1; } 〃较好的初始化:使用默认有意义的初始化 int speedup_factor = -1; if

39、 (condition) ( speedup_factor = 2; } 〃较好的初始化使用?:减少数据流和控制流的混合 int speedup_factor = condition?2:-l; 〃较好的初始化:使用函数代替复杂的计算流 int speedup_factor = ComputeSpeedupFactor(); 建议4.4明确全局变量的初始化顺序,避免跨模块的初始化依赖。 说明:系统启动阶段,使用全局变量前,要考虑到该全局变量在什么时候初始化,使用全局 变量和初始化全局变量,两者之间的时序关系,谁先谁后,一定要分析清楚,不然后果往往 是低级而又灾难性的。

40、建议4.5尽量减少没有必要的数据类型默认转换与强制转换。 说明:当进行数据类型强制转换时,其数据的意义、转换后的取值等都有可能发生变化,而 这些细节若考虑不周,就很有可能留下隐患。 示例:如下赋值,多数编译器不产生告警,但值的含义还是稍有变化。 char ch; unsigned short int exam; ch = -1; exam = ch; //编译器不产生告警,此时exam为OxFFFF。 5宏、常量 规则5.1用宏定义表达式时,要使用完备的括号。 说明:因为宏只是简单的代码替换,不会像函数一样先将参数计算后,再传递。 示例:如下定义的宏都存在一定的风险 #

41、define RECTANGLE_AREA(a, b) a * b ttdefine RECTANGLE_AREA(a, b) (a * b) #define RECTANGLE_AREA(a, b) (a) * (b) 正确的定义应为: ftdefine RECTANGLE_AREA(a, b) ((a) * (b)) 规则5.2将宏所定义的多条表达式放在大括号中。 说明:更好的方法是多条语句写成do while(O)的方式。 示例:看下面的语句,只有宏的第一条表达式被执行。 #define FOO(x) \ printf("arg is %d\n", x); \ do

42、_something_useful(x); 为了说明问题,下面for语句的书写稍不符规范 for (blah = 1; blah < 10; blah++) FOO(blah) 用大括号定义的方式可以解决上面的问题: ttdefine FOO(x) {\ printf(narg is %s\nn, x);\ do_something_useful(x); \ } 但是如果有人这样调用: if (condition == 1) FOO(IO); else F00(20); 那么这个宏还是不能正常使用,所以必须这样定义才能避免各种问题: #define FOO(x)

43、do {\ printf(narg is %s\n“, x);\ do_someth i ng_u sefu I (x); \ } while(O) 用do-while(O)方式定义宏,完全不用担心使用者如何使用宏,也不用给使用者加什么约束。 规则5.3使用宏时,不允许参数发生变化。 示例:如下用法可能导致错误。 #define SQUARE(a) ((a) * (a)) int a = 5; int b; b = SQUARE(a++); 〃结果:a = 7,即执行了两次增。 正确的用法是: b = SQUARE(a); a++;〃结果:a = 6,即只执行了一次增

44、。 同时也建议即使函数调用,也不要在参数中做变量变化操作,因为可能引用的接口函数,在 某个版本升级后,变成了一个兼容老版本所做的一个宏,结果可能不可预知。 规则5.4不允许直接使用魔鬼数字。 说明:使用魔鬼数字的弊端:代码难以理解;如果一个有含义的数字多处使用,一旦需要修 改这个数值,代价惨重。 使用明确的物理状态或物理意义的名称能增加信息,并能提供单一的维护点。 解决途径: 对于局部使用的唯一含义的魔鬼数字,可以在代码周围增加说明注释,也可以定义局部const 变量,变量命名自注释。 对于广泛使用的数字,必须定义const全局变量/宏;同样变量/宏命名应是自注释的。 0作

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

46、:下面的代码无法得到想要的结果: ^define MAX_MACRO (a, b) ((a) > (b) ? (a) : (b)) int MAX_FUNC(int a, int b) { :(b)); return ((a) > (b) ? (a) } int testFunc () { unsigned int a =1: int b = —1; printf("MACRO: max of a and b is : %d\n”, MAX

47、_MACRO(++a, b)); printf CFUNC : max of a and b is : MAX_FUNC (a, b)); } return 0; 上面宏代码调用中,结果是(a

48、RATIO 1.653 编译器会永远也看不到ASPECT_RATIO这个符号名,因为在源码进入编译器之前,它会被预 处理程序去掉,于是ASPECT_RATIO不会加入到符号列表中。如果涉及到这个常量的代码在 编译时报错,就会很令人费解,因为报错信息指的是L653,而不是ASPECT_RATIOo如果 ASPECT_RATIO不是在你自己写的头文件中定义的,你就会奇怪1.653是从哪里来的,甚至会 花时间跟踪下去。这个问题也会出现在符号调试器中,因为同样地,你所写的符号名不会出 现在符号列表中。 解决这个问题的方案很简单:不用预处理宏,定义一个常量: Const double A

49、SPECT_RATIO = 1.653; 这种方法很有效,但有两个特殊情况要注意。首先,定义指针常量时会有点不同。因为常量 定义一般是放在头文件中(许多源文件会包含它),除了指针所指的类型要定义成const外, 重要的是指针也经常要定义成consto例如,要在头文件中定义一个基于char*的字符串常量, 你要写两次 const: const char * const authorName = "Scott Meyers"; 建议5.3宏定义中尽量不使用return> goto> continue、break等改变程序流程的语句。 说明:如果在宏定义中使用这些改变流程的语句,很容易引起

50、资源泄漏问题,使用者很难自 己察觉。 示例:在某头文件中定义宏CHECK_AND_RETURN: #define CHECK_AND_RETURN(cond, ret) (if (cond == NULL_PTR) {return ret;}} 然后在某函数中使用(只说明问题,代码并不完整): pMeml = VOS_MemAlloc(..・); CHECK_AND_RETURN(pMeml, ERR_CODE_XXX) pMem2 = VOS_MemAlloc(...); CHECK_AND_RETURN(pMem2 ERR_CODE_XXX) /*此时如果pMem2=NU

51、LL_PTR,贝lj pMeml未释放函数就返回了,造成内存泄漏。*/ 所以说,类似于CHECK_AND_RETURN这些宏,虽然能使代码简洁,但是隐患很大,使用须 谨慎。 6质量保证 原则6.1代码质量保证优先原则 1. 正确性,指程序要实现设计要求的功能。 2. 简洁性,指程序易于理解并且易于实现。 3. 可维护性,指程序被修改的能力,包括纠错、改进、新需求或功能规格变化的适应能力。 4. 可靠性,指程序在给定时间间隔和环境条件下,按设计要求成功运行程序的概率。 5. 可测试性,指软件发现故障并隔离、定位故障的能力,以及在一定的时间和成本前提下, 进行测试设计、测试执行

52、的能力。 6. 性能高效,指是尽可能少地占用系统资源,包括内存和执行时间。 7. 可移植性,指为了在原来设计的特定环境之外运行,对系统进行修改的能力。 8. 个人表达方式/个人方便性,指个人编程习惯。 原则6.2要时刻注意易混淆的操作符。 说明:包括易混淆和的易用错操作符 1、 易混淆的操作符 C语言中有些操作符很容易混淆,编码时要非常小心。 赋值操作符“二”逻辑操作符 关系操作符位操作符 关系操作符“>”位操作符“>>” 逻辑操作符“II”位操作符T' 逻辑操作符“&&”位操作符 逻辑操作符叩位操作符 2、 易用错的操作符 (1)除操作符“/“:当除操作符的运算

53、量是整型量时,运算结果也是整型。如:1/2=0 ⑵ 求余操作符“%”:求余操作符“%”的运算量只能是整型。如:5%2=1,而5.0%2是错误的。 ⑶自加、自减操作符“++” a v 、 ―: k = 5; x = k++; 执行后, x = 5, k = 6 k = 5; x = ++k; 执行后, x = 6, k = 6 k = 5; x = k-; 执行后, x = 5, k = 4 k = 5; x = -k;执行后,x = 4, k = 4 规则6.1禁止内存操作越界。 说明:内存操作主要是指对数组、指针、内存地址等的操作。内存操作越界

54、是软件系统主要 错误之一,后果往往非常严重,所以当我们进行这些操作时一定要仔细小心。 示例:使用itoa ()将整型数转换为字符串时: char TempShold[10] itoa(ProcFrecy, TempShold, 10); /*刷新间隔设为值1073741823时,监控前台抛异常。*/ TempShold是以‘\0‘结尾的字符数组,只能存储9个字符,而ProcFrecy的最大值可达到 10位,导致符数组TempShold越界。 正确写法:一个int (32位)在一2147483647—2147483648之间,将数组TempShold设置成 12位。 char

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

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

57、留心就会出现在一个模块中释放了某个内存块,而另一模块 在随后的某个时刻又使用了它。 示例:一个函数返回的局部自动存储对象的地址,导致引用已经释放的内存空间 int* foobar (void) ( int local_auto = 100; return &local_auto; } 坚持下列措施可以避免引用已经释放的内存空间: 内存释放后,把指针置为NULL,使用内存指针前进行非空判断。 耦合度较强的模块互相调用时,要仔细考虑其调用关系,防止已经删除的对象被再次使用。 避免操作已发送消息的内存。 规则6.4编程时,要防止差1错误。 说明:此类错误一般是由于把“<二

58、”误写成或“X”误写成等造成的,由此引起 的后果,很多情况下是很严重的,所以编程时,一定要在这些地方小心。当编完程序后,应 对这些操作符进行彻底检查。 使用变量时要注意其边界值的情况。 示例:如C语言中字符型变量,有效值范围为-128到127。故以下表达式的计算存在一定风 险。 char ch = 127; int sum = 200; ch += 1; 〃 127为ch的边界值,再加1将使ch上溢到-128,而不是128 sum += ch; 〃故sum的结果不是328,而是72 规则6.5所有的if... else if结构应该由else子句结束;switch语句必须有

59、default分支。 建议6.1函数中分配的内存,在函数退出之前要释放。 说明:有很多函数申请内存,保存在数据结构中,要在申请处加上注释,说明在何处释放。 建议6.2不要滥用goto语句。 说明:goto语句会破坏程序的结构性,所以除非确实需要,最好不使用goto语句。可以利 用goto语句方面退出多重循环;同一个函数体内部存在大量相同的逻辑但又不方便封装成 函数的情况下,譬如反复执行文件操作,对文件操作失败以后的处理部分代码(譬如关闭文 件句柄,释放动态申请的内存等等),一般会放在该函数体的最后部分,再需要的地方就 goto到那里,这样代码反而变得清晰简洁。实际也可以封装成函

60、数或者封装成宏,但是这 么做会让代码变得没那么直接明了。 建议63时刻注意表达式是否会上溢、下溢。 示例:如下程序将造成变量下溢。 unsigned char size ♦ • • while (size- >= 0) // 将出现下溢 { ...// program code } 当size等于0时,再减不会小于0,而是OxFF,故程序是一个死循环。应如下修改。 char size; // A unsigned char 改为 char • • • while (size- >= 0) ...// program code 7程序效率 原则7.1在保证软件系

61、统的正确性、简洁、可维护性、可靠性及可测性的前提下,提高代 码效率。本章节后面所有规则和建议,都应在不影响前述可读性等质量属性的前提下实施。 说明:不能一味地追求代码效率,而对软件的正确、简洁、可维护性、可靠性及可测性造成 影响。 记住:让一个正确的程序更快速,比让一个足够快的程序正确,要容易得太多。大多数时候, 不要把注意力集中在如何使代码更快上,应首先关注让代码尽可能地清晰易读和更可靠。 原则7.2通过对数据结构、程序算法的优化来提高效率。 建议7.1将不变条件的计算移到循环体外。 说明:将循环中与循环无关,不是每次循环都要做的操作,移到循环外部执行。 示例一: for

62、 (int i = 0; i < 10; i++ ) { sum += i; back_sum = sum; } 对于此for循环来说语句“back_Sum=sum;”没必要每次都执行,只需要执行一次即可, 因此可以改为: for (int i = 0; i < 10; i++ ) ( sum += i; } back_sum = sum; 示例二: for (_UL i = 0; i < func_calc_max(); i++) { //process; } 函数func_calc_max()没必要每次都执行,只需要执行一次即可,因此可以改为: _UL

63、max = func_calc_max(); for (_UL i = 0; i < max; i++) { //process; } 建议7.2对于多维大数组,避免来回跳跃式访问数组成员。 示例:多维数组在内存中是从最后一维开始逐维展开连续存储的。下面这个对二维数组访问 是以SIZE_B为步长跳跃访问,到尾部后再从头(第二个成员)开始,依此类推。局部性比 较差,当步长较大时,可能造成cache不命中,反复从内存加载数据到cache。应该把i和 j交换。 for (int i = 0; i < SIZE_B; i++) { for(intj = 0;j

64、 j++) ( sum +=x[j][i]; } } 建议7.3创建资源库,以减少分配对象的开销。 说明:例如,使用线程池机制,避免线程频繁创建、销毁的系统调用;使用内存池,对于频 繁申请、释放的小块内存,一次性申请一个大块的内存,当系统申请内存时,从内存池获取 小块内存,使用完毕再释放到内存池中,避免内存申请释放的频繁系统调用。 建议7.4将多次被调用的“小函数”改为inline函数或者宏实现。 说明:如果编译器支持inline,可以采用inline函数。在做这种优化的时候一定要注意下面 inline函数的优点:其一编译时不用展开,代码SIZE小。其二可以加断点,易于定

65、位问题, 例如对于引用计数加减的时候。其三函数编译时,编译器会做语法检查。 8注释 原则8.1优秀的代码可以自我解释,不通过注释即可轻易读懂。 说明:优秀的代码不写注释也可轻易读懂,注释无法把糟糕的代码变好,需要很多注释来解 释的代码往往存在坏味道,需要重构。 示例:注释不能消除代码的坏味道: /*判断m是否为素数*/ /*返回值::是素数,:不是素数*/ int p(int m) { int k = sqrt(m); £or (int i = 2; i <= k; i++) if (m % i == 0) break : /* 发现整除,表示m不为素数,结束遍历*

66、/ /* 遍历中没有发现整除的情况,返回*/ if (i > k) retirrn 1 ; /* 遍历中没有发现整除的情况,返回*/ else return 0: } 重构代码后,不需要注释: int IsPrimeNumber(int num) { int sqrt:_of_niim = sqi*T (num); for (int: i = 2; i <= _num; ( if (num % i == O) ( re-turn FALSE ; } } I'e'tum TRUE ; 原则8.2注释的内容要清楚、明了,含义准确,防止注释二义性。 说明:有歧义的注释反而会导致维护者更难看懂代码,正如带两块表反而不知道准确时间。 示例:注释与代码相矛盾,注释内容也不清楚,前后矛盾。 /* 上报网管时要求故障ID与恢复ID才目一致*/ /*因此在此由告警级别获知是不是恢复ID */ /* 若是恢复工D则设置为CleaLrld,否则设置为Alarmld */ if (CLEAR_ALARM_LEVEL != ReDara, level) {

展开阅读全文
温馨提示:
1: 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
2: 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
3.本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
5. 装配图网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

相关资源

更多
正为您匹配相似的精品文档
关于我们 - 网站声明 - 网站地图 - 资源地图 - 友情链接 - 网站客服 - 联系我们

copyright@ 2023-2025  zhuangpeitu.com 装配图网版权所有   联系电话:18123376007

备案号:ICP2024067431-1 川公网安备51140202000466号


本站为文档C2C交易模式,即用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。装配图网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知装配图网,我们立即给予删除!