#学习总结 C语言编程规范

news/2024/7/8 1:43:36

养成一套正确的编程习惯,对职业发展尤为重要。出来参加工作之后,发现公司都有一套程序模板和各种命名规范,在此总结一下个人日常的C语言编程规范。每个人的要求和编程习惯可能都有所不同,仅供学习参考。

文章目录

  • 前言
  • 一、头文件
    • 1.1. 每一个 .c 文件应有一个同名 .h 文件
    • 1.2.头文件中适合放置函数和变量等接口声明,不适合放置函数实现
    • 1.3. .c/.h 文件尽量不包含用不到的头文件
    • 1.4..h内编写内部 #include 保护符(#define 保护)
    • 1.5.#include< >和#include" "引用头文件的区别
  • 二、函数
    • 2.1.一个函数仅完成一件功能
    • 2.2.重复代码应该尽可能提炼成函数
    • 2.4.新增函数的代码块嵌套不超过 4 层
    • 2.5.没有被调用的函数和变量要及时删除
    • 2. 6.建议函数不变参数时使用 const。
    • 2.7.建议函数不是外部可见,应该增加 static 关键字
  • 三、标识符命名与定义
    • 3.1.标识符的命名要清晰、明了
    • 3.2.除了常见的通用缩写以外,不使用单词缩写,不得使用汉语拼音
    • 3.3.用正确的反义词组命名具有互斥意义的变量或相反动作的函数等
    • 3.4.尽量避免名字中出现数字编号,除非逻辑上的确需要编号
    • 3.5.文件命名统一采用小写字符
    • 3.6.禁止使用单字节命名变量
    • 3.7. 函数命名规则
    • 3.8. 宏的命名规则
  • 四、变量
    • 4.1. 一个变量只有一个功能,不能把一个变量用作多种用途
    • 4.2. 一个变量尽可能结构单一,不要设计面面俱到的数据结构
    • 4.3. 局部变量与全局变量尽可能不同名
    • 4.4. 在首次使用前初始化变量,初始化的地方离使用的地方越近越好
    • 4.5. 尽量减少没有必要的数据类型默认转换与强制转换
  • 五、宏、常量
    • 5.1. 用宏定义表达式时,要使用完备的括号。
    • 5.2. 将宏所定义的多条表达式放在大括号中
    • 5.3. 使用宏时,不允许参数发生变化
    • 5.4. 除非必要,应尽可能使用函数代替宏
    • 5.5. 常量建议使用 const 定义代替宏
    • 5.5. 宏定义中尽量不使用 return 、goto 、continue 、break 等改变程序流程的语句
  • 六、注释
    • 6.1. 在代码的功能、意图层次上进行注释
    • 6.2. 注释应放在其代码上方相邻位置或右方
    • 6.3. 对于 switch 语句下的 case 语句进行注释
    • 6.4. 文件头、函数头、全局常量变量、类型定义的注释格式采用工具可识别的格式
    • 6.5.避免在注释中使用缩写,除非是业界通用或子系统内标准化的缩写
    • 6.6.同一产品或项目组统一注释风格
    • 6.7. 建议注释多使用中文
  • 七、表达式
    • 7.1.表达式的值在标准所允许的任何运算次序下都应该是相同的
    • 7.2.函数的调用不要作为另一个函数的参数使用
    • 7.3.赋值语句不要写在 if 等语句中
    • 7.4.用括号明确表达式的操作顺序,避免过分依赖默认优先级
    • 7.5.赋值操作符不能使用在产生布尔值的表达式上
  • 八、排版与格式
    • 8.1.程序块采用缩进风格编写,每级缩进为 4 个空格
    • 8.2.相对独立的程序块之间、变量说明之后必须加空行
    • 8.2.语句过长,可分行写
    • 8.3.多个短语句不允许写在同一行内
    • 8.4. if 、for 、do、while 、case 、switch 、default 等语句独占一行
    • 8.5.在两个以上的变量进行对等操作时,它们之间的操作符前后要加空格
    • 8.6.注释符与注释内容之间要用一个空格进行分隔
  • 总结


前言

本规范是为编写 C 语言程序提供一个基本原则,从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对 C 语言编程提出一个编程习惯的建议。


一、头文件

对于 C 语言来说,头文件的设计体现了大部分的系统设计。不合理的头文件实际上是不合理的设计。

1.1. 每一个 .c 文件应有一个同名 .h 文件

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

1.2.头文件中适合放置函数和变量等接口声明,不适合放置函数实现

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

  • 内部使用的函数(使用范围在一个文件内)声明不应放在头文件中。
  • 内部使用的宏、枚举、结构定义不应放入头文件中。
  • 全局变量定义不应放在头文件中,应放在 .c 文件中,全局变量的声明放在 .h 文件中

1.3. .c/.h 文件尽量不包含用不到的头文件

很多系统中头文件包含关系复杂,开发人员为了省事起见,可能不会去一一钻研,直接包含所有的头文件,甚至有些干脆把所有的头文件放入一个头文件中,然后在所有的.c 文件中包含此文件。
这种只图一时省事的做法,对后来人的维护造成了麻烦。

1.4…h内编写内部 #include 保护符(#define 保护)

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

#ifndef __FILENAME_H
#define __FILENAME_H

#endif

定义包含保护符时,应该遵守如下规则:
1)保护符使用唯一名称。
2)不要在受保护部分的前后放置代码或者注释。例外情况:头文件的版权声明部分以及头文件的整体注释部分(如阐述此头文件的开发背景、使用注意事项等)可以放在保护符(#ifndef XX_H)前。

1.5.#include< >和#include" "引用头文件的区别

  • #include< > 引用的是编译器的类库路径里面的头文件。
    假如你编译器定义的自带头文件引用在 C:\Keil\c51\INC\ 下面,则 #include<stdio.h> 引用的就是 C:\Keil\c51\INC\stdio.h 这个头文件,不管你的项目在什么目录里, C:\Keil\c51\INC\stdio.h 这个路径就定下来了,一般是引用自带的一些头文件,如: stdio.h、conio.h、iostream 等。
  • #include" " 引用的是你程序目录的相对路径中的头文件。
    假如你的项目目录是在 D:\Projects\tmp\ ,则 #include"my.h" 引用的就是 D:\Projects\tmp\my.h 这个头文件,一般是用来引用自己写的一些头文件。如果使用 #include" " ,它是会先在你项目的当前目录查找是否有对应头文件,如果没有,它还是会在对应的引用目录里面查找对应的头文件。例如,使用 #include “stdio.h” 如果在你项目目录里面,没有 stdio.h 这个头文件,它还是会定位到 C:\Keil\c51\INC\stdio.h 这个头文件的。

总结<>和" "包含头文件的本质区别是:查找的策略的区别
" " :自己代码所在的目录下查找,如果找不到,则在库函数的头文件目录下查找‘
< >:直接去库函数头文件所在的目录下查找。

二、函数

函数设计的精髓:编写整洁函数,同时把代码有效组织起来。
整洁函数要求:代码简单直接、不隐藏设计者的意图、用干净利落的抽象和直截了当的控制语句将函
数有机组织起来。代码的有效组织包括:逻辑层组织和物理层组织两个方面。

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

2.1.一个函数仅完成一件功能

一个函数实现多个功能给开发、使用、维护都带来很大的困难。将没有关联或者关联很弱的语句放到同一函数中,会导致函数职责不明确,难以理解,难以测试和改动。
如下面这个例子就是一个比较典型的实现按键检测的功能

u8 key_scan(u8 mode)
{	 
	static u8 key_up=1;//按键按松开标志
	if(mode)
		key_up=1;  //支持连按		  
	if(key_up&&(KEY1==1||KEY1==2||KEY3==0||KEY4==1))
	{
		delay_ms(10);//去抖动 
		key_up=0;
		if(KEY1==0)return KEY1_PRES;
		else if(KEY2==0)return KEY2_PRES;
		else if(KEY3==0)return KEY3_PRES;
		else if(KEY4==0)return KEY4_PRES;
	}
	else if(KEY1==1&&KEY2==1&&KEY2==1&&KEY4==1)
		key_up=1; 	    
 	return 0;// 无按键按下
}

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

重复代码提炼成函数可以使代码结构简洁清晰,降低后期维护难度。
当一段代码重复两次时,即应考虑消除重复,当代码重复超过三次时,应当立刻着手消除重复。一般
情况下,可以通过提炼函数的形式消除重复代码。

2.4.新增函数的代码块嵌套不超过 4 层

本规则仅对新增函数做要求,对已有的代码建议不增加嵌套层次。
函数的代码块嵌套深度指的是函数中的代码控制块(例如:if、for、while、switch 等)之间互相包含
的深度。每级嵌套都会增加阅读代码时的脑力消耗,因为需要在脑子里维护一个“栈”(比如,进入条
件语句、进入循环)。公司规定新增函数的代码块嵌套不超过 4 层。

2.5.没有被调用的函数和变量要及时删除

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

2. 6.建议函数不变参数时使用 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;
}

2.7.建议函数不是外部可见,应该增加 static 关键字

如果一个函数只是在同一文件中的其他地方调用,那么就用 static 声明。使用 static 确保只是
在声明它的文件中是可见的,并且避免了和其他文件或库中的相同标识符发生混淆的可能性。

三、标识符命名与定义

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

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

标识符的命名规则历来是一个敏感话题, 典型的命名风格如 unix 风格、windows 风格等等, 从来
无法达成共识。实际上,各种风格都有其优势也有其劣势,而且往往和个人的审美观有关。我们对标
识符定义主要是为了让团队的代码看起来尽可能统一,有利于代码的后续阅读和修改,产品同一采用
unix like 命名风格。

3.1.标识符的命名要清晰、明了

标识符的命名要清晰、明了,有明确含义,同时使用完整的单词或大家基本可以理解的缩写,
避免使人产生误解。
说明:尽可能给出描述性名称,不要节约空间,让别人很快理解你的代码更重要。
示例:好的命名:

int error_number;
int number_of_completed_connection;

不好的命名:使用模糊的缩写或随意的字符:

int n;
int nerr;
int n_comp_conns;

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

说明:较短的单词可通过去掉“元音”形成缩写,较长的单词可取单词的头几个字母形成缩写,一些单
词有大家公认的缩写,常用单词的缩写必须统一。协议中的单词的缩写与协议保持一致。对于某个系
统使用的专用缩写应该在注视或者某处做统一说明。
示例:一些常见可以缩写的例子:

argument 可缩写为 arg
buffer 可缩写为 buff
clock 可缩写为 clk
command 可缩写为 cmd
compare 可缩写为 cmp
configuration 可缩写为 cfg
device 可缩写为 dev
error 可缩写为 err
hexadecimal 可缩写为 hex
increment 可缩写为 inc 
initialize 可缩写为 init  //这个经常用于IO初始化函数
maximum 可缩写为 max
message 可缩写为 msg
minimum 可缩写为 min
parameter 可缩写为 para
previous 可缩写为 prev
register 可缩写为 reg
semaphore 可缩写为 sem
statistic 可缩写为 stat
synchronize 可缩写为 sync
temp 可缩写为 tmp

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

示例:

add/remove               begin/end     create/destroy
insert/delete           first/last     get/release
increment/decrement       put/get        add/delete
lock/unlock            open/close       min/max
old/new               start/stop       next/previous
source/target           show/hide       send/receive
source/destination     copy/paste        up/down

3.4.尽量避免名字中出现数字编号,除非逻辑上的确需要编号

示例:如下命名,使人产生疑惑。

#define EXAMPLE_0_TEST_
#define EXAMPLE_1_TEST_

应改为有意义的单词命名:

#define EXAMPLE_UNIT_TEST_
#define EXAMPLE_ASSERT_TEST_

3.5.文件命名统一采用小写字符

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

3.6.禁止使用单字节命名变量

禁止使用单字节命名变量,但允许定义 i 、j 、k 作为局部循环变量。最好是使用名词或者形容词+名词方式命名变量。

3.7. 函数命名规则

函数命名应以函数要执行的动作命名,一般采用动词或者动词+名词的结构。
示例:找到当前进程的当前目录的函数

u32 get_current_directory(u32 buffer_length, u8 * buffer);

3.8. 宏的命名规则

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

#define PI_ROUNDED 3.14

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

四、变量

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

一个变量只用来表示一个特定功能,不能把一个变量作多种用途,即同一变量取值不同时,其
代表的意义也不同。
反例:返回具有两种功能

WORD DelRelTimeQue(void )
{
WORD Locate;
Locate = 3;
Locate = DeleteFromQue(Locate); /* Locate 具有两种功能:位置和函数 DeleteFromQue 的返回值
*/
return Locate;
}

正确做法:使用两个变量

WORD DelRelTimeQue( void )
{
WORD Ret;
WORD Locate;
Locate = 3;
Ret = DeleteFromQue(Locate);
return Ret;
}

4.2. 一个变量尽可能结构单一,不要设计面面俱到的数据结构

相关的一组信息才是构成一个结构体的基础,结构的定义应该可以明确的描述一个对象,而不
是一组相关性不强的数据的集合。
设计结构时应力争使结构代表一种现实事务的抽象,而不是同时代表多种。结构中的各元素应代表同
一事务的不同侧面,而不应把描述没有关系或关系很弱的不同事务的元素放到同一结构中。
示例:如下结构不太清晰、合理。

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'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 teacher_ind; /* teacher index */
} TEACHER;
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 int teacher_ind; /* his teacher index */
} STUDENT;

4.3. 局部变量与全局变量尽可能不同名

尽管局部变量和全局变量的作用域不同而不会发生语法错误,但容易使人误解。

4.4. 在首次使用前初始化变量,初始化的地方离使用的地方越近越好

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

// 不可取的初始化:无意义的初始化
int speedup_factor = 0;
if (condition)
{
speedup_factor = 2;
}
else
{
speedup_factor = -1;
}
// 不可取的初始化:初始化和声明分离
int speedup_factor;
if (condition)
{
speedup_factor = 2;
}
else
{
speedup_factor = -1;
}
// 较好的初始化:使用默认有意义的初始化
int speedup_factor = -1;
if (condition)
{
speedup_factor = 2;
}
// 较好的初始化使用: 减少数据流和控制流的混合
int speedup_factor = condition?2:-1;
// 较好的初始化:使用函数代替复杂的计算流
int speedup_factor = ComputeSpeedupFactor();

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

当进行数据类型强制转换时,其数据的意义、转换后的取值等都有可能发生变化,而这些细节
若考虑不周,就很有可能留下隐患。
示例:如下赋值,多数编译器不产生告警,但值的含义还是稍有变化。

char ch;
unsigned short int exam;
ch = -1;
exam = ch; // 编译器不产生告警,此时 exam 为 0xFFFF。

五、宏、常量

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))

这是因为:
如果定义#define RECTANGLE_AREA(a, b) a * b 或#define RECTANGLE_AREA(a, b) (a * b)
则 c/RECTANGLE_AREA(a, b) 将扩展成 c/a * b , c 与 b 本应该是除法运算,结果变成了乘法运算,
造成错误。
如果定义#define RECTANGLE_AREA(a, b) (a) * (b)
则 RECTANGLE_AREA(c + d, e + f) 将扩展成: (c + d * e + f), d 与 e 先运算,造成错误。

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

更好的方法是多条语句写成 do while(0) 的方式。
示例:看下面的语句,只有宏的第一条表达式被执行。

#define FOO(x) \
printf( "arg is %d\n" , x); \  //只执行这条语句
do_something_useful(x);   //这条语句不执行

用大括号定义的方式可以解决上面的问题:

#define FOO(x) { \
printf( "arg is %s\n" , x); \
do_something_useful(x); \
}

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

示例:如下用法可能导致错误

#define SQUARE(a) ((a) * (a))
int a = 5;
int b;
b = SQUARE(a++); // 结果: a = 7 ,即执行了两次增。

正确的用法是:

b = SQUARE(a);
a++; // 结果: a = 6,即只执行了一次增。

同时也建议即使函数调用,也不要在参数中做变量变化操作,因为可能引用的接口函数,在某个版本
升级后,变成了一个兼容老版本所做的一个宏,结果可能不可预知。

5.4. 除非必要,应尽可能使用函数代替宏

宏对比函数,有一些明显的缺点:

  • 宏缺乏类型检查,不如函数调用检查严格。
  • 宏展开可能会产生意想不到的副作用,如#define SQUARE(a) (a) * (a) 这样的定义,如果是
    SQUARE(i++),就会导致 i 被加两次;如果是函数调用 double square(double a) {return a * a;} 则不会
    有此副作用。
  • 以宏形式写的代码难以调试难以打断点,不利于定位问题。
  • 宏如果调用的很多,会造成代码空间的浪费,不如函数空间效率高。
    示例:下面的代码无法得到想要的结果:
#define MAX_MACRO(a, b) ((a) > (b) ? (a) : (b))
int MAX_FUNC(int a, int b) {
return ((a) > (b) ? (a) : (b));
}
int testFunc()
{
unsigned int a = 1;
int b = -1;
printf( "MACRO: max of a and b is: %d\n" , MAX_MACRO(++a, b));
printf( "FUNC : max of a and b is: %d\n" , MAX_FUNC(a, b));
return 0;
}

上面宏代码调用中,结果是(a < b) ,所以 a 只加了一次,所以最终的输出结果是:

MACRO: max of a and b is: -1
FUNC : max of a and b is: 2

5.5. 常量建议使用 const 定义代替宏

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

#define ASPECT_RATIO 1.653

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

const double ASPECT_RATIO = 1.653;

这种方法很有效,但有两个特殊情况要注意。首先,定义指针常量时会有点不同。因为常量定义一般
是放在头文件中(许多源文件会包含它),除了指针所指的类型要定义成 const 外,重要的是指针也
经常要定义成 const 。例如,要在头文件中定义一个基于 char* 的字符串常量,你要写两次 const :

const char * const authorName = "Scott Meyers" ;

5.5. 宏定义中尽量不使用 return 、goto 、continue 、break 等改变程序流程的语句

如果在宏定义中使用这些改变流程的语句,很容易引起资源泄漏问题,使用者很难自己察觉。
示例:在某头文件中定义宏 CHECK_AND_RETURN

#define CHECK_AND_RETURN(cond, ret) { if (cond == NULL_PTR) { return ret;}}

然后在某函数中使用(只说明问题,代码并不完整):

Mem1 = VOS_MemAlloc(...);
CHECK_AND_RETURN(pMem1 , ERR_CODE_XXX)
pMem2 = VOS_MemAlloc(...);
CHECK_AND_RETURN(pMem2,ERR_CODE_XXX)/* 此时如果 pMem2==NULL_PTR 则 pMem1未释放函数就返回了,造成内存泄漏。*/

所以说,类似于 CHECK_AND_RETURN 这些宏,虽然能使代码简洁,但是隐患很大,使用须谨慎。

六、注释

6.1. 在代码的功能、意图层次上进行注释

即注释解释代码难以直接表达的意图,而不是重复描述代码。注释的目的是解释代码的目的、功能和采用的方法,提供代码以外的信息,帮助读者理解代码,防止没必要的重复注释信息。对于实现代码中巧妙的、晦涩的、有趣的、重要的地方加以注释。注释不是为了名词解释( what ),而是说明用途( why)。
错误示例:如下注释纯属多余的

++i;     /*  i 的值自增 */
if (receive_flag)  /* 如果receive_flag的值为真 */

这种无价值的注释不应出现。

6.2. 注释应放在其代码上方相邻位置或右方

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

/* 活动统计任务编号 */
#define MAX_ACT_TASK_NUMBER 1000
#define MAX_ACT_TASK_NUMBER 1000 /* 活动统计任务编号 */

可按如下形式说明枚举/ 数据/ 联合结构

/* 具有sccp用户基元消息名称的sccp接口 */
enum SCCP_USER_PRIMITIVE
{
N_UNITDATA_IND, /* sccp通知sccp用户单元数据到来 */
N_NOTICE_IND, /* sccp通知用户7号网络无法传输此消息*/
N_UNITDATA_REQ, /* sccp用户单位数据传输请求*/
};

6.3. 对于 switch 语句下的 case 语句进行注释

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

case CMD_FWD:
ProcessFwd();
/* 跳进 case CMD_A */
case CMD_A:
ProcessA();
break ;
// 对于中间无处理的连续 case ,已能较清晰说明意图,不强制注释。
switch (cmd_flag)
{
case CMD_A:
case CMD_B:
{
ProcessCMD();
break ;
}
⋯⋯
}

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

采用工具可识别的注释格式,例如 doxygen 格式,方便工具导出注释形成帮助文档。
以 doxygen 格式为例,文件头,函数和全部变量的注释的示例如下:
文件头注释:

/**
* @file (本文件的文件名 eg: mib.h )
* @brief (本文件实现的功能的简述)
* @version 1.1 (版本声明)
* @author (作者, eg:晖大郎)
* @date (文件创建日期, eg: 2023 年 4 月 26 日)
*/

函数头注释:

/**
* @name - commit_set_request
* @Description: 向接收方发送 SET 请求
* @param req - 指向整个 SNMP SET 请求报文.
* @param ind - 需要处理的 subrequest 索引.
* @return SNMP_ERROR_SUCCESS -成功:
*         SNMP_ERROR_COMITFAIL -失败
*/
int commit_set_request(Request *req, int ind)
{
	;
}

函数声明处注释描述函数功能、性能及用法,包括输入和输出参数、函数返回值、可重入的要求等;定义处详细描述函数功能和实现要点,如实现的简要步骤、实现的理由、设计约束等。重要的、复杂的函数,提供外部使用的接口函数应编写详细的注释,需要注意的是并非所有函数都必须写注释,建议针对这样的函数写注释:重要的、复杂的函数,提供外部使用的接口函数。
全局变量注释:

/* 模拟的 Agent MIB */
agentpp_simulation_mib * g_agtSimMib;

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

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

6.6.同一产品或项目组统一注释风格

6.7. 建议注释多使用中文

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

延伸阅读材料:
1、《代码大全第 2 版》( Steve McConnell 著金戈/ 汤凌/ 陈硕/ 张菲译电子工业出版社 2006 年 3 月)" 第 32 章自说明代码" 。
2、《代码整洁之道》( Robert C.Martin 著韩磊译人民邮电出版社 2010 年 1 月) 第四章" 注释" 。

七、表达式

7.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);
p++;

4、函数调用,函数调用

int g_var = 0;
int fun1()
{
g_var += 10;
return g_var;
}
int fun2()
{
g_var += 100;
return g_var;
}
int x = fun1() + fun2();

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

int x = fun1();
x = x + fun2();

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

x = y = y = z / 3;
x = y = y++;

6、volatile 访问
说明:限定符 volatile 表示可能被其它途径更改的变量,例如硬件自动更新的寄存器。编译器不会优
化对 volatile 变量的读取。
示例:下面的写法可能无法实现作者预期的功能:

/* volume 变量被定义为 volatile 类型*/
UINT16 x = ( volume << 3 ) | volume; /* 在计算了其中一个子表达式的时候, volume 的值可能已
经被其它程序或硬件改变,导致另外一个子表达式的计算结果非预期,可能无法实现作者预期的功能
*/

7.2.函数的调用不要作为另一个函数的参数使用

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

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());  //fun1()、fun2()不要作为printf函数的参数使用
}

上面的代码,使用断点调试起来也比较麻烦,阅读起来也不舒服,所以不要为了节约代码行,而写这种代码。

7.3.赋值语句不要写在 if 等语句中

因为 if 语句中,会根据条件依次判断,如果前一个条件已经可以判定整个条件,则后续条件语句不会再运行,所以可能导致期望的部分赋值没有得到运行。
示例:

int main( int argc, char *argv[], char *envp[])
{
int a = 0;
int b;
if ((a == 0) || ((b = fun1()) > 10))
{
printf( "a: %d\n" , a);
}
printf( "b: %d\n" , b);
}

7.4.用括号明确表达式的操作顺序,避免过分依赖默认优先级

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

  1. 一元操作符,不需要使用括号
x = ~a; /* 一元操作符,不需要括号*/
x = -a; /* 一元操作符,不需要括号*/
  1. 二元以上操作符,如果涉及多种操作符,则应该使用括号
x = a + b + c; /* 操作符相同,不需要括号*/
x = f ( a + b, c ) /* 操作符相同,不需要括号*/
if (a && b && c) /* 操作符相同,不需要括号*/
x = (a * 3) + c + d; /* 操作符不同,需要括号*/
x = ( a == b ) ? a : ( a –b ); /* 操作符不同,需要括号*/
  1. 即使所有操作符都是相同的,如果涉及类型转换或者量级提升,也应该使用括号控制计算的次序。除了逗号(,) ,逻辑与(&&) ,逻辑或(||) 之外, C 标准没有规定同级操作符是从左还是从右开始。
    计算,
    以下代码将 3 个浮点数相加:
f4 = f1 + f2 + f3;

以上表达式存在多种计算次序: f4 = (f1 + f2) + f3 或 f4 = f1 + (f2 + f3) ,浮点数计算过程中可能四舍五入,量级提升,计算次序的不同会导致 f4 的结果不同,以上表达式在不同编译器上的计算结果可能不一样,建议增加括号明确计算顺序。

7.5.赋值操作符不能使用在产生布尔值的表达式上

如果布尔值表达式需要赋值操作, 那么赋值操作必须在操作数之外分别进行。这可以帮助避免=和= = 的混淆,帮助我们静态地检查错误。
示例:

x = y;
if (x != 0)
{
fusion ();
}

不能写成:

if (( x = y ) != 0)
{
foo ();
}

八、排版与格式

8.1.程序块采用缩进风格编写,每级缩进为 4 个空格

当前各种编辑器、IDE 都支持 TAB 键自动转空格输入,需要打开相关功能并设置相关功能。编辑器、IDE 如果有显示 TAB 的功能也应该打开,方便及时纠正输入错误。宏定义、编译开关、条件预处理语句可以顶格(或使用自定义的排版方案,但产品、模块内必须保持一致)

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

示例:如下例子不符合规范

if (!valid_ni(ni))
{
// program code
...
}
repssn_ind = ssn_data[index].repssn_index;
repssn_ni = ssn_data[index].ni;

应如下书写:

if (!valid_ni(ni))
{
// program code
...
}
repssn_ind = ssn_data[index].repssn_index;
repssn_ni = ssn_data[index].ni;

8.2.语句过长,可分行写

一条语句不能过长,如不能拆分需分行写。一行到底多少字符换行比较合适,产品可以自行确定。
换行时有如下建议:
换行时要增加一级缩进,使代码可读性更好;
低优先级操作符处划分新行;换行时操作符应该也放下来,放在新行首;
换行时建议一个完整的语句放在一行,不要根据字符数断行

  • 分行符号的作用
    分行符号的作用是将代码分成多行,使代码更加易读。通常情况下,一行代码应该只有一个语句,多个语句可以用分号’;’
    隔开。同样,一个语句也可以写成多行,这时候需要使用反斜杠 ‘’ 将语句连接起来
if ((temp_flag_var == TEST_FLAG) \
&&(((temp_counter_var - TEST_COUNT_BEGIN) % TEST_COUNT_MODULE) >= \
TEST_COUNT_THRESHOLD))
{
// process code
}

8.3.多个短语句不允许写在同一行内

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

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

较好的排版:

int a = 5;
int b= 10;

8.4. 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 语句很长,或者已经有换行,建议 { 使用独占一行的写法。

8.5.在两个以上的变量进行对等操作时,它们之间的操作符前后要加空格

在两个以上的关键字、变量、常量进行对等操作时,它们之间的操作符之前、之后或者前后要加空格;进行非对等操作时,如果是关系密切的立即操作符(如->)后不应加空格。
采用这种松散方式编写代码的目的是使代码更加清晰。
在已经非常清晰的语句中没有必要再留空格,如括号内侧(即左括号后面和右括号前面)不需要加空格,多重括号间不必加空格,因为在 C 语言中括号已经是最清晰的标志了。在长语句中,如果需要加的空格非常多,那么应该保持整体清晰,而在局部不加空格。给操作符留空格时不要连续留两个以上空格。
示例:

int a, b, c; //逗号、分号只在后面加空格

比较操作符,赋值操作符“=”、“+=”,算术操作符“+”、“%”,逻辑操作符“&&”、“&”,位域操作符“<<”、”^”等双目操作符的前后加空格。

if (current_time >= MAX_TIME_VALUE)
a = b + c;
a *= 2;
a = b ^ 2;

“!” 、“~” 、“++” 、“–” 、“&” (地址操作符)等单目操作符前后不加空格

*p = 'a' ; // 内容操作"*" 与内容之间
flag = !is_empty; // 非操作"!" 与内容之间
p = &mem; // 地址操作"&" 与内容之间
i++; // "++","--" 与内容之间

“->” 、“.” 前后不加空格

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

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

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

8.6.注释符与注释内容之间要用一个空格进行分隔

注释符(包括, /* ?,// ?,*/ ?)与注释内容之间要用一个空格进行分隔。这样可以使注释的内容部分更清晰。
现在很多工具都可以批量生成、删除’//’ 注释,这样有空格也比较方便统一处理。


总结

以上就是对C语言编程规范的学习总结,从头文件、函数、标识符命名与定义、变量、宏常量、注释、表达式和排版等方面制定了编程规范,并提出合理的建议,最终目的都是提高开发效率。根据公司要求和每个人的编程习惯不同,大家可以借鉴参考。



http://lihuaxi.xjx100.cn/news/1066629.html

相关文章

cgroup配合tc工具对进程的上下行(出口入口)速度进行限制,附测试脚本

在开始之前&#xff0c;如果不熟悉cgroup、tc、以及ifb虚拟网卡&#xff0c;建议先了解一下再进行试验 注&#xff1a;为什么会用到ifb虚拟网卡呢&#xff0c;因为Linux自带的tc是一套控发不控收的框架。具体ifb详细原理&#xff0c;自行研究&#xff0c;网上资料多的很。 对出…

【Python | 基础语法篇】01、字面量、注释、变量、数据类型及转换

目录 一、字面量 1.1 什么是字面量 1.2 常用的值类型 1.3 字符串 1.4 如何在代码中写它们 1.5 总结 二、注释 2.1 注释的作用 2.2 注释的分类 2.3 注释实战 2.4 总结 2.5 思考 三、变量 3.1 什么是变量 3.2 案例——模拟钱包 3.3 变量的特征 3.4 思考 3.5 …

欧几里得算法、扩展欧几里得算法(特解、应用、通解)

文章目录 1. 欧几里得算法&#xff08;也叫辗转相除法&#xff09;1.1 直接上模拟1.2 几何理解1.3 用代数方法证明 g c d ( a , b ) g c d ( b , a % b ) gcd(a, b) gcd(b, a \% b) gcd(a,b)gcd(b,a%b)1.3.1 左推右&#xff1a; g c d ( a , b ) g c d ( b , a % b ) gcd(a…

2023年制造业产品经理NPDP认证报名找弘博创新

产品经理国际资格认证NPDP是新产品开发方面的认证&#xff0c;集理论、方法与实践为一体的全方位的知识体系&#xff0c;为公司组织层级进行规划、决策、执行提供良好的方法体系支撑。 【认证机构】 产品开发与管理协会&#xff08;PDMA&#xff09;成立于1979年&#xff0c;是…

IDEA执行main方法的时候,会编译整个项目的问题

​ 今天遇到一个奇怪的问题&#xff0c;执行main方法会构建整个项目&#xff0c;速度奇慢无比。我就执行个main方法&#xff0c;搞这么复杂干嘛&#xff1f;经过查阅网上的攻略&#xff0c;最终找到解决方法。 以下方法适用于idea版本 问题解决方法&#xff1a; 参考 https://…

【脚本笔记】EditorApplication

EditorApplication 是编辑器下的主要程序类&#xff0c;为我们提供丰富的方法和事件等。 静态变量 applicationContentsPath 返回你当前Unity编辑器的Data文件路径。如D:/UnityVersions/2021.3.22f1c1/Editor/Data applicationPath 返回你当前Unity编辑器的执行文件位置。如…

五种原因导致孩子易患口腔溃疡,专家为你一一支招

最近&#xff0c;常接到电话咨询&#xff1a;疫情期间&#xff0c;孩子宅在家&#xff0c;反复起“口疮”怎么办&#xff1f; 这里说到的“口疮”&#xff0c;即是一种常见的口腔黏膜疾病——口腔溃疡。口腔溃疡的发病率较高&#xff0c;不仅成年人可能患病&#xff0c;不少儿…

5.4 Javascript中的浅拷贝与深拷贝

Javascript中的浅拷贝与深拷贝 目录一、Javascript中的浅拷贝与深拷贝概述1. 浅拷贝&#xff08;Shallow Copy&#xff09;2. 深拷贝&#xff08;Deep Copy&#xff09; 二、Javascript中的浅拷贝与深拷贝概述1. 浅拷贝&#xff08;Shallow Copy&#xff09;a. 使用数组的浅拷贝…