资料下载地址(包括2012英文版规范以及2004版中文规范,以及一些讲座资料等):
待上传。。。。
以下以2012版规范进行学习
总结:凡是在code中可以删除的,且不影响code正常运行的代码都被定义为unused code.
在MISRA c中规定,这部分代码需要被移除,否则QAC将会报错.
未使用的代码分有以下几种:
int test_func(void)
{
int i = 3;
return i;
i = 8; //这部分就被成为不可达code
}
所谓的死代码就是指定义的未用变量或函数
extern int value;
extern char*p //假定p在别的函数中被应用
void test_func(void)
{
int i;
i = 3;//dead code,该函数没有传参,没有返回,对于变量的操作都可视为dead code,除非外部可用变量
value >> 3;//dead code
*p++;//error, misra c规定不允许指针地址做++
(*p)++;//correct,
}
规范对于该rule的解释是:如果代码中出现的类型没有被使用,那么就会造成阅读者的误解,因为他不清楚这个类型没有被使用是因为冗余的还是本身是错误的
int test_func(void)
{
typedef int LOCAL_INT;//Non-compliant
return 64;
}
这里的标签是指tag。比如说结构体定义时的tag,一般结构体的定义有多种形式,但是misra-c在这方面更严格些。
typedef struct student
{
int key;
}stu;//这里的tag:student就属于 unused tag declaration
//misra-c中更建议下边的定义方式:
typedef struct
{
int key;
}stu;
这里的标签是指tag。比如说结构体定义时的tag,一般结构体的定义有多种形式,但是misra-c在这方面更严格些。
int test_func(void)
{
#define TEST_DATA1 3
#define TEST_DATA2 5//Non-compliant
return TEST_DATA1;
}
int test_func(void)
{
int i = 3;
label: //Non-compliant
return i;
}
int test_func(int value,
int p[]) //Non-compliant
{
value = 3;
return value;
}
一般的为了避免出现unused parameter情况,可以在函数中增加如下代码作为检测。
/* NULL_PTR define with a void pointer to zero definition*/
# ifndef NULL_PTR
# define NULL_PTR ((void *)0)
# endif
#define UNUSED_PARAMETER(VariableName) {if((VariableName) != 0U)\
{/* Do Nothing */}}
int test_func(int value,
void *p)
{
value = 3;
UNUSED_POINTER (p)
UNUSED_PARAMETER(value )
return value;
}
本节主要讲注释符的使用的,略过。
本节略过
比如'\141t' //Non-compliant
'\141\t' //compliant
再比如: "\x41g" //Non-compliant
"\x41""g" //compliant
"\x41\x67" //compliant
C99与C90关于标识符长度具体要求是不一样的,C99更宽松一些。但是为了不必要的麻烦,长度应尽量控制。ISO标准要求变量,类型,函数和标签名的长度不超过31个字符
MISRA C中关于位域的声明做如下要求:
C90:仅允许有符号或者无符号int型,其他如enum,short,char等被视为未定义
C99:1.允许有符号无符号int 2.允许其他有符号或无符号的显式整数类型
注:MISRA C中关于位域的操作,对于纯int型是不被允许的。需要显示的表明是有符号还是无符号。
typedef unsigned int UINT_16;
unsigned int b1:2; //Compliant
int b2:2; //Non-Compliant
UINT_16 b3:2; //Compliant
signed long b4:2;//Non-Compliant
如果一个位域的值位宽仅有1,那么是不允许被定义为有符号类型的,因为这样会导致负数出现,而偏离程序员本身的期望。MISRA C中规定一个单字节有符号位域包括一个符号位和0值位
注:此规则不适用于未命名的位字段,因为他们的值不能被访问。
signed int f:1; // Non-compliant; there's only room here for the sign
signed int:1; // unnamed, Compliant
unsigned int f:1; //Compliant
signed int f:2;//Compliant
MISRA C中规定,开发者使用前导为0的常量是希望它们被作为十进制来使用,而不是八进制。当然,八进制的转义序列不在此规定中。
extern uint16_t code[10];
code[1] = 109; // compliant-----decimal 109
code[2] = 100; // compliant-----decimal 100
code[3] = 052; // Non-compliant-----decimal 42
code[4] = 071; // Non-compliant-----decimal 57
MISRA C 中规定无符号常量末尾必须带u。如果不带u且环境允许的话,该常量会被当做有符号对待。比如说0x8000(32768)在16位环境下为无符号类型,在32位情况下为有符号类型。
下面的示例假设机器具有16位int 类型和32位long 类型。
常量 | 类型 | 合规 |
---|---|---|
32767 | signed int | Complicant |
0x7fff | signed int | Compliant |
32768 | signed long | Compliant |
32768u | unsigned int | Compliant |
0x8000 | unsigned int | Non-compliant |
0x8000u | unsigned int | Compliant |
MISRA C规定:在声明文字时,使用大写后缀"L"消除了”1“和"l"之间的潜在歧义。
const int64_t a = 1L; // compliant
const int64_t a = 1l; // Non-compliant
const uint64_t c = 1Lu; // compliant
const uint64_t d = 1lu; // Non-compliant
const uint64_t e = 1ULL; // compliant
const uint64_t f = 1Ull; // Non-compliant
const int128_t g = 1LL; // compliant
const int128_t h = 1ll; // Non-compliant
const float128_t m = 1.2L; // compliant
const float128_t n = 2.4l; // Non-compliant
MISRA C中描述:任何修改字符串值的操作都会导致未定义的行为。例如:当字符串值存储在只读内存中,在这种情况下,修改字符串值的操作将失败还可能导致异常或崩溃。
下边的示例显示了试图修改字符串值的操作:
"0123456789"[0] = '*'; // Non-compliant
下边的示例显示了如何防止字符串的修改:
char *s = "string";// Non-compliant
const volatile char *p = "string";// compliant
extern void func1(char *s1);
extern void func2(const char *s2);
void test(void)
{
func1("string"); // Non-compliant
func2("string"); // compliant
}
char *func3(void)
{
return ("MISRA");// Non-compliant
}
const char *func3(void)
{
return ("MISRA");// compliant
}
MISRA C中指出,在C90标准中允许在某种情况下省略类型,在这种情况下隐式指定类型为int类型。
但是不推荐这种操作,因为省略显示表达会导致一定的混淆。
示例如下:
extern void test(char c, const k);
这里k其实是const int型,但是实际的期望可能是const char类型。
关于一些合规或不合规的表达示例如下:
first sample_对象声明:
extern int16_t tmp; // compliant
extern tmp; // Non-compliant,隐式int类型
const int16_t tmp; // compliant
const tmp; // Non-compliant,隐式int类型
second sample_函数类型声明:
extern func(void);// Non-compliant,隐式int类型
extern int16_t func(void);// compliant
extern func(char c, const k);// Non-compliant,隐式int类型
extern int16_t func(char c, const int16_t k);// compliant
third sample_类型定义:
typedef (*pfi) (void); // Non-compliant,隐式int返回类型
typedef int16_t (*pfi) (void); // compliant
typedef void (*pfv) (const x); // Non-compliant,参数x为隐式int类型
typedef void (*pfv) (int16_t x); // compliant
fourth sample_结构体成员声明:
struct str
{
int16_t x; // compliant
const y; // Non-compliant,成员y为隐式int类型
}s;
MISRA C中表示,参数和形参的数量,类型与函数的预期和实际返回类型之间的不匹配可能会导致未定义的行为,该规则与规则8.1和规则8.4的目的是通过要求显式指定参数类型和函数返回类型来避免这种未定义的行为。规则17.3确保该信息在函数调用时可用,因此要求编译器诊断检测到的任何不匹配。
该规则还要求在声明中为所有参数指定名称。参数名可以提供有关函数接口的有用信息,声明和定义之间的不匹配可能表明编程错误
注:空的参数列表在原型中时无效的。如果一个函数类型没有参数,它的原型形式应使用关键字void。
下边示例展示了函数的声明以及相应的定义是否合规:
extern int16_t func1(int16_t n); // compliant
extern void func2(int16_t ); // Non-compliant,没有具体的参数名
static int16_t func3(); // Non-compliant,没有入参
static int16_t func4(void); // compliant
/* compliant */
int16_t func1(int16_t n)
{
return n;
}
/* Non-compliant */
static int16_t func3(vec, n)
int16_t *vec;
int16_t n;
{
return vec[n-1];
}
下边示例展示了函数其他应用方面是否合规:
int16_t (*pf1) ();// Non-compliant
int16_t (*pf1) (void);// compliant
typedef int16_t (*pf2_t) (int16_t);// Non-compliant
typedef int16_t (*pf3_t) (int16_t n);// compliant
声明或引用函数时,要考虑参数类型,以及参数名的一致性即函数定义及其声明的接口一致性。
示例如下:
extern void f(signed int tmp);
void f(int tmp); // compliant
extern void g(int *const);
void g(int*); // Non-compliant
extern int16_T func(int16_t num, int16_t tmp);
// Non-compliant 参数名字不匹配
int 16_t func(int16_t tmp, int16_t num)
{
return num/tmp;
}
下边的示例中即使width_t与height_h实际的基本类型是一样的,但是也不允许如下的示例操作:
typedef uint16_t width_t;
typedef uint16_t height_t;
typedef uint32_t area_t;
extern area_t area(width_t w, height_t h);
area_t area(width_t w, width_t h) // Non-compliant
{
return (area_t) w*h;
}
该规则不要求函数指针声明与函数声明使用相同的名称,因此,下面的示例是合规的:
extern void f1(int16_t x);
extern void f2(int16_t y);
void f(bool_t b)
{
void (*fp1) (int16_t z) = b?f1:f2;
}
MISRA C中指出如果一个对象或函数的声明在定义该对象或函数时可见,编译器必须检查该声明和定义是否合规。在存在函数原型的情况下,如规则8.2所要求的,检查扩展到函数参数的数量和类型。
使用外部链接实现对象和函数声明的推荐方法是在头文件中声明它们,然后将头文件包含在所有需要它们的代码文件中,包括定义它们的代码文件(参见规则8.5).
extern int16_t count;
int16_t count = 0; // compliant
extern uint16_t speed = 6000u; // Non-compliant 在此定义之前没有声明
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 参数类型不一致
}
void func4(void)
{
//Non-compliant 在此定义前没有func4的声明
}
static void func5(void)
{
//compliant 该规则不适用于带内部链接的对象或函数
}
NISRA C中指出, 通常,单个声明将在头文件中进行,头文件将包含在定义或使用标识符的任何翻译单元中。这确保了以下之间的一致性:
注:一个项目中可能有多个头文件,但每个外部对象或函数只能在一个头文件中声明
示例如下:
/* featureX.h */
extern int16_t a; // Declare a
/* file.c */
int16_t a = 0; // Define a
该规范指出,如果使用的标识符存在多个定义(在不同的文件中)或根本不存在定义,则该行为是未定义的。该规则不允许在不同的文件中有多个定义,即使这些定义是相同的。如果声明不同,或将标识符初始化为不同的值,则为未定义的行为。
下边的示例中对象i被定义了两次:
/* file.c */
int16_t i = 10;
/* file1.c */
int16_t i = 20; //Non-compliant, 两次定义 i
下边的示例中对象j有一个临时定义和外部定义
/* file3.c */
int16_t j;//临时定义
int16_t j = 0; //compliant 外部定义
下边的示例中是不合规的,应为file4.c中的临时定义在翻译单元结束时会转变为外部定义,导致存在两个外部定义对象k
/* file4.c */
int16_t k;//临时定义-》变为外部定义
/* file5.c */
int16_t k = 0;//外部定义
规范中指出通过赋予对象内部链接或没有链接来限制对象的可见性,可以减少无意中访问该对象的机会。类似地,通过赋予函数内部链接来降低函数的可见性,可以减少无意中调用函数的可能性。
遵循此规则还可以避免标识符与另一个翻译单元或库中的相同标识符之间的混淆。
规则指出,当一个对象或函数被静态说明符修饰后,则对象或函数不能作为外部链接使用。
static int32_t x = 0; //内部链接,compliant
extern int32_t x;// Non-compliant
static int32_t f (void)//内部链接,compliant
int32_t f (void)// Non-compliant
{
return 1;
}
static int32_t g (void);//内部链接,compliant
extern int32_t g (void)// Non-compliant
{
return 1;
}
对于标题块作用域,个人理解的是局部变量。仅在单一函数中出现的。
下边合规的示例中, i在块作用域声明,因为它是一个循环计数器。同一个文件中其他函数不会调用该对象
void func (void)
{
int32_t i;
for(i = 0; i < N; i++)
{
}
}
下边的合规示例中,函数计数跟踪它被调用的次数,并返回该次数。其他函数不需要知道count实现的细节。
uint32_t count(void)
{
static uint32_t call_count = 0;
++call_count;
return call_count;
}
如果使用外部链接声明了内联函数,但没有在同一翻译单元中定义,则该行为是未定义的。
对使用了外部链接声明的内联函数的调用可以调用函数的外部定义,也可以使用内联定义。尽管这不会影响被调用函数的行为,但它可能会影响执行时间,从而对实时程序产生影响。
注:一个内联函数可以被多个翻译单元使用,只要把它的定义放在头文件中。
虽然可以声明不完全类型的数组并访问其元素,但当数组的大小可以显式地确定时,这样会更安全。为每个声明提供大小信息可以检查它们的一致性,以及还允许静态检查器执行一些数组边界分析。
extern int32_t array1[10];//compliant
extern int32_t array2[];//Non-compliant
规范中描述:隐式指定的枚举常量的值比前一个值大1。如果隐式指定了第一个枚举常量,则其值为0。
显式指定的枚举常量具有关联常量表达式的值。
如果隐式指定和显式指定的常量在枚举列表中混合使用,则可以复制值。这种复制可能是无意的,并可能导致意外行为。
该规则要求显式地复制枚举常量,这样就明确了意图.
enum colour {red = 3, blue, green, yellow = 5};//Non-compliant, yellow and green
enum colour {red = 3, blue, green = 5, yellow = 5};//compliant
指针应该指向const限定类型,除非:
1.用于修改对象 或
2.通过以下两种方式将其复制到另一个指向非const限定类型的指针:任务/内存移动或复制函数。
为了简单起见,该规则是根据指针及其所指向的类型编写的。
但是,它同样适用于数组及其包含的元素类型。数组应该有const限定类型的元素,除非:
1.数组中的任何元素被修改或者
2.通过上述方法将其复制到指向非const限定类的指针。
该规则通过确保指针不会无意中被用于修改对象来鼓励最佳实践。概念上,它等价于最初声明:
1.所有具有const限定类型元素的数组
2.所有指向const限定类型的指针。
然后只在需要符合语言标准约束的地方删除const限定
下边不合规示例中,p不用于修改对象,但对象所指向的类型不是const限定的
uint16_t f (uint16_t *p)
{
return *p;
}
上述示例的合规写法应为:
uint16_t f (const uint16_t *p)
void h (const uint16_t *p)
{
*p = 0; // Non-compliant,试图修改const限定的指针
}
uint6_t first (uint16_t a[4]) // Non-compliant
{
return a[0];
}
uint16_t first (const uint16_t a[5]) //compliant
当使用restrict型限定符时必须要小心,使用它不仅可以提高编译器生成代码的效率,还可以允许改进静态分析。但是使用restrict限定符必须确保两个或多个指针操作的内存区域不重叠。
如果不正确的使用restrict,编译器将生成行为和预期不一致的代码,这是一个风险。
下边示例是合规的,因为MISRA c指南不被应用于库函数。但是编程人员需要确保p q and n没有重叠:
void f (void)
{
memcpy(p, q, n);
}
下边示例是不合规的,因为参数被restrict定义:
void user_copy(void * restrict p, void * restrict q, size_t n)
{
}
根据标准,具有静态特性的对象将自动初始化为0,除非显式初始化。非静态属性的对象不会自动初始化,因此可能具有不确定的值。
注:有时可能忽略非静态属性对象的显示初始化。当使用goto或switch语句跳转到标签时,会绕过对象的声明,该对象将按预期声明,但将忽略任何显式初始化。
void f(bool_t b, uint16_t *p)
{
if(b)
{
*p = 6U;
}
}
void g(void)
{
uint16_t u;
f(false, &u);
if(u == 3U)
{
//Non-compliant u has not been assigned a value
}
}
下边的示例不合规的在C99,goto语句跳过了x的初始化
{
goto L1;
uint16_t x = 10u;
L1:
x = x + 1u;//Non-compliant u has not been assigned a value
}
使用大括号表示子对象的初始化提高了代码的清晰度,并迫使程序员考虑复杂数据结构(如多维数组或结构数组)中元素的初始化。
标准允许以下三种初始化的形式并且是等价的。但是该规则不允许第一种形式,因为它没有使用大括号显式的显示子对象的初始化:
int16_t array[3][2] = {1,2,0,0,5,6}//Non-compliant
int16_t array[3][2] = {{1,2},{0,0},{5,6}}//compliant
int16_t array[3][2] = {{1,2},{0},{5,6}}//compliant
下边示例通过子对象初始化来初始化数组:
int16_t test[2][2] = {{0},[1][1]=1};//compliant,打印结果:0,0,0,1
int16_t test[2][2] = {{0},[1][1]=1,[1][0]=1};//compliant,打印结果:0,0,1,1
int16_t test[2][2] = {[0][1]=0,{0,1}};//compliant,打印结果:0,0,0,1
int16_t test[2][2] = {{0},[1][0]=1,1};//Non-compliant,test[1][1]与test[1][0]
下边都是些合规的示例:
float32_t a[3][2] = {0};
float32_t a[3][2] = {{0},{0},{0}};
float32_t a[3][2] = {{0.0f,0.0f},{0.0f,0.0f},{0.0f,0.0f}};
union u1{
int16_t i;
float32_t f;
}u = {0};
struct s1{
uint16_t len;
char buf[8];
}s[3] = {
{5u, {'a','b','c','d','e','\0','\0','\0'}},
{2u,{0}},
{.len=0u}
};
规范指出,对于数组的每个元素都应有个清晰的显式初始化值。
int32_t x[3] = {0,1,2};
int32_t y[3] = {0,1};//Non-compliant
float32_t t[4] = {[1]=1.0f,2.0f};//Non-compliant t[0]/t[3]被隐式初始化
char h[10] = "hello";//compliant 数组元素6->9被隐式初始化为'\0'
示例如下:
关于数组的初始化:
int16_t a1[5] = {1,2,3,4,5};//compliant
int16_t a2[5] = {[0]=1,[1]=2,[2]=3,[3]=4,[4]=5};//compliant
/* 下边示例是Non-compliant, 重复初始化导致元素值被覆盖,会引发预料外的结果。1,2,4,0,5 */
int16_t a3[5] = {[0]=1,[1]=2,[2]=3,[2]=4,[4]=5};
/* 下边的违规示例中,未说明是否存在副作用 */
uint16_t *p;
void test(void)
{
uint16_t array[2] = {[0] = *p++, [0] = 1};
}
关于结构体的初始化:
struct mystruct
{
int32_t a;
int32_t b;
int32_t c;
int32_t d;
};
struct mystruct s1 = {100,1,43,999};//compliant
struct mystruct s2 = {.a=100, .b=1, .c=43, .d=999};//compliant
struct mystruct s3 = {.a=100, .b=1, .a=43, .d=999};//Non-compliant,s3=43,1,0,999
为了使意图明确,数组大小应该显式的声明出来。在程序开发期间,如果初始化元素的索引发生变化,超出了数组边界,这样违反了规范(C99第6/7/8节)而报错误信息,这就提供了某种保护。
int a1[] = {[0] = 1};//Non-compliant
int a1[10] = {[0] = 1};//compliant
本节中的规则共同定义了基本类型模型,并对C类型系统进行了限制,以便:
一个对象或表达式的基本类型是由其基本类型类别和大小定义的。
表达式的基本类型类别反映了其基本行为,可以是:
下表显示了标准整数类型如何映射到基本类型类别。
Boolean | character | signed | unsigned | enum | floating |
---|---|---|---|---|---|
_Bool | char | signed char signed short signed int signed long signed long long |
unsigned char unigned short unsigned int unsigned long unsigned long long |
named enum | float double long double |
在下表中,单元格中的数字表示在何处应用限制,以限制将基本类型作为操作符的操作数使用。这些数字与下文中的数字一一对应,并表明为什么实行每一项限制。
operator | operand | Boolean | character | enum | signed | unsigned | floating |
---|---|---|---|---|---|---|---|
[ ] | integer | 3 | 4 | 1 | |||
+ | 3 | 4 | 5 | ||||
- | 3 | 4 | 5 | 8 | |||
+ - | either | 3 | 5 | ||||
* / | either | 3 | 4 | 5 | |||
% | either | 3 | 4 | 5 | 1 | ||
< > <= >= | either | 3 | |||||
== != | either | ||||||
! && 或 | any | 2 | 2 | 2 | 2 | 2 | |
<< >> | left | 3 | 4 | 5,6 | 6 | 1 | |
<< >> | right | 3 | 4 | 7 | 7 | 1 | |
~ & ^ | any | 3 | 4 | 5,6 | 6 | 1 | |
?: | 1st | 2 | 2 | 2 | 2 | 2 | |
?: | 2nd and 3rd |
Rationale:
下边是本节的一些示例说明
enum enuma { a1, a2, a3 } ena, enb; //本质上枚举类型
enum {K1=1, K2=2}; //本质上有符号类型
下面的示例是不合规的,注释指的是导致不合规的编号的基本原理项
f32a & 3U //Rationale 1,违背约束条件
f32a << 2 //Rationale 1,违背约束条件
cha && bla //Rationale 2,char类型用作boolean值
ena ? a1 : a2 //Rationale 2,enum类型用作boolean值
s8a && bla //Rationale 2,signed类型用作boolean值
u8a ? a1 : a2 //Rationale 2,unsigned类型用作boolean值
f32a && bla //Rationale 2,float类型用作boolean值
bla * blb // Rationale 3,boolean类型用作数值使用
bla > blb // Rationale 3,boolean类型用作数值使用
cha & chb // Rationale 4,char类型用作数值使用
cha << 1 // Rationale 4,char类型用作数值使用
ena-- // Rationale 5,enum类型用作算术操作
ena * a1 // Rationale 5,enum类型用作算术操作
s8a & 2 // Rationale 6,signed类型的位操作
50 << 3U // Rationale 6,signed类型的移位操作
u8a << s8a // Rationale 7,移位幅度使用有符号类型
u8a << -1 // Rationale 7,移位幅度使用有符号类型
-u8a // Rationale 8,无符号类型的一元减操作
/* 不合规操作,违反此规则以及10.3 */
ena += a1 // Rationale 5,enum类型使用算术操作
/* 下边示例是合规的 */
bla && blb
bla ? u8a : u8b
cha - chb
cha > chb
ena > a1
K1 * s8a
s8a + s16b
-(s8a) * s8b
s8a > 0
--s16b
U8a + u16b
u8a & 2U
u8a > 0u
u8a << 2U
f32a + f32b
f32a > 0.0
规则表明,本质上具有字符类型(字符数据)的表达式不应用于算术,因为数据不代表数值
上面的使用是允许的,因为它们允许对字符数据进行可能合理的操作。
例如:
下边是合规的示例:
'0' + u8a //转换u8a到数字
s8a + '0' //转换s8a到数字
cha - '0' //转换cha到序数
'0' - s8a //转换-s8a到数字
下边是不合规的示例:
s16a - 'a'
'0' + f32a
cha + ':'
cha - ena
C语言允许程序员有相当大的自由度,并允许自动执行不同算术类型之间的赋值。但是,使用这些隐式转换可能会导致意想不到的结果,可能会导致值,符号或精度的损失。关于C类型系统的更多细节可以在附录C中找到
MISRA基本类型模型强制使用更强的类型,减少了这些问题发生的可能性。
另:
如下示例是合规的:
uint8_t u8a = 0;
bool_t flag = (bool_t)0;
bool_t ser = true;
bool_t get = (u8b > u8c);
u8a = 2;
cha += 1;
u8a = u8b + u8c + u8d;
u8a = (uint8_t) s8a;
u32a = u16a;
u32a = 2U + 125U;
下边示例是不合规的,因为他们有不同的基本类型:
uint8_t u8a = 1.0f; //无符号和浮点型
bool_t bla = 0; //布尔和有符号型
cha = 7; //字符和浮点型
u8a = 'a'; //无符号和字符型
u8b = 1-2; //无符号和有符号型
u8c += 'a'; // u8c = u8c + 'a' 将字符分配给无符号
下边示例是不合规的,由于它们包含对较窄的基本类型的赋值:
s8a = K2;// 常量值不适合
u16a = u32a;// uint32_t to uint16_t
uint8_t fool (uint16_t x)
{
return x; // uint16_t to uint8_t
}
以下允许使用通用形式的字符操作:
下边的示例是合规的,由于它们是同样的基本类型:
ena > A1
u8a + u16b
下边的示例是合规的,由于字符操作1:
cha += u8a
下边的示例是不合规的:
s8a += u8a //signed and unsigned
u8b + 2 //unsigned and signed
enb > A1 //enum and enum
u8a += cha //unsigned and char
示例如下:
(bool_t) false //compliant
(int32_t) 3U //compliant
(bool_t) 0 //compliant
(bool_t) 3U //Non-compliant
(int32_t) ena //compliant
(enum enuma) 3 //Non-compliant
(char) enc //compliant
示例如下
u16c = u16a + u16b; // compliant
u32a = (uint32_t) u16a + u16b //compliant cast causes addition in uint32_t
以下是不合规的示例:
u32a = u16a + u16b; 赋值上的隐式转换
use_uint32(u16a + u16b); //参数的隐式转换
示例如下:
以下示例是合规的
u32a * u16a + u16b;
(u32a*u16a) + u16b
u32a * ((uint32_t) u16a + u16b)
以下示例是不合规的:
u32a * (u16a + u16b)
u32a + (u16a + u16b)
示例如下:
(uint16_t) (u32a + u32b)
(uint16_t) (s32a + s32b) // Non-cmpliant 不同的基本类型
(uint16_t) s32a // compliant s32a不是复合表达式
(uint32_t) (u16a + u16b) //Non-compliant 不能转换为更宽的基本类型
指针类型可以分为以下几种:
涉及指针的唯一被标准允许的转换是:
尽管语言约束允许,指针和除整数类型外的任何算术类型之间的转换都是未定义的
以下允许的指针转换不需要显式转换:
指针类型和整数类型之间的转换是由实现定义的
基本原理概述:
将指向函数的指针转换为:
如果函数是通过与被调用函数类型不兼容的指针调用的,则该行为是未定义的。标准允许将指向函数的指针转换为指向不同类型函数的指针。标准也允许将整数转换为指向函数的指针。然而,为了避免由于使用不兼容的指针类型调用函数而导致的未定义行为,这两条规则都是禁止的。
例外的情况:
示例如下:
typedef void (*fp16) (int16_t n);
typedef void (*fp32) (int32_t n);
#include //获取宏NULL
fp16 fp1 = NULL; //compliant 例外的情况1
fp32 fp2 = (fp32) fp1; // Non-compliant,函数指针转换到不同类型的函数指针
if (fp2 != NUll) //compliant 例外的情况1
{
}
fp16 fp3 = (fp16) 0x8000; //Non-compliant 整型转换为函数指针
fp16 fp4 = (fp16) 1.0e6F; //Non-compliant 浮点型转换为函数指针
在下面的例子中,函数调用返回一个指向函数类型的指针。将返回值转换为void,符合此规则。
typedef fp16 (*pfp16) (void);
pfp16 pfp1;
(void) (*pfp1()); // compliant 例外的情况2 函数指针转换为void
下面的示例展示了从函数类型到指向该函数类型的指针的兼容隐式转换。
extern void f (int16_t n);
f(1); // compliant 例外的情况3
fp16 fp5 = f; // compliant 例外的情况3
基本原理概述:
转换到或从指向不完全类型的指针可能导致指针未正确对齐,从而导致未定义的行为
将指向不完全类型的指针转换或从浮点类型转换总是导致未定义的行为。
指向未完成类型的指针有时用于隐藏对象的表示形式。将指向未完成类型的指针转换为指向对象的指针将破坏这种封装。
例外的情况:
struct tmp1; //不完全类型
struct tmp2; //不完全类型
struct tmp1 *sp;
struct tmp2 *tp;
int16_t *ip;
#include //获取宏NULL
ip = (int16_t *)sp; // Non-compliant
sp = (struct tmp1 *)1234; // Non-compliant
tp = (struct tmp2 *) sp;// Non-compliant
sp = NULL; // compliant 例外的情况1
struct tmp1 *f(void);
(void) f(); // compliant 例外的情况2
基本原理概述:
将指向对象的指针转换为指向不同对象的指针可能导致指针未正确对齐,从而导致未定义的行为。
即使已知转换会产生正确对齐的指针,但如果该指针用于访问对象,则其行为可能是未定义的。例如,如果一个int类型的对象被作为short类型访问,即使int和short有相同的表示和对齐要求,其行为也是未定义的。
例外的情况:
允许将指向对象类型的指针转换为指向char,signed char 或 unsigned char类型之一的对象类型的指针。标准保证指向这些类型的指针可以用来访问对象的
示例如下:
uint8_t *p1;
uint32_t *p2;
p2 = (uint32_t *) p1; //Non-compliant 可能导致指针未对齐
const short *p;
const volatile short *q;
q = (const volatile short *) p; // compliant
int * const * p;
const int * const *q;
q = (const int * const *) p; // Non-compliant 指针类型不同
基本原理:
将整数转换为指向对象的指针可能导致指针未正确对齐,从而导致未定义行为。
将对象指针转换为整数可能会产生无法用所选整数类型表示的值,从而导致未定义的行为。
应该尽可能避免在指针和整数类型之间进行转换,但在寻址内映射寄存器或其他硬件特定特性时,可能需要进行类型转换。如果使用整数和指针之间的转换,应注意确保所产生的任何指针不会引发规则11.3种讨论的未定义行为。
例外的情况:
具有整型的空指针常量可以转换为指向对象的指针
示例如下:
uint8_t *PORTA = (uint8_t *) 0x0002; // Non-compliant
uint16_t *p;
int32_t addr = (int32_t) &p; // Non-compliant
uint8_t *q = (uint8_t *) addr; // Non-compliant
bool_t b = (bool_t) p; // Non-compliant
enum etag {A, B} e = (enum etag) p; // Non-compliant
基本原理:
将指向void的指针转换为指向object的指针可能导致指针没有正确对齐,从而导致未定义的行为。在可能但没必要的情况下应该避免使用,例如在处理内存分配函数时,如果要将指向对象的指针转换为指向void的指针,应注意确保所产生的任何指针不会导致规则11.3中讨论的未定义行为
例外的情况:
指向void类型的空指针常量可以转换为指向对象的指针。
示例如下:
uint32_t *p32;
void *p;
uint16_t *p16;
p = p32; // compliant 指针类型由uint32_t 到 void
p16 = p; //Non-compliant
p = (void *) p16; //compliant
p32 = (uint32_t *) p; //Non-compliant
基本原理:
将整数转换为指向void的指针可能导致指针没有正确对齐,从而导致未定义的行为。
将void指针转换为整数可能会产生无法用所选整数类型表示的值,从而导致未定义的行为
任何非整数算术类型与指向void的指针之间的转换都是未定义的
例外的情况:
值为0的整型常量表达式可以转换为指向void的指针
示例如下:
void *p
uint32_t u;
p = (void *) 0x1234u; // Non-compliant
p = (void *) 1024.0f; // Non-compliant
u = (uint32_t) p; // Non-compliant
将本质上是布尔型,字符型或枚举型的类型转换为指向对象的指针可能导致指针没有正确对齐,从而导致未定义的行为。
将对象指针转换为本质上的布尔型,字符型或枚举型可能会产生无法用所选整数类型表示的值,从而导致未定义的行为。
将指向对象的指针转换为浮点型或从浮点型转换为浮点型导致未定义的行为。
示例如下:
int16_t *p;
float32_t f;
f = (float32_t) p; // Non-compliant
p = (int16_t *) f; // Non-compliant
2004版本:
先来再次了解下各个数据类型:
| 类型标识符 | 类型说明 | 字节数 | 范围 | 备注 |
|–|–|–|–|–|–|
| char | 字符型 | 1 | -128~127 | - |
| unsigned char | 无符号字符型 | 1 | 0~255 | - |
| short | 短整型 | 2 | -32768~32767 | - |
| unsigned short | 无符号短整型 | 2 | 0~65535 | - |
| int | 整型 | 4 | -2147483648~2147483647 | - |
| unsigned int | 无符号整型 | 4 | 0~4294967295 | - |
| float | 单精度 | 4 | - | 7位有效位 |
| double | 双精度 | 8 | - | 15位有效位 |
MISRA-C 中对于表达式中存在隐式数据类型转换的情况作了严格的限制:
规则10.1:以下情况,整型表达式不允许出现隐式转换
规则10.2:以下情况,浮点数表达式不允许出现隐式转换
举两个例子:
第一个是关于符号位的:
//incorrect
unsigned char a = 0x5a;//0101 1010, ~a->11111111 1010 0101
unsigned char b = (~a) >> 4;
printf("%d\r\n", b); //250
//correct
unsigned char a = 0x5a;//0101 1010, ~a->11111111 1010 0101
unsigned char b = ((unsigned char)(~a)) >> 4;
printf("%d\r\n", b); //100
第二个是关于数据范围溢出的:
//当编译器为16位时打印结果为5,5
//当编译器为32位时打印结果为5,65541
#include <stdio.h>
int main(void) {
unsigned short a = 10;
unsigned short b = 65531;
unsigned short test = a + b;
unsigned int c = 0;
unsigned int d;
d = (a+b)+c;
printf("%d-%d\r\n",test,d);//5,65541
return 0;
}
//correct operate
#include <stdio.h>
int main(void) {
unsigned short a = 10;
unsigned short b = 65531;
unsigned short test = a + b;
unsigned int c = 0;
unsigned int d;
d = (unsigned int)a + (unsigned int)b + c;
printf("%d-%d\r\n",test,d);//5,65541
return 0;
}
//有的人可能说将d = (a+b)+c;->d = a+(b+c);也可以避免出现
//溢出问题,但这并不是治本的方法,学习是为了了解本质,而
//不是只为了解决问题。只有明确了每一个操作数的
//实际数据类型,才能保障代码的安全
针对第一个例子,MISRA C中有明确规定:
规则10.5:如果位操作符~和移位操作符<< or >>联合作用于unsigned char
对于MISRA C规范来说,在编码的过程中与其去一一辨别隐式表达式是否在MISRA C规则中,还不如去用强制转换显式的标识出每个操作数的实际数据类型。
MISRA C中为了防止越界等问题,做了如下规定:
持续更新中。。。。。