【MISRA-C 2012】浓缩版解读

文章目录

  • 1、前言
  • 2、简介
    • 2.1、如何看待MISRA-C 2012
    • 2.2、准则(guidelines)里面的指示(Directive)和规则(Rule)
    • 2.3、准则(guidelines)的级别(Category)
  • 3、若干重要的Directive和Rule
    • 3.1、指示(Directive)
      • Dir 2.1(必要) 所有的源文件编译过程不得有编译错误
      • Dir 4.4(建议)不应该注释掉代码
      • Dir 4.11(必要)检查传递给库函数参数值的有效性
    • 3.2、规则(Rule)
      • Rule 2.1(必要)不能含有不可达代码
      • Rule 2.2(必要)不能含有死代码
      • Rule 2.3~2.7(建议)不应该含有未使用的类型声明、标签、宏、形参
      • Rule 5.1~5.9(必要)宏、类型定义/声明、函数定义/声明、变量定义/声明都不得重名
      • Rule 5.3(必要)内部作用域声明的标识符不得隐藏外部作用域声明的标识符
      • Rule 7.2(必要)无符号整形常量都是用"u"或者"U"后缀
      • Rule 7.3(必要)小写字母"l"不能作为常量后缀
      • Rule 7.4(必要)不能把字符串常量赋值给对象,除非对象类型为const char*
      • Rule 8.4(必要)具备外部链接的标识符必须有显式可见的声明
      • Rule 8.5(必要)具备外部链接的标识符只能在一个文件声明一次
      • Rule 8.8(必要)具备内部链接的变量和函数都必须使用static修饰
      • Rule 8.11(建议)具备外部链接的数组声明,应该显式指定长度
      • Rule 9.1(强制)具备自动储存周期的对象,在设定它的值之前不能读取
      • Rule 10.3(必要)表达式的值不得赋给更窄的基本类型,也不得赋给不同的基本类型类别
      • Rule 11.9(必要)宏NULL必须为整数类型空指针常量的唯⼀允许形式
      • Rule 12.1(建议)应明确表达式中操作数的优先级
      • Rule 12.3(建议)不得使用逗号表达式
      • Rule 12.4(建议)对常量表达式进⾏求值不应导致整数回绕
      • Rule 15.4(建议)对于任何迭代语句,最多只应使⽤⼀个break或goto语句进⾏终⽌
      • Rule 15.5(建议)函数结尾应只有⼀个退出点
      • Rule 15.7(必要)所有if … else if构造都必须以⼀个else语句终⽌
      • Rule 16.4(必要)每个switch语句都必须有default标记
      • Rule 17.2(必要)不得使用递归调用
      • Rule 17.8(建议)不应该修改函数的形参
      • Rule 21.3~21.11(必要)不得使用下面标准库中的接口


  推荐的个人网站MISRA-C中文翻译,但是它少了Directives部分,Rule部分是比较详细的;
  推荐的MISRA-C条目整理文档:待补充

1、前言

  首先,MISRA-C是一个C语言标准,是一个文档,是收费的,所以网上的解读以及翻译都是有人在官网买过,然后根据官网的文档而来;

  其次,为什么要搞浓缩版,因为MISRA-C实在太长了,而且很多东西我们极其少用到,比如初始化数组:

uint8_t array[2] = {[0] = 1, [1] = 2}

  或者已经是默认行为如禁止使用goto等,所以小白在这边精炼一些对实际项目和工作有帮助的内容进行展开;

2、简介

2.1、如何看待MISRA-C 2012

  MISRA-C 2012是属于第三版的MISRA-C,一开始是为了汽车行业而专门定的C语言编程规范(不包含代码风格),说白了就是以前从事汽车行业开发的大佬,在积累N年的C语言编程经验后得出的宝典,宝典中沉淀着他们实际项目中遇到的奇葩问题的预防措施,就是只要你按照宝典的要求去写代码,那么就会减少很多奇奇怪怪的问题,把这些问题扼杀在摇篮当中,而不是等问题出现后再这样去写代码

  其实每一个嵌入式公司基本都会有内部的编程规范,这些规范更多是软件总监等级别的人编写的,像华为的编程规范,阿里巴巴的编程规范,但是这些商业的编程规范都比较倾向于他们所属的业务,具有领域属性,很多规定都是为业务而生,所以不必诧异为什么里面会规定这样的内容,因为每一条内容的背后都会有血的教训。但是MISRA-C在第二版之后就不具备领域属性了,而是面向所有领域,跨行业的应用,因此现在很多公司的C语言编程规范,多多少少都带着MISRA-C的内容在里面,因为MISRA-C道出了C语言编程本身的各种问题,而不是某个领域的问题;

  所以你可以当MISRA-C是一个字典工具,偶尔看一看,或者编程的时候突然想起有相关的就查一下,不必一直啃食,因为很多内容如果你没经历过,大概就是从左耳进,右耳就出去了,当你先过一遍,脑子里大概知道有提及哪些内容,然后实际真的遇到后,你自然会回来查阅的;

2.2、准则(guidelines)里面的指示(Directive)和规则(Rule)

  话不多说,MISRA-C原文档的各种总览、背景、工具等我都忽略过,直接进入主题,MISRA-C既然是一个编程规范,里面肯定是各种条例规定,理所当然最重要的内容叫准则(guidelines),可以理解为最顶层的规范,它由2部分组成:

  1. Directive翻译为指示,引导你需要做这个事情,但是这个事情没有判断对错的标准。实际项目是否符合规范是需要借助其它条件来证实的,单纯依赖MISAR-C是不能证实的。比如Dir 4.3提及汇编语言必须被封装并隔离,但是封装和隔离都是每一个项目根据实际情况来进行的,接口不统一、做法不统一,统一的只有这个思维,这就是Directive;
    在这里插入图片描述

  2. Rule翻译为规则,明确代码就是要这样做,否则就违反标准。它不需要根据其它情况可以进行判定,比如Rule 15.1 说明不应该使用goto语句,你用了而且没有其它预防措施就是不符合MISRA-C规范,没有用或者用了但是有各种预防措施就符合这一条的规范,这就是Rule;
    在这里插入图片描述

2.3、准则(guidelines)的级别(Category)

  其中每一条Dir或者Rule都有3种类型的级别:
    mandatory(强制):不允许违反;
    required(必要):只有在明确限制、要求和预防措施的情况下才可以违反;
    advisory(建议):在合理可行的范围内建议遵守;
  这3个级别是相同重要的,差异点在于是否允许偏离标准,偏离标准的例子如下,为了某些特殊情况偏离标准,比如将 int 类型值强制转为指针来实现访问内存地址空间映射的 I/O,需要专门的文档记录这种违反MISRA-C的地方:

// 内存中的0x0002地址内数据映射了某一I/O端口数据
#define PORT (*(volatile unsigned char *)0x0002)
// 修改该位置数据就相当于修改了该I/O端口数据
PORT = 0x10u;

3、若干重要的Directive和Rule

  Directive和Rule加起来会有170+条,每一条在官网文档种都有描述,但是我们这里只选择若干重点的内容来展开,这些都是养成良好编程习惯的重要规范,对工作和实际项目有好处无坏处;

3.1、指示(Directive)

Dir 2.1(必要) 所有的源文件编译过程不得有编译错误

小白理解:每一次编译成功后,都需要保持项目的 0 warning,0 error

  很多人会容忍warning的存在,但这是不对的,可能项目存在定义但未使用的代码然后触发warning,虽然无关紧要,但是太多的时候会把真正的warning掩盖掉,比如if (x = y)这种写法会触发warning,如果你的项目本来就有几十个warning的时候,那么多这1个也很难去看到,于是你就错过了编译器帮你排查BUG的机会,当出现问题的时候需要花费额外的时间去排查。因此永远遵守0 warning,0 error能让编译器帮你识别很多问题;

Dir 4.4(建议)不应该注释掉代码

小白理解:注释代码容易和真正的注释产生混淆,如果真的想暂时屏蔽一段代码,应该要明确标注出起点和终点,使用#if或者#ifdef起点然后#endif重点,但是我觉得最好的做法是删掉他们,利用git等代码管理工具来保存记录

  很多人在实际项目中暂时取消某个功能就是使用注释,因为注释一遍都是有快捷键的,很方便就能选中代码然后注释掉,但是由于真正的注释和这种行为的注释用法是一样的,会让其他人看这个代码产生混淆,它到底有没有作用,如果真的存在一个能符合语法的注释它真的是一个注释,而且混淆在这些屏蔽代码里面,那么程序员将很难分清代码行为。

void FuncA(void)
{uint8_t Cnt = 0;//符合Dir 4.4写法
#if 0Cnt++;
#endif//不符合Dir 4.4写法
//	Cnt++;
}

Dir 4.11(必要)检查传递给库函数参数值的有效性

小白理解:在调用库函数之前,必须先检查参数是否有效,再进行调用,而不是相信库函数会自己检查,原则上就是不相信其它库的接口在遇到非法数据时行为是正常的,都要假设是异常的。

  其实这种思维在Linux早已根深蒂固,Linux内核的设计思维之一就是不相信用户会正确使用内核函数,因此给用户加了各种权限,让用户需要通过各种验证后才能使用接口,所有我们在调用别人的库之前就需要检查入口,同时编写接口的时候也要做到入口参数的检查。

/* A.h */
extern void FuncA(uint8_t* pMsg);
extern uint8_t* GetMsgPointer(void);/* B.c */
void FuncB(void)
{uint8_t* pM = GetMsgPointer();//符合Dir 4.11写法if (NULL != pM) {FuncA(pM);}//不符合Dir 4.11写法FuncA(pM);
}

3.2、规则(Rule)

Rule 2.1(必要)不能含有不可达代码

小白理解:原因是1这些代码占用Ram和Flash而且毫无意义纯属浪费,2是可能导致编译器产生一些又臭又长的跳转指令但实际并不需要,3是可能导致整个循环变慢。

  很多不可达的代码都有warning,请遵守Dir 2.1保持0 warning,0 error。另外就是有比较难检查到的不可达代码,就比较依赖静态检查工具来进行了,但是一般遵守if-else或者switch-case的时候不要提前return,而且确保判断的值范围都在内部一般不会有什么大问题。

#include "stdafx.h"
typedef enum ErrStatus {Success = 0,Err_1,Err_2
} ErrStatus;ErrStatus f(int x) {if (x < 0) {return Err_1;}else{return Success;}
}int main()
{ErrStatus x = f(5);switch (x){case Err_1:printf("err 1"); break;case Err_2:printf("err 2 "); break;  /* unreachable code */default:printf("Success"); break;}
}

Rule 2.2(必要)不能含有死代码

小白理解:把调试代码和取消的功能删干净再上传业务代码。

  意在提醒实际项目中调试或者删除功能时,要把代码弄干净,写了一个debug接口,结束后忘记删除然后停留在业务上面,如果不调试它是没有任何作用的,占用Ram和Flash纯属浪费,还容易引起胡混淆;

extern volatile uint16_t v;
extern char *p;
void f(void)
{uint16_t x;(void)v;     /* Compliant - 这种方式用于抑制编译器的未使用告警,是有意义的,如果删除就会产生编译器告警,不视为dead code */(int32_t) v; /* Non-compliant - the cast operator is dead */v >> 3;      /* Non-compliant - the >> operator is dead */x = 3;       /* Non-compliant - the = operator is dead* - x is not subsequently read */*p++;        /* Non-compliant - result of * operator is not used */(*p)++;      /* Compliant - *p is incremented */
}

Rule 2.3~2.7(建议)不应该含有未使用的类型声明、标签、宏、形参

小白理解:我觉得这一堆跟Rule 2.2是一样的含义,没用的代码弄干净点,我推荐直接删掉,连#if和#ifdef都不要用,除非是必须的调试代码。

Rule 5.1~5.9(必要)宏、类型定义/声明、函数定义/声明、变量定义/声明都不得重名

小白理解:在 C99 中规定外部链接标识符的有效识别长度为 31 个字符(是否大小写敏感取决于编译器),也就是前 31 个字符需要唯一,才能区分两个外部链接标识符表示不同的项。

我建议每一个对象的命名,都跟随其所属模块作为前缀,这样重名的概率低很多,比如EEPROM模块,那么里面的函数、变量、struct、enum等都是EEPROM_作为前缀,这样即使另一个模块比如Timer也有一个名字教Data的,那么EEPROM_Data就和Timer_Data不相同,另外是命名不要过长,模块_动作_名称,控制在31字符内;

/*      1234567890123456789012345678901********* Characters */
int32_t engine_exhaust_gas_temperature_raw;
int32_t engine_exhaust_gas_temperature_scaled; /* Non-compliant,两个变量名的前31个字符相同 */
/*      1234567890123456789012345678901********* Characters */
int32_t engine_exhaust_gas_temp_raw;
int32_t engine_exhaust_gas_temp_scaled; /* Compliant */

Rule 5.3(必要)内部作用域声明的标识符不得隐藏外部作用域声明的标识符

小白理解:函数内部的局部变量不要和全局变量重名

  特别是循环计数变量,习惯用i,j,k没问题,但是一个函数内部每一个循环的计数名字都要不一样,可以有i,j,k,m,n,z,y……都行,最好就是明确当前循环计数的含义,比如当前循环是找A的,计数值变量就叫A_i或者其它具体含义,下一个循环是找B的就叫B_i或者其它具体含义,只有一个循环直接叫i或者其它具体含义;

void fn1(void)
{int16_t i; /* Declare an object "i" */{int16_t i; /* Non-compliant - hides previous "i" ,第三种情况连续嵌套块*/i = 3;     /* Could be confusing as to which "i" this refers */}
}
struct astruct
{int16_t m;
};
extern void g(struct astruct *p);
int16_t xyz = 0;             /* Declare an object "xyz" */
void fn2(struct astruct xyz) /* Non-compliant - outer "xyz" is* now hidden by parameter name */
{g(&xyz);
}
uint16_t speed;
void fn3(void)
{typedef float32_t speed; /* Non-compliant - type hides object */
}

Rule 7.2(必要)无符号整形常量都是用"u"或者"U"后缀

小白理解:不要相信编译器可以很好帮你处理常量类型

  当你的的写法不一样的时候,4095是uint16_t或者uint32_t或者sint32_t都是不知道的,但是你只要写成4095u,那他无论怎么都是无符号型,而且必须是uint16_t起步,这样子就算和有符号的进行计算,也不会认为最高位是符号位了;
  例如,整数常量 40000 在 32 位环境中属于带符号 int 类型,但在 16 位环境中属于 signed long 类型。数值 0x8000 在 16 位环境中属于 unsigned int 类型,但在 32 位环境中属于 signed int 类型。在 2-bit int 和 64-bit long 环境中:

void R_7_2(void)
{use_int32(2147483647);   /* int constant */use_int32(0x7FFFFFFF);   /* int constant */use_int64(2147483648);   /* long constant */use_uint32(2147483648U); /* unsigned int constant */use_uint32(0x80000000);  /* unsigned int constant -  Non-compliant */use_uint32(0x80000000U); /* unsigned int constant */
}

Rule 7.3(必要)小写字母"l"不能作为常量后缀

小白理解:纯属和数字1长得很像,肉眼难以区分,干脆不用

const int64_t a = 0L;
const int64_t b = 0l;        /* Non-compliant */
const uint64_t c = 0Lu;
const uint64_t d = 0lU;      /* Non-compliant */
const uint128_t e = 0ULL;
const uint128_t f = 0Ull;     /* Non-compliant */
const int128_t g = 0LL;
const int128_t h = 0ll;      /* Non-compliant */
const float128_t m = 1.2L;
const float128_t n = 2.4l;   /* Non-compliant */

Rule 7.4(必要)不能把字符串常量赋值给对象,除非对象类型为const char*

小白理解:道理很简单大家都懂,字符串常量不可被改变,但是写接口的时候很多会忽略,只能说带来隐患,写上就100%没问题

extern void f1(char *s1);extern void f2(const char *s2);static void g2(void)
{f1("string"); /* Non-compliant,形参为非const,实参是字符串常量 */f2("string"); /* Compliant     */
}static char *name1(void)
{return ("MISRA"); /* Non-compliant,返回参数类型非const */
}static const char *name2(void)
{return ("MISRA"); /* Compliant*/
}void R_7_4(void)
{char *s = "string"; /* Non-compliant */const volatile char *p = "string"; /* Compliant     */"0123456789"[0] = '*'; /* Non-compliant,未定义行为 */g2();(void)name1();(void)name2();use_const_char_ptr(s);use_const_volatile_char_ptr(p);
}

Rule 8.4(必要)具备外部链接的标识符必须有显式可见的声明

小白理解:说白了就是调用文件以外的变量、函数、宏等都需要有显式的声明,而且变量和函数都必须带extern

extern void func1(void);
extern void func2(int16_t x, int16_t y);
extern void func3(int16_t x, int16_t y);
void func1(void)
{/* Compliant */
}
void func2(int16_t x, int16_t y)
{/* Compliant */
}
void func3(int16_t x, uint16_t y)
{/* Non-compliant - parameter types different,违反规则8.3 */
}
void func4(void)
{/* Non-compliant - no declaration of func4 before this definition */
}
static void func5(void)
{/* Compliant - rule does not apply to objects/functions with internal* linkage */
}

Rule 8.5(必要)具备外部链接的标识符只能在一个文件声明一次

小白理解:声明都放在.h里面,不要越界操作

  这个是和Rule 8.4有关系的,Rule 8.4是说外部链接的标识需要有声明,这里则说只能声明一次,稍微转化一下就是,规定外部链接的标识符只能在.h里面声明且只有1次声明,换言之,不要在.c文件里面用extern把其它文件的变量或者函数给包含进来,这种属于不合规的做法,正常使用你只能#include一个.h头文件进来,如果只是临时调试使用,调试结束后记得删干净。

/* featureX.h */
extern int16_t a; /* Declare a *//* file.c */
#include "featureX.h"int16_t a = 0; /* Define a */

Rule 8.8(必要)具备内部链接的变量和函数都必须使用static修饰

小白理解:不给别人用的变量和函数,都加上static修饰

  1来可以防止重名问题,2来可以清晰阅读一个.c里面哪些只限于内部使用,3来添加权限属性,别人在.c里面强制extern你的static变量或者函数是会编译失败的,他必须再去你文件里面删掉static,也就是让越界行为变得复杂,稍微再防止一下

static int32_t x = 0;   /* definition: internal linkage */
extern int32_t x;       /* Non-compliant,先前已存在x的声明,* 导致这个x的链接性就是内部的,* 而不是我们平常认为的用extern修饰的是外部链接 */
static int32_t f(void); /* declaration: internal linkage */
int32_t f(void)         /* Non-compliant */
{return 1;
}
static int32_t g(void); /* declaration: internal linkage */
extern int32_t g(void)  /* Non-compliant */
{return 1;
}

Rule 8.11(建议)具备外部链接的数组声明,应该显式指定长度

小白理解:不要用extern uint8_t array[],一定要用#define LENGTH_MAX 10 extern uint8_t array[LENGTH_MAX]

  没什么好说的,给被人用的数组一定要让别人有方法知道长度,否则就存在越界的隐患,你可以用函数返回,可以用全局变量,可以用宏,可以用枚举等等,一定要用其中一个

extern int32_t array1[10]; /* Compliant */
extern int32_t array2[];   /* Non-compliant */

Rule 9.1(强制)具备自动储存周期的对象,在设定它的值之前不能读取

小白理解:局部变量定义时即初始化,不要偷懒

  这也是减少隐患的预防性编程,定义局部变量的时候顺手写一个赋值操作,这带来了确定性,天晓得下一个人会不会在局部变量还没赋值的时候使用,或者你不会,但下一个维护你代码的人会

static void f(bool_t b, uint16_t *p)
{if (b){*p = 3U;}
}static void g(void)
{uint16_t u; /* Non-compliant declaration,u未被显式赋值 */f(false, &u);if (u == 3U) /* Non-compliant use - "u" has not been assigned a value. */{use_uint16(u); /*  */}
}static void jmp_over_init(void)
{goto L1; /* violates R.15.1 */uint16_t x = 10u;
L1:// 此处的x声明虽然被跳过,但x还是被正常声明了,可编译通过x = x + 1u; /* Non-compliant - x has not been been assigned a value */use_uint16(x);
}void R_9_1(void)
{bool_t b = get_bool();uint16_t val = 3u;f(b, &val);use_uint16(val);g();jmp_over_init();
}

Rule 10.3(必要)表达式的值不得赋给更窄的基本类型,也不得赋给不同的基本类型类别

小白理解:运算类型始终保持一致性,多使用强制类型转换

  这是避免编译器的隐形转换导致数据截断或者精度问题,编译器不一定会按照你的想法进行隐式转换,自己强制转换能掌控全局

u8a = 2;      /* Compliant By 例外1 */
u8a = 2 * 24; /* Compliant By 例外1 */uint8_t u8f = 1.0f; /* Non-compliant - unsigned and floating */
bool_t bla = 0;     /* Non-compliant - boolean and signed,不符合例外1,因为bla不是无符号整型 */
cha = 7;            /* Non-compliant - character and signed */
u8a = 'a';          /* Non-compliant - unsigned and character */
u8b = 1 - 2;        /* Non-compliant - unsigned and signed,不符合例外1,因为1-2不是非负的 */
u8c += 'a';         /* Non-compliant - u8c = u8c + 'a' assigns character to unsigned */s8a = K2;    /*  Non-compliant - Constant value does not fit */
u16a = u32a; /*  Non-compliant - uint32_t to uint16_t */s8a = -123L; /*  Non-compliant - signed long to int8_t */u8a = 6L; /* Non-compliant - signed long to uint8_t,不符合例外1 *//* Standard Type has rank greater than int,* so exception does not apply *//* integer constant expression from + with value 5U and UTLR of unsigned char */
u8a = (uint16_t)2U + (uint16_t)3U; /* Compliant,例外1? *//* integer constant expression from + with value 100000U and UTLR of unsigned int */
u16a = (uint16_t)50000U + (uint16_t)50000U; /*  Non-compliant,不符合例外1,超过了u16的最大值 *//* Top-level cast returns C standard type of unsigned short */
u8a = (uint16_t)(2U + 3U); /*  Non-compliant,经过转换后不再是常量表达式,不符合例外1 */

Rule 11.9(必要)宏NULL必须为整数类型空指针常量的唯⼀允许形式

小白理解:只能使用NULL来判断空指针,不能使用0

  这个网上有很多解释了,(void*)0和0的含义是不一样的,你不能相信编译器会把他们认为是一样,所以统一使用NULL的(void*)0

#define MY_NULL_1 0
#define MY_NULL_2 (void *)0
#define MY_NULL_3 NULLextern void f9(uint8_t *p);int32_t *p1 = 0;         /* Non-compliant */
int32_t *p2 = (void *)0; /* Compliant     */
int32_t *p3 = MY_NULL_3; /* Compliant     */if (p1 == MY_NULL_1) /* Non-compliant - also breaks R.14.3 */
{
}
if (p2 == MY_NULL_2) /* Compliant - but breaks R.14.3 */
{
}f9(NULL); /* Compliant for any conforming definition of* NULL, such as:*       0*       (void *)0*       (((0)))*       (((1 - 1)))*/

Rule 12.1(建议)应明确表达式中操作数的优先级

小白理解:不要怜惜括号的使用,每一层计算都需要加

  预防性编程的一种,把结果掌控在程序员手里,而不是选择相信编译器,比如很多人喜欢这样写if ( a >= b && c <= d),这完全没问题,但是MISRA-C更加推荐你这样写,if ( (a >= b) && (c <= d)),给每一个运算都加上括号维护起来,这是100%不会出现符号优先级问题的

Rule 12.3(建议)不得使用逗号表达式

小白理解:很少人用,优先级和副作用问题多,干脆不要用

  逗号表达式最多出现在for的子句里面,比如for(i = 0, j = 0; ; i++, j++)这样子,看起来是完全没问题的如果只是如此简单,不过如果你的计算稍微复杂比如突然有一天j要从i开始,就改成了for(i = 0, j = i; ; i++, j++),请问如果你选择相信编译器,j的值是0还是没有赋值0之前的i值?还是那句话,不要依赖编译器,能自己完成的事情就不要让编译器去做,取消逗号表达式的使用

/* also violates R.14.2 */
for (i = 0, p = &a[0]; /* Non-compliant */i < N;++i, ++p) /* Non-compliant */
{
}

Rule 12.4(建议)对常量表达式进⾏求值不应导致整数回绕

小白理解:常量的加法不应该溢出,这个是很难人工检查出来的,一般依赖静态检查,或者编程的时候注重常量的加法

// 与 case 标签关联的表达式必须是常量表达式。
// 如果在 case 表达式求值期间发生无符号环绕,则很可能是无意的。
// 在具有 16 位 int 类型的计算机上会导致以下示例中的回绕:
#define BASE 65024u
switch (x)
{
case BASE + 0u:f();break;
case BASE + 1u:g();break;
case BASE + 512u: /* Non-compliant - wraps to 0 */h();break;
}

Rule 15.4(建议)对于任何迭代语句,最多只应使⽤⼀个break或goto语句进⾏终⽌

小白理解:禁止!禁止!禁止!使用goto,另外遵循单点退出原则,并且退出点必须在模块的最后一句

  单点退出原则也就是一个模块只能有一个退出点,这个的好处是约束了模块的行为是稳定的,它必然会跑到固定的退出点,而退出点固定在模块最后一句,意味着模块内每一句语句都会被执行,防止了一些锁的成对操作缺漏,或者模块的退出操作缺漏,预防性编程

#define LIMIT 100u/* Note: All uses of goto also break R.15.1 */void R_15_4(void)
{uint32_t x;uint32_t y;uint32_t z;for (x = 0; x < LIMIT; ++x){if (ExitNow(x)){break; /* compliant - single exit from outer loop */}for (y = 0; y < x; ++y){if (ExitNow(LIMIT - y)){break; /* compliant - single exit from inner loop* 这个break仅用来退出本for循环而不是上级for,* 所有和上面那个break不冲突 */}}}for (x = 0; x < LIMIT; ++x){if (BreakNow(x)){break;}else if (GotoNow(x)){goto EXIT; /* Non-compliant - break and goto in loop */}else{KeepGoing(x);}}EXIT:;while (x != 0u){if (x == 1u){break;}while (y != 0u){if (y == 1u){// 这个goto直接退了两层while,和上面的break冲突goto L1; /* Non-compliant (outer loop) Compliant (inner loop)   *//* goto causes early exit of both inner and outer loop */}}}
L1:z = x + y;
}

Rule 15.5(建议)函数结尾应只有⼀个退出点

小白理解:和Rule 15.4的一个意思,遵守单点退出原则;

static bool_t f(uint16_t n, const char *p)
{if (n > MAX){return false; /* Non-compliant */}if (p == NULL){return false; /* Non-compliant */}return true;
}

Rule 15.7(必要)所有if … else if构造都必须以⼀个else语句终⽌

小白理解:强制让你思考else的内容,也就是让你多思考一个分支情况,防止发生错漏,实际项目很多时候就是说”啊,少考虑了这种情况“

if (flag_1)
{action_f1();
}
else if (flag_2)
{action_f2();
}
/* Non-compliant */if (flag_1)
{action_f1();
}
else if (flag_2)
{action_f2();
}
else
{; /* No action required - ; is optional */
}

Rule 16.4(必要)每个switch语句都必须有default标记

小白理解:switch或许有你考虑不到的值,如果没有default,则switch会退出,你想debug的机会都难,多写一个default,里面加打印语句,100%不会错漏

switch (x)
{case 0:++x;break;case 1:case 2:break;/* Non-compliant - default label is required */
}

Rule 17.2(必要)不得使用递归调用

小白理解:嵌入式的RAM太宝贵了,就算你有1M的RAM,也经不住递归的堆栈开辟,随时栈溢出的风险,除非你有G级别的RAM,但这已经不太属于嵌入式了,不要用是100%不会出现递归导致的栈溢出问题

static uint16_t fn_a(uint16_t parama)
{uint16_t ret_val;if (parama > 0U){ret_val = parama * fn_a(parama - 1U); /* Non-compliant */}else{ret_val = parama;}return ret_val;
}

Rule 17.8(建议)不应该修改函数的形参

小白理解:参数或许需要拿来判断原始值,留住备份总是一个好习惯

int16_t glob = 0;
void proc(int16_t para)
{para = glob; /* Non-compliant */
}
void f(char *p, char *q)
{p = q;   /* Non-compliant */*p = *q; /* Compliant */
}

Rule 21.3~21.11(必要)不得使用下面标准库中的接口

小白理解:不要相信标准库能很好兼容你的芯片,直接不用是100%不出标准库问题

  stdlib.h中的动态内存接口、sethmp.h所有接口、signal.h所有接口、stdlib的标准IO接口、atof、atoi、atoll、atol、system终止函数、bsearch、qsort、time、date、tgmath.h所有接口

#include <stdlib.h>void R_21_3(void)
{char_t *p;p = (char_t *)malloc(11U); /* Non-compliant: use of malloc  */if (p != NULL){(void)realloc(p, 20U); /* Non-compliant: use of realloc */}free(p); /* Non-compliant: use of free    */p = (char_t *)calloc(10, sizeof(char_t)); /* Non-compliant: use of calloc */free(p); /* Non-compliant: use of free    */
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/212372.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【医学图像处理】超详细!PET图像批量预处理

目录 一、单个PET图像预处理1、使用[MRIConvert](https://pan.baidu.com/s/1cn3kgeVRir8HvP6HHm0M0Q?pwd5rt5)处理DCM2、MRI和PET数据预处理过程1&#xff09; 打开matlab命令行输入spm pet&#xff0c;打开SMP12&#xff0c;界面如下2&#xff09; Realign&#xff0c;只需要…

每日一练 | 华为认证真题练习Day136

1、OSI参考模型从高层到低层分别是&#xff1f; A. 应用层、会话层、表示层、传输层、网络层、数据链路层、物理层 B. 应用层、传输层、网络层、数据链路层、物理层 C. 应用层、表示层、会话层、传输层、网络层、数据链路层、物理层 D. 应用层、表示层、会话层、网络层、传…

【论文解读】在上下文中学习创建任务向量

一、简要介绍 大型语言模型&#xff08;LLMs&#xff09;中的上下文学习&#xff08;ICL&#xff09;已经成为一种强大的新的学习范式。然而&#xff0c;其潜在的机制仍未被很好地了解。特别是&#xff0c;将其映射到“标准”机器学习框架是具有挑战性的&#xff0c;在该框架中…

openGauss学习笔记-131 openGauss 数据库运维-启停openGauss

文章目录 openGauss学习笔记-131 openGauss 数据库运维-启停openGauss131.1 启动openGauss131.2 停止openGauss131.3 示例131.3.1 启动openGauss131.3.2 停止openGauss 131.4 错误排查 openGauss学习笔记-131 openGauss 数据库运维-启停openGauss 131.1 启动openGauss 以操作系…

超详细的接口测试

本文主要分为两个部分&#xff1a; 第一部分&#xff1a;主要从问题出发&#xff0c;引入接口测试的相关内容并与前端测试进行简单对比&#xff0c;总结两者之前的区别与联系。但该部分只交代了怎么做和如何做&#xff1f;并没有解释为什么要做&#xff1f; 第二部分&#xf…

leetcode 343.整数拆分 198.打家劫舍(动态规划)

OJ链接 &#xff1a;leetcode 343.整数拆分 代码&#xff1a; class Solution {public int integerBreak(int n) {int[] dp new int[n1];//每个n&#xff0c;拆分多个整数乘积的最大值dp [0] 0;dp [1] 1; for(int i 2 ; i<n; i){for(int j 0 ; j < i; j){dp[i] Ma…

Harmony Ble蓝牙App(二)连接与发现服务

Ble蓝牙App&#xff08;二&#xff09;连接与发现服务 前言正文一、BlePeripheral回调二、连接和断连三、连接状态回调四、发现服务五、服务提供者六、显示服务七、源码 前言 在上一篇中我们进行扫描设备的处理&#xff0c;本文中进行连接和发现服务的数据处理&#xff0c;运行…

【LeetCode】挑战100天 Day13(热题+面试经典150题)

【LeetCode】挑战100天 Day13&#xff08;热题面试经典150题&#xff09; 一、LeetCode介绍二、LeetCode 热题 HOT 100-152.1 题目2.2 题解 三、面试经典 150 题-153.1 题目3.2 题解 一、LeetCode介绍 LeetCode是一个在线编程网站&#xff0c;提供各种算法和数据结构的题目&…

阿里入局鸿蒙!鸿蒙原生应用再添两员新丁

今日HarmonyOS微博称&#xff0c;阿里钉钉、蚂蚁集团旗下的移动开发平台mPaaS与华为达成合作&#xff0c;宣布启动鸿蒙原生应用的开发&#xff01;相关应用将以原生方式适配#HarmonyOS NEXT#系统。 #HarmonyOS#市场或迎来爆发式增长&#xff01; 阿里钉钉 阿里钉钉与华为达成合…

【2023年第十三届APMCM亚太地区大学生数学建模竞赛】C题 中国新能源电动汽车的发展趋势 完整论文

【2023年第十三届APMCM亚太地区大学生数学建模竞赛】C题 中国新能源电动汽车的发展趋势 题目 新能源汽车是指技术原理先进、技术新、结构新&#xff0c;以非常规车用燃料为动力源(非常规车用燃料是指汽油、柴油以外的燃料)&#xff0c;在车辆动力控制和驱动方面融合先进技术的…

为什么vue中数组和对象的props默认值要写成函数形式?

多个组件数据不相互干涉 假如在一个地方引用了同一个组件&#xff0c;并给他们都绑定了单独的值。如果只声明为一个对象或数组&#xff0c;可能会导致在某一个实例中修改数据&#xff0c;影响到其他实例中的数据&#xff0c;因为数组和对象是引用类型的数据。为了在多次引用组件…

Django(十、中间件)

文章目录 一、中间件的介绍中间件有什么用中间件功能自定义中间中间件的顺序 一、中间件的介绍 中间件顾名思义&#xff0c;是介于request与response处理之间的一道处理过程&#xff0c;相对比较轻量级&#xff0c;并且在全局上改变django的输入与输出。因为改变的是全局&…