|
1.1 介绍 本文档详细说明了Wind River的C代码和包含在代码中的附加文档的编写习惯。 这些习惯是特意约定的,不全,但是能写出更高质量的代码。每一个源模块必须包含某些基本的文档,并且这些代码和文档必须具备一个好的格式以便于阅读和理解。 这些习惯也用来在不同的程序员之间提供一个统一的编码标准,这样就能减少在别人的代码上工作时因为不同的代码风格所做的额外调整。同时,也能允许对代码的自动处理。比如,可以写一些程序来自动生成参考手册,模块摘要,更新说明等。 本文档所描述的编码习惯由以下两部分组成: l 文件标题:无论什么编程语言,在每一个源文件的头部都有一个统一的标题说明 l C代码习惯 1.2 文件标题 每一个包含C代码的文件,不管它是头文件,资源文件,或者是一个使用主机工具、库例程或应用程序的文件,都必须包含一个标准的文件标题。 文件标题由下面描述的几块组成,这些块由一个或几个空行分开,但是在每一个块里面不能有空行,以便于文件标题的自动处理。 l 名称:由一行注释组成,包含工具,库或应用程序的名字,以及一个简短的描述。名字必须跟文件名一样。这一行将变成自动处理后的参考条目和索引中的名称。 l 版权:由一行注释组成,包含适当的版权信息。 l 更改历史:包含一个注释块,就是C语言中的多行注释。更改历史中的每一个条目包含版本号、更改时间、更改人和有关更改内容的完整描述。 版本号由两个阿拉伯数字和一个字符组成(例如,03C)。字符的增加表示一个内部的改动(小的改动),数字的增加表示一个大的改动,特别是改变了模块的外部接口。 下面是一个标准的C源文件的文件标题的例子: Example I-1: Standard File Heading (C Version) /* fooLib.c - foo subroutine library */ /* Copyright 1984-1995 Wind River Systems, Inc. */ /* modification history -------------------- 02a,15sep92,nfs added defines MAX_FOOS and MIN_FATS. 01b,15feb86,dnw added routines fooGet() and fooPut(); added check for invalid index in fooFind(). 01a,10feb86,dnw written. */ 1.3 C代码习惯 分成下面几个种类 l 模块布局 l 子程序布局 l 代码布局 l 命名习惯 l 风格 l 头文件布局 l 文档生成 1.3.1 C模块布局 一个模块是指在一个单一文件中的所有代码单元。模块标题紧跟在文件标题之后,由以下几块组成,每一块之间由一个或几个空行隔开。 在更新历史之后和第一个函数或可执行的模块代码之前,如果需要,必须按照顺序包含下面的几个部分: l 模块文档概要:由注释块组成,包含所有模块目的和功能的一个完整的描述,特别是外部接口。包括相关的包含头文件。格式为INCLUDE FILES:文件列表。 l 包含头文件:由一个包含includes字样的单行注释加上一行或多行C的预处理宏#include组成。 这部分包含了本模块需要包含的所有头文件。 l 定义:由一个包含defines字样的单行注释加上一个或多个C的预处理宏#define组成。这部分包含了本模块所需要的所有定义。 l 类型:由一个包含typedefs字样的单行注释加上一个或多个C的typedef声明组成。这部分包含了本模块需要的所有类型定义。 l 全局变量:由一个包含globals字样的单行注释加上一个或多个C的全局定义组成,每行一个。这部分包含了所有希望在本模块外能访问的变量的定义。 l 本地变量:由一个包含local字样的单行注释加上一个或多个C的局部定义组成,每行一个。这部分包含了所有不希望在本模块外能访问的变量的定义。 l 前向声明:由一个包含forward declarations字样的单行注释加上一个或多个ANSI C的函数原型声明组成,每行一个。这部分包含了所有本模块中定义的函数原型。前向声明仅仅用于本地函数,其它类型的函数在头文件中声明。 下面是一个例子,包含文件标题: Example I-2: C File and Module Headings /* fooLib.c - foo subroutine library */ /* Copyright 1984-1995 Wind River Systems, Inc. */ /* modification history -------------------- 02a,15sep92,nfs added defines MAX_FOOS and MIN_FATS. 01b,15feb86,dnw added routines fooGet() and fooPut(); added check for invalid index in fooFind(). 01a,10feb86,dnw written. */ /* DESCRIPTION This module is an example of the Wind River Systems C coding conventions. ... INCLUDE FILES: fooLib.h */ /* includes */ #include "vxWorks.h" #include "fooLib.h" /* defines */ #define MAX_FOOS 112 /* max # of foo entries */ #define MIN_FATS 2 * min # of FAT copies */ /* typedefs */ typedef struct fooMsg /* FOO_MSG */ { VOIDFUNCPTR func; /* pointer to function to invoke */ int arg [FOO_MAX_ARGS]; /* args for function */ } FOO_MSG; /* globals */ char * pGlobalFoo; /* global foo table */ /* locals */ LOCAL int numFoosLost; /* count of foos lost */ /* forward declarations */ LOCAL int fooMat (list * aList, int fooBar, BOOL doFoo); FOO_MSG fooNext (void); STATUS fooPut (FOO_MSG inPar); 1.3.2 C子程序布局 每一个子程序前面由一段C的注释标题组成,包含以下的几块。标题中不能有空行,但是每一块必须由第一个字符是(*)且仅包含这个字符的单独一行分开。 l Banner:表示注释的开始,由一个斜线(/)和75个星号(*)组成。 l 名称:只有一行,包含子程序名和一段短的注释。名称中的子程序名必须匹配声明中的子程序名。这一行将变成自动处理后的参考条目和索引中的名称。 l 描述:一个完整的描述,关于这个子程序的功能和如何使用。 l 返回值:单词RETURNS:加上可能的返回值的描述。如果没有返回值(声明时返回值为void),输入:RETURNS: N/A l 错误号:单词ERRNO:加上所有可能返回的错误号。不用给出错误的描述,只需要写出错误号的值和常量定义的形式。 子程序标题用C语言的字符(*/)结束,必须另起一行,并从第一列开始。 Example I-3: Standard C Subroutine Layout: /******************************************************************** * * fooGet - get an element from a foo * * This routine finds the element of a specified index in a specified * foo. The value of the element found is copied to <pvalue>. * * RETURNS: OK, or ERROR if the element is not found. * * ERRNO: * S_fooLib_BLAH * S_fooLib_GRONK */ STATUS fooGet ( FOO foo, /* foo in which to find element */ int index, /* element to be found in foo */ int * pvalue /* where to put value */ ) { ... } 1.3.3 C定义格式 一行只包含一个定义,并且必须符合当前的缩进水平。 变量 l 基本变量:首先是类型,然后插入一个制表符(TAB),最后是包含在一个单行注释中的描述。例如: unsigned rootMemNBytes; /* memory for TCB and root stack */ int rootTaskId; /* root task ID */ BOOL roundRobinOn; /* boolean for round-robin mode */ l 指针变量的定义格式如下 FOO_NODE * pFooNode; /* foo node pointer */ FOO_NODE ** ppFooNode; /* pointer to the foo node pointer */ l 结构的格式:第一行是关键字struct加上结构名,下一行是开始的大括号,然后就是结构中的元素。每一个元素占用单独的一行,并采用适当的缩进和注释。如果需要,注释可以不止一行。定义的结尾是一个反大括号,类型名字,分号。记住,始终用typedef来声明这个结构或联合,并且包含结构名和类型名。不要直接用结构定义去声明一个变量。例: typedef struct symtab /* SYMTAB - symbol table */ { OBJ_CORE objCore; /* object maintanance */ HASH_ID nameHashId; /* hash table for names */ SEMAPHORE symMutex; /* symbol table mutual exclusion sem */ PART_ID symPartId; /* memory partition id for symbols */ BOOL sameNameOk; /* symbol table name clash policy */ int nSymbols; /* current number of symbols in table */ } SYMTAB; 这个格式也适用于其他的复合类型声明,例如联合或枚举。 不直接用结构定义去声明一个变量的例外是在结构中有一个指向结构的指针,隐 含着另外一种类型声明。这种情况允许结构存储相关的结构指针而不需要包含定义那种类型的头文件。 例如,下面的例子可以编译通过而不需要包含定义类型struct fooInfo的头文件 CORRECT: typedef struct tcbInfo { struct fooInfo * pfooInfo; ... } TCB_INFO; 相反,下面的例子如果不包含定义类型FOO_INFO的头文件就不能通过编译。 INCORRECT: typedef struct tcbInfo { FOO_INFO * pfooInfo; ... } TCB_INFO; 子程序: 子程序的声明有两种格式,取决于子程序是否带参数。 l 如果子程序带参数,第一行是返回类型和子程序名,第二行是括号,接着就是传给子程序的参数,每行一个,最后包含一个反括号的单独一行表示子程序声明的结束。例如: int lstFind ( LIST * pList, /* list in which to search */ NODE * pNode /* pointer to node to search for */ ) l 如果子程序不带参数,就只占用一行的空间,并且括号之间必须有一个关键字void。例如: STATUS fppProbe (void) 1.3.4 C代码布局 每一行代码的最大长度为80个字符,包含以下内容: l 垂直间隔 l 水平间隔 l 缩进 l 注释 垂直间隔 l 使用空行使代码更容易阅读,并且把逻辑上相关的部分放在一起。在注释行的前后分别放置一个空行。 l 不要在一行放置多个声明,每一个变量和函数的参数必须在一个单独的行中声明,不要使用逗号列表去声明多个变量。 l 不要在一行中放置多个表达式,但是for表达式例外, for (i = 0; i < count; i++) 或者在switch表达式中动作很短并且几乎一样也可以放在同一行中。 在if表达式中,执行部分总是相对于条件表达式另起一行。 if (i > count) i = count; l 括号({和})和case标记总是占用它们自己的一行。 水平间隔 l 在二元操作符两边,逗号后面,括号前面放置空格,不要在结构成员和指针操作符两边放置空格。在数组的第一个括号前放置空格,但是如果数组里面只有一到两个字符的话,该空格可以省略。例如: status = fooGet (foo, i + 3, &value); foo.index pFoo->index fooArray [(max + min) / 2] string[0] l 一行写不下的按照下面的格式排列: a = (b + c) * (d + e); status = fooList (foo, a, b, c, d, e); if ((a == b) && (c == d)) ... 缩进 l 缩进的间隔是4个字符(即第1,5,9,13列) l 模块和子程序的标题以及子程序的声明从第一列开始 l 下列情况进行缩进 子程序声明 条件表达式 循环表达式 switch表达式 case标记 typedef后面的结构定义 l else的缩进方式与if一样,例如: if ( condition ) { statements } else { statements } else if的缩进方式: if ( condition ) { statements } else if ( condition ) { statements } else { statements } l switch表达式的缩进方式: switch ( input ) { case 'a': ... break; case 'b': ... break; default: ... break; } 如果执行的动作很短而且几乎所有的动作都相同,可以采用下面的形式: switch ( input ) { case 'a': x = aVar; break; case 'b': x = bVar; break; case 'c': x = cVar; break; default: x = defaultVar; break; } l 所有的注释都有相同的缩进方式 l 括号({和})的缩进方式和它前后的代码相同 注释 l 注释应该放在相关的代码前面,并且缩进的水平应该相同。在注释和代码之间用一个单独的空行隔开。 ² 单行注释的格式如下: /* This is the correct format for a single-line comment */ foo = MAX_FOO; ² 如果是多行注释,开始和结束的注释标识应该单起一行,每一行注释的前面放置一个星号(*),如下: /* * This is the correct format for a multiline comment * in a section of code. */ foo = MIN_FOO; l 声明中和代码后部的多行注释由一个或多个单行注释组成,例如: int foo ( int a, /* this is the correct format for a */ /* multiline comment in a declaration */ BOOL b /* standard comment at the end of a line */ ) { day = night; /* when necessary, a comment about a line */ /* of code can be done this way */ } 1.3.5 C命名习惯 l 当创建名字的时候,记住这段代码只会写一次,但是可能有很多人来阅读。所以应该取一个有意义的和易读的名字,不要使用模糊的缩写。 l 程序名、变量名、结构或联合的成员变量名由大写和小写字母组成,不要使用下划线。每个单词的首字母大写,但第一个字母除外。 aVariableName l 类型,常量和宏的定义全部由大写字母组成,每个单词间用下滑线隔开 A_CONSTANT_value l 每一个模块都有一个短的前缀,2到5个字符。所有外部会用到的子程序,变量,常量,宏和类型的前面都要加上这个前缀。(外部不会用到的不用遵循这个习惯)。 fooLib.c module name fooObjFind subroutine name FooCount variable name FOO_MAX_COUNT constant FOO_NODE type l 子程序名遵循模块-名词-动词的规则,首先是模块名前缀,然后是该程序要处理的东西或对象的名字,最后是该程序要使用的动作。 fooObjFind foo - object - find sysNvRamGet system - NVRAM - get taskSwitchHookAdd task - switch hook - add l 每一个头文件会定义一个预处理用的符号以防止该文件被多次包含。这个符号的格式是该文件名加上前缀__INC(去掉.)。例如: __INCfooLibh l 指针变量名的前面加上一个前缀p,多级指针就加多个。例: FOO_NODE * pFooNode; FOO_NODE ** ppFooNode; FOO_NODE *** pppFooNode; 1.3.6 C风格 附加的编程风格。 l 注释:不要使用不完全的注释 l 数字常量:使用#define来定义常量,并为该常量取一个好的名字。不要在代码或声明中使用数字常量(除了很明显的小数字例如1或者0)。 l 布尔测试:当你测试一个布尔类型时不要使用非布尔类型的测试。例如,如果X是一个整数: 正确: if (x == 0) 不正确: if (! x) 相似的,当你测试一个非布尔类型时不要使用布尔类型的测试。例如,如果libInstalled是一个布尔变量: 正确: if (libInstalled) 不正确: if (libInstalled == TRUE) l 私有接口:私有接口指的是应用程序或库内部使用的函数和数据,不会作为外部接口的一部分的。私有接口应该放置在一个单独的头文件中,位于目录private下。该头文件名的最后一个字母是大写的P。例如,库blahlib用到的私有函数原型和数据放在文件private/private/blahLibP.h中。 l 结构的传递和返回:总是使用指针的形式,不要直接使用结构本身。 l 状态返回值:返回状态值的函数应该返回OK或者ERROR(在vxworks.h中定义)。特殊的错误类型由errno的值描述。不返回任何值的函数应该返回void。 l 使用定义好的名字:尽可能的使用在vxworks.h中定义好的名字,特别要注意下面的定义: TRUE或FALSE用于布尔变量 EOS用于字符串结束的测试 NULL用于空指针 IMPORT用于外部变量 LOCAL用于静态变量 FUNCPTR和VOIDFUNCPTR用于指向函数的指针 1.3.7 C头文件布局 头文件包含状态编码,类型定义,函数原型和其它的声明。和其它文件一样,头文件也必须有一个文件标题。 结构: l 为确保头文件不会被多次包含,必须采用下面的形式。 #ifndef __INCfooLibh #define __INCfooLibh ... #endif /* __INCfooLibh */ l 为保证C++的兼容,同时支持c或c++的编译环境,必须采用下面的形式: #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ ... #ifdef __cplusplus } #endif /* __cplusplus */ 声明顺序: 建议采用以下的声明顺序: 1. 其它头文件的包含 2. 简单的定义,例如错误状态码和宏定义 3. 类型定义 4. 函数原型声明 Example I-4: Sample C Header File The following header file demonstrates the conventions described above: /* bootLib.h - boot support subroutine library */ /* Copyright 1984-1993 Wind River Systems, Inc. */ /* modification history -------------------- 01g,22sep92,rrr added support for c++. 01f,04jul92,jcf cleaned up. 01e,26may92,rrr the tree shuffle. 01d,04oct91,rrr passed through the ansification filter, -changed VOID to void -changed copyright notice 01c,05oct90,shl added ANSI function prototypes; added copyright notice. 01b,10aug90,dnw added declaration of bootParamsErrorPrint(). 01a,18jul90,dnw written. */ #ifndef __INCbootLibh #define __INCbootLibh #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ /* * BOOT_PARAMS is a structure containing all the fields of the * VxWorks boot line. The routines in bootLib convert this structure * to and from the boot line ASCII string. */ /* defines */ #define BOOT_DEV_LEN 20 /* max chars in device name */ #define BOOT_HOST_LEN 20 /* max chars in host name */ #define BOOT_ADDR_LEN 30 /* max chars in net addr */ #define BOOT_FILE_LEN 80 /* max chars in file name */ #define BOOT_USR_LEN 20 /* max chars in user name */ #define BOOT_PASSWORD_LEN 20 /* max chars in password */ #define BOOT_OTHER_LEN 80 /* max chars in "other" field */ #define BOOT_FIELD_LEN 80 /* max chars in boot field */ /* typedefs */ typedef struct bootParams /* BOOT_PARAMS */ { char bootDev [BOOT_DEV_LEN]; /* boot device code */ char hostName [BOOT_HOST_LEN]; /* name of host */ char targetName [BOOT_HOST_LEN]; /* name of target */ char ead [BOOT_ADDR_LEN]; /* ethernet internet addr */ char bad [BOOT_ADDR_LEN]; /* backplane internet addr */ char had [BOOT_ADDR_LEN]; /* host internet addr */ char gad [BOOT_ADDR_LEN]; /* gateway internet addr */ char bootFile [BOOT_FILE_LEN]; /* name of boot file */ char startupScript [BOOT_FILE_LEN]; /* name of startup script */ char usr [BOOT_USR_LEN]; /* user name */ char passwd [BOOT_PASSWORD_LEN]; /* password */ char other [BOOT_OTHER_LEN]; /* avail to application */ int procNum; /* processor number */ int flags; /* configuration flags */ } BOOT_PARAMS; /* function declarations */ extern STATUS bootBpAnchorExtract (char * string, char ** pAnchorAdrs); extern STATUS bootNetmaskExtract (char * string, int * pNetmask); extern STATUS bootScanNum (char ** ppString, int * pvalue, BOOL hex); extern STATUS bootStructToString (char * paramString, BOOT_PARAMS * pBootParams); extern char * bootStringToStruct (char * bootString, BOOT_PARAMS * pBootParams); extern void bootParamsErrorPrint (char * bootString, char * pError); extern void bootParamsPrompt (char * string); extern void bootParamsShow (char * paramString); #ifdef __cplusplus } #endif /* __cplusplus */ #endif /* __INCbootLibh */ 1.3.8 C文件格式习惯 本节描述了源代码的文件格式。WRS的工具refgen用来自动生成每一个模块的参考条目(HTML方式)。更多的信息请参考Tornado BSP Developer's Kit User's Guide: document.tion Guidelines。 布局: 为了让refgen工作正常,模块源文件必须按照一个简单的规则来存放,文件sample.c位于installDir/target/unsupported/tools/mangen提供了一个例子,以及更多的信息。 |