【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条目整理文档,百度云盘NaiveSystems Analyze 2022.1 Tech Spec CN 提取码:fau8

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.mzph.cn/news/166162.shtml

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

相关文章

聚类笔记/sklearn笔记:Affinity Propagation亲和力传播

1 算法原理 1.1 基本思想 将全部数据点都当作潜在的聚类中心(称之为 exemplar )然后数据点两两之间连线构成一个网络( 相似度矩阵 )再通过网络中各条边的消息( responsibility 和 availability )传递计算出各样本的聚类中心。 1.2 主要概念 Examplar聚类中心similarity S(i…

【ARM CoreLink 系列 3.2 -- CCI-400,CCI-500, CCI-550 差异】

文章目录 CCI-400 和 CCI-500 差异ARM CCI-400ARM CCI-500ARM CCI-550CCI-400 和 CCI-500 差异 ARM的 CCI(Cache Coherent Interconnect)系列产品是用于多核处理器之间的高性能缓存一致性互连。CCI-400 和 CCI-500 是该系列中的两种设计,它们旨在允许多个处理器核心和其他资…

TopNet-(CVPR2023)前背景图像合成

文章目录 摘要引言算法架构结构损失函数 实验数据集评估SOTA比较模型是否过拟合到修复区域泛化到真实图片消融实验 讨论及结论限制 参考文献 摘要 作者调研自动放置目标到背景进行图像合成的问题。提供背景图、分割的目标&#xff0c;训练模型预测合理放置信息&#xff08;位置…

程序员接单,宝藏好平台抄底攻略清单!五大平台精选。

前阵子“双十一”购物节狂欢促销&#xff0c;各种好货清单席卷而来。 程序员购不购物我不知道&#xff0c;但是这个兼职、接单清单相信你一定用得着。 搜罗海量信息&#xff0c;整理大量数据与评价&#xff0c;挖出了5个宝藏平台&#xff0c;绝对个个精选&#xff0c;保证量大…

图片转换成pdf格式的软件ABBYY16

ABBYY PDF这款提供多种图像处理选项&#xff0c;可提高源图像的质量&#xff0c;便于准确地识别光学字符。我们扫描纸质文档或从图像文件创建 PDF 时&#xff0c;务必选择合适的图像处理选项。而在ABBYY PDF 中包含下列图像处理选项。 识别文本 — 选择此选项会将文本层放在图…

(保姆级教程)Mysql中索引、触发器、存储过程、存储函数的概念、作用,以及如何使用索引、存储过程,代码操作演示

讲解 MySQL 中索引、触发器、存储过程、存储函数的使用 文章目录 1. 索引1.1 索引的分类1.2 索引的设计原则1.3 如何使用&#xff08;create index&#xff09; 2. 触发器2.1 触发器的分类2.2 如何使用&#xff08;create trigger&#xff09; 3. 存储过程3.1 如何使用&#xf…

新生儿散光:原因、科普和注意事项

引言&#xff1a; 散光是一种常见的眼睛问题&#xff0c;虽然在新生儿时期相对较少见&#xff0c;但了解其原因、科普相关知识&#xff0c;并提供一些建议的注意事项&#xff0c;对于婴儿的视力健康至关重要。本文将深入探讨新生儿散光的原因、相关科普知识&#xff0c;并为父…

大厂前沿技术导航

百度Geek说 - 知乎 腾讯技术 - 知乎 美团技术团队

YaRN方法:无需微调,高效扩展语言模型上下文窗口/蚂蚁集团与浙大发布原生安全框架v1.0,引领企业网络安全新时代 |魔法半周报

我有魔法✨为你劈开信息大海❗ 高效获取AIGC的热门事件&#x1f525;&#xff0c;更新AIGC的最新动态&#xff0c;生成相应的魔法简报&#xff0c;节省阅读时间&#x1f47b; &#x1f525;资讯预览 YaRN方法&#xff1a;无需微调&#xff0c;高效扩展语言模型上下文窗口 蚂蚁…

2023 hnust 湖南科技大学 信息安全管理课程 期中考试 复习资料

前言 ※老师没画重点的补充内容★往年试卷中多次出现或老师提过的&#xff0c;很可能考该笔记是奔着及格线去的&#xff0c;不是奔着90由于没有听过课&#xff0c;部分知识点不一定全&#xff0c;答案不一定完全正确 题型 试卷有很多题是原题 判断题&#xff08;PPT&#xff…

python-冒泡排序

冒泡排序 &#xff08;稳定&#xff09; O(n^2) (稳定&#xff1a;表示相等的数&#xff0c;相对位置会不会改变) 冒泡排序&#xff08;Bubble Sort&#xff09;是一种简单的排序算法&#xff0c;它通过多次遍历待排序的元素&#xff0c;比较相邻两个元素的大小并交换它们&…

Kafka 常用功能总结(不断更新中....)

kafka 用途 业务中我们经常用来两个方面 1.发送消息 2.发送日志记录 kafka 结构组成 broker&#xff1a;可以理解成一个单独的服务器&#xff0c;所有的东西都归属到broker中 partation&#xff1a;为了增加并发度而做的拆分&#xff0c;相当于把broker拆分成不同的小块&…

党建信息管理系统源码 支持在线交党费 附带完整的搭建教程

传统的党建管理模式通常采用手工方式&#xff0c;不仅效率低下&#xff0c;而且容易出错。随着组织规模的扩大和党员数量的增加&#xff0c;这种管理方式已经无法满足现实需求。此外&#xff0c;传统的党建管理模式缺乏在线交党费功能&#xff0c;给党员带来不便。因此&#xf…

Kubernetes 离线部署 Spinnaker

离线部署 Spinnaker 离线部署 spinnaker 需要提前准备以下依赖项 halyard 安装工具&#xff1a;该hal命令的apt源地址https://us-apt.pkg.dev/projects/spinnaker-community位于国外halyard boms物料清单&#xff1a;Spinnaker 将其halyard boms配置存储在公共谷歌云存储 ( g…

如何在 Web 应用程序中查找端点?

如何在 Web 应用程序中查找端点? 这篇文章主要讲述了如何在网络应用中找到端点。以下是文章的主要要点: 端点是网络服务的访问地址,通过引用这个URL,客户可以访问服务提供的操作。端点提供了寻址Web服务端点所需的信息。 HTTP消息是服务器和客户端之间交换数据的方式,包…

2024免费MacBook清理工具CleanMyMac X4.15

CleanMyMac X 是一款专业的Mac清理软件&#xff0c;可智能清理mac磁盘垃圾和多余语言安装包&#xff0c;快速释放电脑内存&#xff0c;轻松管理和升级 Mac 上的应用。同时 CleanMyMac X 可以强力卸载恶意软件&#xff0c;修复系统漏洞&#xff0c;一键扫描和优化 Mac 系统&…

【ChatGLM3-6B】Docker下部署及微调

【ChatGLM2-6B】小白入门及Docker下部署 注意&#xff1a;Docker基于镜像中网盘上上传的有已经做好的镜像&#xff0c;想要便捷使用的可以直接从Docker基于镜像安装看Docker从0安装前提下载启动访问 Docker基于镜像安装容器打包操作&#xff08;生成镜像时使用的命令&#xff0…

什么手机30万?VERTU唐卡手机顶配56.8万

近日,一则新闻在社交媒体上引发了广泛关注。一名男子遗失了一部价值30万的VERTU唐卡定制款手机,而一位女士在捡到这部手机后,误以为是一部普通的老年机,引发了种种误会。30万的手机是什么牌子?VERTU唐卡手机浮出水面 据了解,这部VERTU唐卡定制款手机是一款豪华的奢侈品定制手机…

GoogleNet详解

一、亮点 AlexNet、VGG都只有一个输出层。googlenet有三个&#xff08;其中两个是辅助分类层&#xff09; 二、先看看Inception结构 1、Inception 之前的网络&#xff1a; AlexNet、VGG都是串行结构 Inception&#xff1a; 并行结构 上一层的输出同时传入四个并行结构&…

有了倾斜摄影,如何搭建一座智慧城市?

随着无人机航测、倾斜摄影等全新一代测绘信息技术方法的发展&#xff0c;可以迅速搜集制作精细化的城市三维模型&#xff0c;搭建城市地理信息基础服务架构。 近期都在重点关注的“智慧城市”究竟是什么&#xff0c;有什么重大作用&#xff0c;同时又面临着什么难关&#xff0c…