C语言编程规范 — 变量、常量、表达式、控制语句

C语言编程规范 — 变量、常量、表达式、控制语句,第1张

一、变量

【原则】1.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;
}

【原则】1.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;

【原则】1.3  不用或者少用全局变量

【说明】单个源文件内部可以使用 static 修饰的全局变量,可以将其理解为类的私有成员变量。

全局变量应该是模块的私有数据,不能作为对外的接口使用,使用 static 类型定义,可以有效防止外部文件的非正常访问,建议定义一个 STATIC 宏,在调试阶段,将 STATIC 定义为 static,版本发布时,改为空,以便于后续的打补丁等 *** 作。

#ifdef _DEBUG
#define STATIC static
#else
#define STATIC
#endif

直接使用其他模块的的私有数据,将使模块间的关系逐渐走向 “剪不断理还乱” 的耦合状态,这种情形是不允许的,模块与模块之间最好是低耦合关系。

【规则】1.1  防止局部变量与全局变量同名。

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

【规则】1.2  网络通讯过程中使用的结构,必须注意字节序

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

由于位域在不同字节序下,表现看起来差别更大,所以更需要注意对于这种跨平台的交互,数据成员发送前,都应该进行主机序到网络序的转换;接收时,也必须进行网络序到主机序的转换。

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

说明:强烈建议 1.3(在首次使用变量前初始化变量,初始化的地方离使用的地方越近越好。)可以有效避免为初始化的错误。

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

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

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

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

定义的接口应该有比较明确的意义,比如一个风扇管理功能模块,有自动和手动工作模式,那么设置、查询工作模块就可以定义接口为SetFanWorkMode,GetFanWorkMode;查询转速就可以定义为GetFanSpeed;风扇支持节能功能开关,可以定义EnabletFanSavePower等等。

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

说明:未初始化变量是 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();

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

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

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

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

错误示例:如下赋值,多数编译器不产生告警,但值的含义还是稍有变化。

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

规则2.1  不允许使用魔鬼数字。

什么是魔鬼数字?在代码中没有具体含义的数字或字符串。

说明:使用魔鬼数字的弊端:代码难以理解,影响了代码的可读性。当程序中出现的魔鬼数字过多时,代码的可维护性将会急剧下降,代码变得难以修改,并容易引入错误。

使用明确的物理状态或物理意义的名称增加信息,并能够提供单一的维护点。

解决途径:

对于局部使用的唯一含义的魔鬼数字,可以在代码周围增加说明注释,也可以定义局部 const 变量,变量命名自注释。

对于广泛使用的数字,必须定义 const 全局变量/宏;同样变量/宏命名应是自注释的。

0 作为一个特殊的数字,作为一般默认值没有使用歧义时,不用特别定义。

规则2.2  需要对外公开的常量放在头文件中,不需要对外公开的常量放在定义文件的头部。为便于管理,可以把不同模块的常量集中存放在一个公共的头文件中。

规则2.3  如果某一常量与其它常量密切相关,应在定义中包含这种关系,而不应给出一些孤立的值。

示例:

const float RADIUS = 100;
const float MIAMETER = RADIUS * 2;
三、表达式

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

说明:除了少数 *** 作符(函数调用 *** 作符()、&&、||、? : 和 , (逗号))之外,子表达式所依据的运算次序是未指定的并会随时更改。注意,运算次序的问题不能使用括号来解决,因为这不是优先级的问题。

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

1、自增或自减 *** 作符

示例

// 错误示例
x = b[i] + i++;
// b[i] 的运算是先于还是后于 i++ 的运算,表达式会产生不同的结果,把自增运算作为单独的语句,可以避免这个问题

// 正确写法
x = b[i];
i++;

2、函数参数

说明:函数参数通常是从右到左压栈,但函数参数的计算次序不一定与压栈次序相同。

示例

//错误示例
x = func(i++, i);

//正确写法:应该修改代码明确先计算第一个参数:
i++;
x = func(i, i);

3、函数指针

说明:函数参数和函数自身地址的计算次序未定义。

示例

// 错误示例
p->tast_start_fn(p++);

// 正确写法:求函数地址p与计算p++无关,结果是任意值,必须单独计算 p++
p->tast_start_fn(p);
p++;

4、函数调用

错误示例

int g_var = 0;

int func1()
{
    g_var += 10;
    return g_var;
}

int func2()
{
    g_var += 100;
    return g_var;
}

int x = func1() + func2();

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

正确写法:应该修改代码,明确 func1、func2 的计算次序:

int x = func1();
x += func2();

5、嵌套赋值语句

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

错误示例

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

6、volatile 访问

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

错误示例:下面的写法可能无法实现作者预期的功能:

// volume 变量被定义为 volatile 类型
uint16 x = (volume << 3) | volume;

在计算了其中一个子表达式的时候(先计算表达式:volume << 3),volume 的值可能已经被其他程序或硬件改变,导致另外一个子表达式的计算结果非预期,可能无法实现作者预期的功能。

建议3.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());
}

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

建议3.2  赋值语句不要写在 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);
}

上述 if 语句中,(a == 0) 的表达式逻辑结果为 true,那么 后半部分的 ((b = func() > 10) 表达式不会得到执行,这就存在可能的隐患。

  • 赋值表达式作为函数参数来使用,参数的压栈顺序不同可能导致结果未知

错误示例:看如下代码,能否一眼看出输出结果会是什么吗?程序好理解吗?

int g_var;
int main(int argc, char *argv, char *envp[])
{
    g_var = 1;
    printf("set 1st: %d, add 2nd: %d\n", g_var = 10, g_var++);
    g_var = 1;
    printf("add 1st: %d, set 2nd: %d\n", g_var++, g_var = 10);
}

建议3.3  用括号明确表达式的 *** 作顺序,避免过分依赖默认优先级

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

1、一元 *** 作符,不需要使用括号

x = ~a;  //一元 *** 作符,不需要使用括号
x = -a;  //一元 *** 作符,不需要使用括号

2、二元以上 *** 作符,如何设计多种 *** 作符,则应该使用括号

x = a + b + c;  // *** 作符相同,不需要使用括号

x = func(a + b, c);  // *** 作符相同,不需要使用括号

if (a && b && c)  // *** 作符相同,不需要使用括号

x = (a * 3) + c + d; // *** 作符不同,需要使用括号

x = (a == b) ? a: (a - b);  // *** 作符不同,需要使用括号

3、即使所有 *** 作符都是相同的,如何设计数据类型转换或者量级提升,也应该使用括号控制计算的次序。

示例:以下代码将3个浮点数相加:

/*
除了逗号(,)、逻辑与(&&)、逻辑或(||)之外,C语言标准没有规定同级 *** 作数是从左还是从右开始计算,以上
表达式存在各种计算次序:
f4 = (f1 + f2) + f3 或 f4 = f1 + (f2 + f3),浮点数计算过程中可能四舍五入,量级提升,计算次序的不
同会导致f4的结果不同,以上表达式在不同编译器上的计算结果可能不一样,建议增加括号明确计算次序
*/
f4 = f1 + f2 + f3;    // 错误写法
f4 = (f1 + f2) + f3;  // 正确写法

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

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

示例

// 正确写法
x = y;
if (x != 0)
{
    foo();
}

// 不能写成
if ((x = y) != 0)
{
    foo();
}

// 或者更坏的写法
if (x = y)
{
    foo();
}
四、控制语句 4.1  判断语句

原则4.1.1  控制表达式的结果必须是布尔值

说明:控制表达式的结果必须是布尔值,以及如下表达式的运算结果:

  • 关系表达式:<、<=、>=、>、==、!=
  • 逻辑表达式:&&、||、!

控制表达式应用的语句包括:

  • if 语句
  • while 语句
  • do...while 语句
  • for 语句
// if语句
if (controlling expression) {
    ...
}

// while语句
while (controlling expression) {
    ...
}

// do...while语句
do {
    ...
} while(controlling expression);

//for语句
for (...; controlling expression; ...) {
    ...
}

错误示例

int value = GetValue();
if (value) { // 不符合:表达式结果不是布尔类型
    ...
}

正确示例

int value = GetValue();
if (value != 0) { // 符合:表达式结果是布尔类型
    ...
}

while (true) { //符合:特殊场景下可以使用布尔类型常量
    ...
}

char *p = GetPointer();
if (p != NULL) {  //符合
    ...
}

char *p = GetPointer();
if (p == NULL) {  //符合
    ...
}

例外:当判断条件中的表达式是指针时,可以直接使用指针作为表达式。当 p 是一个指针,通常将 if (p) 解读为 “如果 p 有效”,这是程序员意图的直接表达,因此允许写成 if (p) 的形式。

char *p = GetPointer();
if (p) {  //允许使用
    ...
}

char *p = GetPointer();
if (!p) {  //允许使用
    ...
}

建议4.1.1  && 和 || *** 作符的右侧 *** 作数不应包含副作用

说明:逻辑与(&&)、逻辑或(||) 表达式中的右 *** 作数是否被求值,取决于左 *** 作数的求值结果,当左 *** 作数的求值结果可以得出整个逻辑表达式的结果时,不会在计算右 *** 作数的结果。如果右 *** 作数包含副作用,则不能确定是否确实发生了副作用,因此,

副作用是指:对执行状态产生影响,包括:修改对象、访问volatile对象、修改文件等。在某些情况下副作用会给程序带来不必要的麻烦,其产生的错误十分难以查找定位,并降低程序的可读性。

错误示例1:如下代码中,当 flag > 0 或 value < 0 时执行 if 语句,但是value 自减的前提是 flag <= 0。尽管代码行为是正确的,也有可能是精心设计的,但是不易阅读理解,并且给维护带来不便,容易引入问题。

if (flag >0 || value-- > 0) {
    ...
}

正确写法1:如下代码示例中,明确逻辑行为(可以将公共代码提取成函数在不同分支调用):

if (flag > 0) {
    ...
} else {
    value--;
    if(value >= 0) {
        ...
    }
}

错误示例2:下面程序本意是依据指针 p 来源不同,执行不同的 *** 作。但是如下代码示例中,逻辑与(&&) 表达式的右 *** 作数申请内存,取决于左 *** 作数的判断结果。在后续的执行过程中,无法准确知道 p 是来自 getPtr()还是malloc()。

char *p = getPtr();
if ((p == NULL) && ((p = (char *)malloc(SIZE)) == NULL)) {
    return;
}
...
free(p);   //不确定释放的是否为上面malloc函数申请的内存
p = NULL;
...

正确写法2:重构上面错误示例的代码,使指针语义准确,无副作用。

char *p = getPtr();
if (p == NULL) { // 如果p不是来自getPtr(),执行本分支
    p = (char *)malloc(SIZE);
    ...          //p判断并初始化指针p *** 作
} else {
    ...          //如果p来自getPtr(),执行本分支
}
...
free(p);
p = NULL;
...
4.2  循环语句

【原则】4.2.1  循环必须安全退出。

说明:在应用程序中,一个重复提供服务的逻辑循环应当设计退出机制,并且将资源正确释放后安全退出。退出条件的设计,除了让程序逻辑更加完整,也能通过实现优雅退出的代码,显式释放服务循环中分配的资源,避免资源内存泄漏。

错误示例:以下代码,在一个大循环内,ReceiveMsg函数内申请资源,接收外部数据。ParseMsg函数内处理数据,但没有退出条件,会导致循环前申请的资源无法释放,该程序没有安全退出。

void DoService()
{
    ...
    size_t size = 0;
    unsigned char *pMsg = NULL;
    
    CreateServiceResource(); // 分配服务资源
    while (true) {
        pMsg = ReceiveMsg(&size);
        if (pMsg != NULL) {
            ParseMsg(pMsg, size);
            FreeMsg(pMsg);
        }
        size = 0;
    }
}

正确写法:重新设计函数,通过提供服务退出条件,并在资源释放函数ReleaseServiceResource内释放服务循环前申请的资源。

bool ParseMsg(unsigned char *msg, size_t msgLen)
{
    ...
    if (msg->type == EXIT_MESSAGE_TYPE) {
        return false;
    } else {
        return true;
    }
}

void DoService()
{
    ...
    size_t size = 0;
    unsigned char *pMsg = NULL;
    bool doRunServiceFlag = true; // 服务退出条件
    
    CreateServiceResource(); // 分配服务资源
    while (doRunServiceFlag) {
        pMsg = ReceiveMsg(&size);
        if (pMsg != NULL) {
            doRunServiceFlag = ParseMsg(pMsg, size);
            FreeMsg(pMsg);
        }
        size = 0;
    }
    ReleaseServiceResource(); // 释放服务资源
}

【例外】

1、 *** 作系统软件的 IDLE 线程,可能需要无线循环。

2、 *** 作系统在不可恢复的错误中,为避免更多的错误发生,进入指令无限循环。

void FaultReboot()
{
    ...
    reboot(rebootCmd);
    while (1) {
        CoreWait();
    }
}

3、嵌入式设备的 *** 作系统或主线程,可能使用无限循环。例如:

void OsTask()
{
    ...
    while (1) {
        msgHdl = Receive(OS_WAIT_FOREVER, &msgId, &senderPid);
        if (msgHdl == 0) {
            continue;
        }
        switch (msgId) {
            ...
        }
        Free(msgHdl);
        msgHdl = NULL;
    }
}

【原则】4.2.2  禁止使用浮点数作为循环计数器

说明:二进制浮点数算术标准 ISO/IEEE Std 754-1985 中规定了32位单精度和64位双精度浮点类型的表示方法。因为存储二进制浮点的 bit 位是有限的,所以二进制浮点数的表示范围也是有限的,并且无法精确地表示所有实数。因此,浮点数计算结果也不是精确值,不能将浮点变量用作循环计数器。

反例】如下代码示例中,使用浮点变量用作循环计数器,在不同实现下循环次数不同,循环可能执行9次也可能执行10次:

float x = 0.1f;
while (x <= 1.0f) {
    // 可能执行9或10次
    x += 0.1f;
}

for(x = 0.1f; x <= 1.0f; x += 0.1f) {
    //可能执行9或10次
}

正例】如下代码示例中,循环计数器有浮点数修改为整数:

size_t count = 1;
while(count <= 10) {
    float x = count / 10.0f;
    // 执行10次
    count++;
}

for (count = 1; count <= 10; count++) {
    float x = count / 10.0f;
    // 执行10次
}
4.3  goto语句

【建议】4.3.1  慎用goto语句

【说明】goto语句会破坏程序的结构性,所以除非确实需要,最好不使用goto语句。使用时,也只允许跳转到本函数内goto语句之后的标签。

goto语句通常用来实现函数单点返回。

同一个函数体内部存在大量相同的逻辑但又不方便封装成函数的情况下,例如反复执行文件 *** 作,对文件 *** 作失败以后的处理部分代码(例如关闭文件描述符,释放动态申请的内存等),一般会放在该函数体的最后部分,在需要的地方就goto到那里,这样代码反而变得清晰简介。

实际也可以将失败处理的代码封装成函数或者封装成宏,但是这么做会让代码变得没那么直接明了。

正例

// 符合:使用 goto 实现单点返回
int SomeInitFunc(void)
{
    void *p1 = NULL;
    void *p2 = NULL;
    void *p3 = NULL;
    p1 = malloc(MEM_LEN);
    if (p1 == NULL) {
        goto EXIT;
    }
    p2 = malloc(MEM_LEN);
    if (p2 == NULL) {
        goto EXIT;
    }
    p3 = malloc(MEM_LEN);
    if (p3 == NULL) {
        goto EXIT;
    }
    DoSomething(p1, p2, p3);
    return 0;
EXIT:  // 符合
    if (p1 != NULL) {
        free(p1);
    }
    if (p2 != NULL) {
        free(p2);
    }
    if (p3 != NULL) {
        free(p3);
    }
    return -1;
}

【原则】4.3.2  goto语句只能向下跳转

【说明】goto语句会破坏程序的结构性,所以除非确实需要,最后不使用goto语句。使用时,也只允许跳转到本函数内goto语句之后的语句。

不加限制地使用goto语句,特别是使用往回跳的goto语句,会增加代码的复杂性,使程序结构难以理解,在这种情形下,应尽量避免使用goto语句。

反例

void Func(void)
{
    int loopCnt = 0;
LOOP1:
    loopCnt++;
    if (loopCnt < MAX_COUNT) {
        goto LOOP1;  // 不符合:不能往回跳转
    } else {
        ...
    }
    ...
}

正例

void Func(void)
{
    int loopCnt = 0;
    while (loopCnt++) {
        if (loopCnt == MAX_COUNT) {
            goto LOOP2;  // 符合:只能向下跳转
        }
        ...
    }
    ...
LOOP2:
    ...
}
4.4  switch语句

【原则】4.4.1  switch语句要有default分支

【说明】大部分情况下,switch语句中要有default分支,保证在遗漏case标签处理时能够有一个缺省的处理行为。一般要求统一将default分支放在switch语句块的最后位置。

【例外】如果switch条件变量是枚举类型,并且case分支覆盖了所有取值,则可以不要求有default分支。

正例

typedef enum {
    RED,
    GREEN,
    BLUE
}Color;

Color color;

// 因为switch条件变量是枚举值,这里可以不用加default处理分支
switch  (color) {
    case RED:
        DoRedThing();
        break;
    case GREEN:
        DoGreenThing();
        break;
    case BLUE:
        DoBlueThing();
        break;
}

现在编译器都具备是否在switch语句中遗漏了某些枚举值的case分支的能力,会有相应的warning提示。

【原则】4.4.2  switch语句中至少有两个case条件分支

【说明】单个路径的选择语句更适合用 if 语句进行判断;且如果条件分支是布尔值,那么也不适合使用switch语句,使用 if 语句更合适。

反例】如下的代码示例写法都是错误的。

void Foo1(void)
{
    ...
    switch (color) {
        default: // 不符合:switch是多余的
            x = 0;
            break;
    }
}

void Foo2(void)
{
    ...
    switch (color) {
        case RED:
            DoRedThing();
            break;
        default: // 不符合:switch是多余的
            x = 0;
            break;
    }
}

正例】如下的代码示例写法是正确的。

void Foo(void)
{
    ...
    switch (lightColor) {
        case RED:
            lastSeconds = 30; // 红灯保持时间
            break;
        case GREEN:
            lastSeconds = 45; // 绿灯保持时间
            break;
        default:  // 符合:存在两个case分支
            lastSeconds = 3;  // 除了红绿灯,其他灯(如黄灯)保持3秒
            break;
    }
}
五、声明与初始化

【原则-1】不要声明或定义保留的标识符

【说明】如果声明或者定义了一个保留的标识符,那么程序的行为是未定义的。

C11 标准 7.1.3 说明:

1)双下划线开头,或下划线加一个大写字母开头的所有标识符始终保留使用;

2)除 label 名字和结构体、共用体的成员外,其他以下划线开头的标识符都在文件范围内有效;

3)C标准库中已经定义的标识符均保留其用途;

4)errno 和其他C标准库的外部链接标识符始终作为外部链接;

5)C标准库中具有文件范围的标识符都保留用作宏的名字。

反例

#undef __LINE__              // 不符合:双下划线开头
#define _MODULE_INCLUDE_     // 不符合:下划线开头
int errno;                   // 不符合:errno是C标准库中的保留标识符
void *malloc(size_t nbytes); // 不符合:malloc是C标准库中的保留标识符
#define SIZE_MAX 80          //不符合:SIZE_MAX是C标准库中的保留宏定义

【例外】作为编译器供应商或标准库程序员,可以使用为编译器保留的标识符。保留的标识符可以由编译器在标准库标头,或标准库标头包含的标头中定义,如在以下示例中来自 glibc 库实现的声明:

#define __need_site_t
#include 
// Generate a unique file name (and possibly open it).
extern int __path_search(char *__tmpl, size_t __tmpl_len, const char *__dir,
                        const char *__pfx, int __try_tempdir);
参考

《华为C语言编程规范(2826-2011.5).pdf》

《高质量C/C++编程指南(林锐-2001).pdf》

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/874674.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-13
下一篇 2022-05-13

发表评论

登录后才能评论

评论列表(0条)

保存