华为C语言编程规范(精华总结)

您所在的位置:网站首页 ug编程仿真不了报错找到了camipw库的错误版本 华为C语言编程规范(精华总结)

华为C语言编程规范(精华总结)

#华为C语言编程规范(精华总结)| 来源: 网络整理| 查看: 265

长文建议收藏

1、代码总体原则

2、头文件

3、函数

4、标识符命名与定义

5、变量

6、宏、常量

7、表达式

8、注释

9、排版与格式

10、代码编辑编译 

1、代码总体原则

1、清晰第一 

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

目前软件维护期成本占整个生命周期成本的40%~90%。根据业界经验,维护期变更代码的成本,小型系统是开发期的5倍,大型系统(100万行代码以上)可以达到100倍。业界的调查指出,开发组平均大约一半的人力用于弥补过去的错误,而不是添加新的功能来帮助公司提高竞争力。

一般情况下,代码的可阅读性高于性能,只有确定性能是瓶颈时,才应该主动优化。

2、简洁为美

简洁就是易于理解并且易于实现。代码越长越难以看懂,也就越容易在修改时引入错误。写的代码越多,意味着出错的地方越多,也就意味着代码的可靠性越低。因此,我们提倡大家通过编写简洁明了的代码来提升代码可靠性。

废弃的代码(没有被调用的函数和全局变量)要及时清除,重复代码应该尽可能提炼成函数。

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

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

2、头文件

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

1、头文件中适合放置接口的声明,不适合放置实现

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

要求:

内部使用的函数(相当于类的私有方法)声明不应放在头文件中。

内部使用的宏、枚举、结构定义不应放入头文件中。

变量定义不应放在头文件中,应放在.c文件中。

变量的声明尽量不要放在头文件中,亦即尽量不要使用全局变量作为接口。变量是模块或单元的内部实现细节,不应通过在头文件中声明的方式直接暴露给外部,应通过函数接口的方式进行对外暴露。即使必须使用全局变量,也只应当在.c中定义全局变量,在.h中仅声明变量为全局的。

2、头文件应当职责单一,切忌依赖复杂

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

错误示例:某平台定义WORD类型的头文件:

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

typedef unsigned short WORD;

这个头文件不但定义了基本数据类型WORD,还包含了stdio.h syslib.h等等不常用的头文件。如果工程中有10000个源文件,而其中100个源文件使用了stdio.h的printf,由于上述头文件的职责过于庞大,而WORD又是每一个文件必须包含的,从而导致stdio.h/syslib.h等可能被不必要的展开了9900次,大大增加了工程的编译时间。

3、头文件应向稳定的方向包含 

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

就我们的产品来说,依赖的方向应该是:产品依赖于平台,平台依赖于标准库。某产品线平台的代码中已经包含了产品的头文件,导致平台无法单独编译、发布和测试,是一个非常糟糕的反例。除了不稳定的模块依赖于稳定的模块外,更好的方式是两个模块共同依赖于接口,这样任何一个模块的内部实现更改都不需要重新编译另外一个模块。在这里,我们假设接口本身是最稳定的。

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

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

现有某些产品中,习惯一个.c文件对应两个头文件,一个用于存放对外公开的接口,一个用于存放内部需要用到的定义、声明等,以控制.c文件的代码行数。编者不提倡这种风格。这种风格的根源在于源文件过大,应首先考虑拆分.c文件,使之不至于太大。另外,一旦把私有定义、声明放到独立的头文件中,就无法从技术上避免别人include之,难以保证这些定义最后真的只是私有的。

5、禁止头文件循环依赖

头文件循环依赖,指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的源代码重新编译。

6、  .c/.h文件禁止包含用不到的头文件

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

7、  头文件应当自包含 

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

示例:如果a.h不是自包含的,需要包含b.h才能编译,会带来的危害:每个使用a.h头文件的.c文件,为了让引入的a.h的内容编译通过,都要包含额外的头文件b.h。额外的头文件b.h必须在a.h之前进行包含,这在包含顺序上产生了依赖。

注意:该规则需要与“.c/.h文件禁止包含用不到的头文件”规则一起使用,不能为了让a.h自包含,而在a.h中包含不必要的头文件。a.h要刚刚可以自包含,不能在a.h中多包含任何满足自包含之外的其他头文件。

8、总是编写内部 #include 保护符( #define  保护)

多次包含一个头文件可以通过认真的设计来避免。如果不能做到这一点,就需要采取阻止头文件内容被包含多于一次的机制。通常的手段是为每个文件配置一个宏,当头文件第一次被包含时就定义这个宏,并在头文件被再次包含时使用它以排除文件内容。所有头文件都应当使用#define 防止头文件被多重包含,命名格式为FILENAME_H,为了保证唯一性,更好的命名是PROJECTNAME_PATH_FILENAME_H。

注:没有在宏最前面加上单下划线"_",是因为一般以单下划线"_"和双下划线"__"开头的标识符为ANSIC等使用,在有些静态检查工具中,若全局可见的标识符以"_"开头会给出告警。

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

保护符使用唯一名称;

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

正确示例:假定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

例外情况:头文件的版权声明部分以及头文件的整体注释部分(如阐述此头文件的开发背景、使用注意事项等)可以放在保护符(#ifndef XX_H)前面。

9、禁止在头文件中定义变量

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

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

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

11、禁止在 extern "C" 中包含头文件

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

在extern "C"中包含头文件,可能会导致被包含头文件的原有意图遭到破坏。

错误示例:

extern “C”

{

#include “xxx.h”

...

}

正确示例:

#include “xxx.h”

extern “C”

{

...

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

需要注意的是,这个.h并不是简单的包含所有内部的.h,它是为了模块使用者的方便,对外整体提供的模块接口。以Google test(简称GTest)为例,GTest作为一个整体对外提供C  单元测试框架,其1.5版本的gtest工程下有6个源文件和12个头文件。但是它对外只提供一个gtest.h,只要包含gtest.h即可使用GTest提供的所有对外提供的功能,使用者不必关系GTest内部各个文件的关系,即使以后GTest的内部实现改变了,比如把一个源文件c拆成两个源文件,使用者也不必关心,甚至如果对外功能不变,连重新编译都不需要。对于有些模块,其内部功能相对松散,可能并不一定需要提供这个.h,而是直接提供各个子模块或者.c的头文件。

比如产品普遍使用的VOS,作为一个大模块,其内部有很多子模块,他们之间的关系相对比较松散,就不适合提供一个vos.h。而VOS的子模块,如Memory(仅作举例说明,与实际情况可能有所出入),其内部实现高度内聚,虽然其内部实现可能有多个.c和.h,但是对外只需要提供一个Memory.h声明接口。

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

降低接口使用者的编写难度

14、头文件不要使用非习惯用法的扩展名,如 .inc 

目前很多产品中使用了.inc作为头文件扩展名,这不符合c语言的习惯用法。在使用.inc作为头文件扩展名的产品,习惯上用于标识此头文件为私有头文件。但是从产品的实际代码来看,这一条并没有被遵守,一个.inc文件被多个.c包含比比皆是。

除此之外,使用.inc还导致source insight、Visual stduio等IDE工具无法识别其为头文件,导致很多功能不可用,如“跳转到变量定义处”。虽然可以通过配置,强迫IDE识别.inc为头文件,但是有些软件无法配置,如Visual Assist只能识别.h而无法通过配置识别.inc。

15、同一产品统一包含头文件排列方式

常见的包含头文件排列方式:功能块排序、文件名升序、稳定度排序。

正确示例1:以升序方式排列头文件可以避免头文件被重复包含:

#include

#include

#include

#include

#include

正确示例2:以稳定度排序,建议将不稳定的头文件放在前面,如把产品的头文件放在平台的头文件前面:

#include

#include

相对来说,product.h修改的较为频繁,如果有错误,不必编译platform.h就可以发现product.h的错误,可以部分减少编译时间。

3、函数

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

整洁函数要求:代码简单直接、不隐藏设计者的意图、用干净利落的抽象和直截了当的控制语句将函数有机组织起来。

代码的有效组织包括:逻辑层组织和物理层组织两个方面。逻辑层,主要是把不同功能的函数通过某种联系组织起来,主要关注模块间的接口,也就是模块的架构。物理层,无论使用什么样的目录或者名字空间等,需要把函数用一种标准的方法组织起来。例如:设计良好的目录结构、函数名字、文件组织等,这样可以方便查找。

1、一个函数仅完成一件功能

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

将没有关联或者关联很弱的语句放到同一函数中,会导致函数职责不明确,难以理解,难以测试和改动。

2、重复代码应该尽可能提炼成函数

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

重复代码是我司不良代码最典型的特征之一。在“代码能用就不改”的指导原则之下,大量的烟囱式设计及其实现充斥着各产品代码之中。新需求增加带来的代码拷贝和修改,随着时间的迁移,产品中堆砌着许多类似或者重复的代码。

项目组应当使用代码重复度检查工具,在持续集成环境中持续检查代码重复度指标变化趋势,并对新增重复代码及时重构。当一段代码重复两次时,即应考虑消除重复,当代码重复超过三次时,应当立刻着手消除重复。

3、避免函数过长,新增函数不超过 50 行 (非空非注释行) 

过长的函数往往意味着函数功能不单一,过于复杂。

函数的有效代码行数,即NBNC(非空非注释行)应当在[1,50]区间。

例外:某些实现算法的函数,由于算法的聚合性与功能的全面性,可能会超过50行。

延伸阅读材料:业界普遍认为一个函数的代码行不要超过一个屏幕,避免来回翻页影响阅读;一般的代码度量工具建议都对此进行检查,例如Logiscope的函数度量:"Number of Statement" (函数中的可执行语句数)建议不超过20行,QA C建议一个函数中的所有行数(包括注释和空白行)不超过50行。

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

函数的代码块嵌套深度指的是函数中的代码控制块(例如:if、for、while、switch等)之间互相包含的深度。每级嵌套都会增加阅读代码时的脑力消耗,因为需要在脑子里维护一个“栈”(比如,进入条件语句、进入循环„„)。应该做进一步的功能分解,从而避免使代码的阅读者一次记住太多的上下文。优秀代码参考值:[1, 4]。

错误示例:代码嵌套深度为5层:

void serial (void)

{

    if (!Received)

    {

        TmoCount = 0;

         switch (Buff)

        {

            case AISGFLG:

                if ((TiBuff.Count > 3)&& ((TiBuff.Buff[0] == 0xff) || (TiBuf.Buff[0] == CurPa.ADDR)))

                {

                    F lg7E = false;

                    Received = true;

                }

                else

                {

                    TiBuff.Count = 0;

                     F lg7D = false;

                     F lg7E = true;

                }

                break;

            default:

                break;

        }

    }

}

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

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

示例:函数square_exam返回g_exam平方值。那么如下函数不具有可重入性。

int g_exam;

unsigned int example( int para )

{

    unsigned int temp;

    g_exam = para; // (**)

    temp = square_exam ( );

    return temp;

}

此函数若被多个线程调用的话,其结果可能是未知的,因为当(**)语句刚执行完后,另外一个使用本函数的线程可能正好被激活,那么当新激活的线程执行到此函数时,将使g_exam赋于另一个不同的para值,所以当控制重新回到“temp =square_exam ( )”后,计算出的temp很可能不是预想中的结果。此函数应如下改进。 

int g_exam;

unsigned int example( int para )

{

    unsigned int temp;

    [申请信号量操作] // 若申请不到“信号量”,说明另外的进程正处于

    g_exam = para; //给g_exam赋值并计算其平方过程中(即正在使用此

    temp = square_exam( ); // 信号),本进程必须等待其释放信号后,才可继

    [释放信号量操作] // 续执行。其它线程必须等待本线程释放信号量后

    // 才能再使用本信号。

    return temp;

}

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

对于模块间接口函数的参数的合法性检查这一问题,往往有两个极端现象,即:要么是调用者和被调用者对参数均不作合法性检查,结果就遗漏了合法性检查这一必要的处理过程,造成问题隐患;要么就是调用者和被调用者均对参数进行合法性检查,这种情况虽不会造成问题,但产生了冗余代码,降低了效率。

7、对函数的错误返回码要全面处理

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

8、设计高扇入,合理扇出(小于7)的函数

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

扇出过大,表明函数过分复杂,需要控制和协调过多的下级函数;而扇出过小,例如:总是1,表明函数的调用层次可能过多,这样不利于程序阅读和函数结构的分析,并且程序运行时会对系统资源如堆栈空间等造成压力。通常函数比较合理的扇出(调度函数除外)通常是3~5。 

扇出太大,一般是由于缺乏中间层次,可适当增加中间层次的函数。扇出太小,可把下级函数进一步分解多个函数,或合并到上级函数中。当然分解或合并函数时,不能改变要实现的功能,也不能违背函数间的独立性。扇入越大,表明使用此函数的上级函数越多,这样的函数使用效率高,但不能违背函数间的独立性而单纯地追求高扇入。公共模块中的函数及底层函数应该有较高的扇入。

较良好的软件结构通常是顶层函数的扇出较高,中层函数的扇出较少,而底层函数则扇入到公共模块中。

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

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

10、函数不变参数使用const 

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

正确示例:C99标准 7.21.4.4 中strncmp 的例子,不变参数声明为const。

int strncmp(const char *s1, const char *s2, register size_t n)

{

    register unsigned char u1, u2;

    while (n-- > 0)

    {

        u1 = (unsigned char) *s1  ;

        u2 = (unsigned char) *s2  ;

        if (u1 != u2)

        {

            return u1 - u2;

        }

        if (u1 == '\0')

        {

            return 0;

        }

    }

    return 0;

}

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

带有内部“存储器”的函数的功能可能是不可预测的,因为它的输出可能取决于内部存储器(如某标记)的状态。这样的函数既不易于理解又不利于测试和维护。在C语言中,函数的static局部变量是函数的内部存储器,有可能使函数的功能不可预测。

错误示例:如下函数,其返回值(即功能)是不可预测的。

unsigned int integer_sum( unsigned int base )

{

    unsigned int index;

    static unsigned int sum = 0;// 注意,是static类型的。

    // 若改为auto类型,则函数即变为可预测。

    for (index = 1; index = TEST_COUNT_THRESHOLD))

{

    // process code

}

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

错误示例:

int a = 5; int b= 10; //不好的排版

正确示例:

int a = 5;

int b= 10;

5、if 、 for 、 do 、 while 、 case 、 switch 、 default 等语句独占一行

执行语句必须用缩进风格写,属于if、for、do、while、case、switch、default等下一个缩进级别;

一般写if、for、do、while等语句都会有成对出现的„{}‟,对此有如下建议可以参考:if、for、do、while等语句后的执行语句建议增加成对的“{}”;如果if/else配套语句中有一个分支有“{}”,那么另一个分支即使一行代码也建议增加“{}”;添加“{”的位置可以在if等语句后,也可以独立占下一行;独立占下一行时,可以和if在一个缩进级别,也可以在下一个缩进级别;但是如果if语句很长,或者已经有换行,建议“{”使用独占一行的写法。

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

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

在已经非常清晰的语句中没有必要再留空格,如括号内侧(即左括号后面和右括号前面)不需要加空格,多重括号间不必加空格,因为在C语言中括号已经是最清晰的标志了。在长语句中,如果需要加的空格非常多,那么应该保持整体清晰,而在局部不加空格。给操作符留空格时不要连续留两个以上空格。

正确示例:

1、逗号、分号只在后面加空格。

int a, b, c;

2、比较操作符, 赋值操作符"="、 " =",算术操作符" "、"%",逻辑操作符"&&"、"&",位域操作符""、"."前后不加空格。

p->id = pid; // "->"指针前后不加空格

5、if、for、while、switch等与后面的括号间应加空格,使if等关键字更为突出、明显。

if (a >= b && c > d)

7、注释符(包括/**/、//)与注释内容之间要用一个空格进行分隔

8、源程序中关系较为紧密的代码应尽可能相邻

10、代码编辑编译 

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

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

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

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

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

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

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

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

6、要小心地使用编辑器提供的块拷贝功能编程

————————————————

版权声明:本文为CSDN博主「不脱发的程序猿」的原创文章,

https://blog.csdn.net/m0_38106923/article/details/105042594



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3