文章目录
- Rules
- 8.10 基本类型模型(The essential type model)
- 8.10.1 原理
- 8.10.2 基本类型(Essential type)
- Rule 10.1 操作数不得具有不适当的基本类型
- Rule 10.2 在加减法运算中,不得不当使用本质为字符类型的表达式
- Rule 10.3 表达式的值不得赋值给具有较窄基本类型或不同基本类型的对象
- Rule 10.4 执行常规算术转换的运算符运算符的两个操作数应具有相同的基本类型
- Rule 10.5 表达式的值不应强制转换为不适当的基本类型
- 8.10.3复合运算符和表达式
- Rule 10.6 复合表达式的值不能赋给具有更宽基本类型的对象
- Rule 10.7 如果将复合表达式用作执行常规算术转换的运算符的一个操作数,则另一个操作数不得具有更宽的基本类型
- Rule 10.8 复合表达式的值不得转换为其他基本类型或更宽的基本类型
Rules
8.10 基本类型模型(The essential type model)
8.10.1 原理
本节中的规则共同定义了基本类型模型,并对C类型系统进行了约束,以便:
- 支持更强大的类型检查系统;
- 为定义控制隐式和显式类型转换使用的规则提供合理的基础;
- 推广可移植编码做法;
- 解决一些在ISO C中发现的类型转换异常。
基本类型模型通过为被ISO C 认为属于算术类型的对象和表达式赋值基本类型来实现上述目的。例如,
将 int 与char 相加得到的结果实质上具有char类型,而不是整数增大实际产生的 int 类型。
基本类型模型背后的完整原理在附录C中给出,附录D提供了任何算术表达式的基本类型的全面定义
8.10.2 基本类型(Essential type)
对象或表达式的基本类型由其基本类型类别和大小定义。
表达式的基本类型类别反映了其基本行为,它们可能是:
•基本型是布尔值;
•基本型为字符;
•基本型是枚举;
•基本型为有符号的;
•基本型为无符号的;
•基本型为浮点型
注意:每个枚举类型本质上都是一个唯一的枚举类型,标识为enum<i>。这允许将不同的枚举类型作为不同的类型处理,从而支持更强大的类型检查系统。一个例外是在C90中使用枚举类型来定义布尔值。
这样的类型被认为本质上是布尔类型。另一个例外是附录d中定义的匿名枚举的使用。匿名枚举是定义一组相关的常量整数的一种方式,并且被认为具有本质上的有符号类型
在比较同一类型类别的两种类型时,术语更宽和更窄用于描述它们的相对大小(以字节为单位)。两种不同的类型有时用相同的大小实现
下表显示了标准整数类型如何映射到基本类型类别。
注意:C99实现可以提供扩展的整数类型,每个类型将被分配一个适合于其等级和符号相符的位置(参见C99第6.3.1.1节)。
本节规则强制的限制也适用于复合赋值操作符(例如^=),因为这些等价于对使用算术运算、位运算或移位运算之一所获得的结果赋值。例如
u8a += u8b + 1U;
等同于:
u8a = u8a + ( u8b + 1U );
Rule 10.1 操作数不得具有不适当的基本类型
等级:必要
分析:可判定,单一编译单元
适用:C90,C99
展开:在下表中,单元格中的数字表示限制在何处适用于将基本类型用作操作符的操作数。这些数字对应于下面原理部分的各段,并指出施加每项限制的原因。
在此规则下,“++”与“–”运算符的行为与双目运算符“+”、“-”相同。
其他规则进一步限制了可在表达式中使用的基本类型的组合。
原理:
- 将浮点型表达式用于这些操作数,违反 C 语言标准。
- 当操作数被解释为布尔值时,应始终使用基本型为布尔类型的表达式。
- 如果操作数被解释为数值,则不应使用布尔类型的表达式。
- 如果操作数被解释为数值,则不应使用字符型的操作数。字符数据的数值是由实现定义的。
- 枚举型的操作数不应在算术运算中使用,因为枚举对象使用实现定义的整数类型,枚举型是否有符号依赖于编译环境。因此,涉及枚举对象的操作可能会产生意外类型的结果。请注意,来自匿名枚举的枚举常量的基本类型为有符号型。
- 移位运算符的左操作数和按位运算只能在无符号型的操作数上执行。它们在有符号型上使用所产生的数值是实现定义的,结果可能与预期不同。
- 移位运算符的右操作数应为无符号型,以确保不会因负移位而导致未定义的行为。
- 无符号型的操作数不应用作单目运算符“-”的操作数,因为结果的是否有符号由 int 的实现大小决定,亦即依赖编译环境。
例外:有符号型的常量表达式,若其值为非负,可以用作移位运算符的右手操作数。
示例:
enum enuma { a1, a2, a3 } ena, enb; /* 基本型为枚举<enuma>型*/
enum { K1 = 1, K2 = 2 }; /* 基本型为有符号整形 */
下面的示例违规,注释中标识其违规的“原理”序号及其具体原因。
f32a & 2U /* 原理 1 - 违反标准约束 */
f32a << 2 /* 原理 1 - 违反标准约束 */
cha && bla /* 原理 2 - 字符型用作布尔型 */
ena ? a1 : a2 /* 原理 2 - 枚举型用作布尔型 */
s8a && bla /* 原理 2 - 有符号型用作布尔型 */
u8a ? a1 : a2 /* 原理 2 - 无符号型用作布尔型 */
f32a && bla /* 原理 2 - 浮点型用作布尔型 */
bla * blb /* 原理 3 - 布尔型用作数值 */
bla > blb /* 原理 3 - 布尔型用作数值 */
cha & chb /* 原理 4 - 字符型用作数值 */
cha << 1 /* 原理 4 - 字符型用作数值 */
ena-- /* 原理 5 - 枚举型用于数值计算 */
ena * a1 /* 原理 5 - 枚举型用于数值计算 */
s8a & 2 /* 原理 6 - 有符号型用于按位运算的左操作数 */
50 << 3U /* 原理 6 - 有符号型用于移位运算的左操作数 */
u8a << s8a /* 原理 7 - 有符号型用于按位运算的右操作数 */
u8a << -1 /* 原理 7 - 有符号型用于按位运算的右操作数 */
-u8a /* 原理 8 - 单目运算"-"不得用在无符号数上 */
下面示例既违反的此规则,又违反了 Rule 10.3:
ena += a1 /* 原理 5 - 枚举型用于数值计算 */
以下示例合规:
bla && blb
bla ? u8a : u8bcha - chb
cha > chbena > a1
K1 * s8a /* 合规 - K1为匿名枚举, 识别为有符号数 */s8a + s16b
-(s8a) * s8b
s8a > 0
--s16bu8a + u16b
u8a & 2Uu8a > 0U
u8a << 2U
u8a << 1 /* 合规 - 符合例外 */f32a + f32b
f32a > 0.0
下面示例符合此规则,但违反 Rule 10.2。
cha + chb
Rule 10.2 在加减法运算中,不得不当使用本质为字符类型的表达式
等级:必要
分析:可判定,单一编译单元
适用:C90,C99
展开:
适当的用法是:
- 对于+操作符,一个操作数基本上是字符类型,另一个操作数基本上是有符号类型或无符号类型。操作的结果本质上具有字符类型。
- 对于-操作符,第一个操作数本质上应为字符类型,第二个操作数本质上应为有符号类型、无符号类型或字符类型。如果两个操作数本质上都是字符类型,则结果具有标准类型(在本例中通常是int),否则结果具有本质上的字符类型
原理:字符型(字符数据)的表达式不能做算术计算,因为该数据不代表数值。
允许上述用法,是因为它们可以潜在地合理处理字符数据。例如:
•两个本质上是字符类型的操作数的减法可以用于在’ 0 ‘到’ 9 '范围内的数字和相应的序数之间进行转换;
•添加本质上的字符类型和本质上的无符号类型可用于将序数转换为0到9范围内的相应数字;
•从本质字符类型中减去本质unsigned类型可用于将字符从小写转换为大写
示例:
下列示例合规:
'0' + u8a /* 将 u8a 换算为数字(字符) */
s8a + '0' /* 将 s8a 换算为数字(字符) */
cha - '0' /* 将 cha 换算为序数(数值) */
'0' - s8a /* 将 -s8a 换算为数字(字符) */
下列示例违规:
s16a - 'a' /* 减法第一个操作数不为字符型 */
'0' + f32a /* 加法一个操作数应为字符型,另一个不为有符号或无符号型 */
cha + ':' /* 加法一个操作数应为字符型,另一个不为有符号或无符号型 */
cha - ena /* 减法第二个操作数不为有符号类型、无符号类型或字符类型 */
Rule 10.3 表达式的值不得赋值给具有较窄基本类型或不同基本类型的对象
等级:必要
分析:可判定,单一编译单元
适用:C90,C99
展开:
此规则涵盖以下操作:
- 术语表(Glossary)中定义的任务;
- 将switch语句的case标号中的常量表达式转换为控制表达式的提升类型。
原理:C 语言允许程序员具有极大的自由度,并允许自动执行不同算术类型之间的隐式赋值。但是,使用这些隐式转换可能会导致意外结果,并可能导致值、符号或精度的损失。有关 C 语言类型系统的更多详细信息, 请参见附录 C。
MISRA 的基本类型模型强制使用了更强的类型限制,从而降低了发生这些问题的可能性。
例外:
- 有符号型的对象的非负整数常量表达式可以赋值给无符号型的对象,只要它的值可以用该类型完全表示。
- 初始化{0}可用于初始化聚合或联合类型
示例:
enum enuma { A1, A2, A3 } ena;
enum enumb { B1, B2, B3 } enb;
enum { K1=1, K2=128 };
合规示例:
uint8_t u8a = 0; /* 符 合 例 外 */
bool_t flag = ( bool_t ) 0;
bool_t set = true; /* true 为布尔型*/
bool_t get = ( u8b > u8c );ena = A1;
s8a = K1; /* 常数值适合 */
u8a = 2; /* 符合例外 */
u8a = 2 * 24; /* 符合例外 */
cha += 1; /* cha = cha + 1 字符赋值给字符 */pu8a = pu8b; /* 相同的基本类型 */
u8a = u8b + u8c + u8d; /* 相同的基本类型 */
u8a = (uint8_t) s8a; /* 强制转换为相同的基本类型 */
u32a = u16a; /* 赋值给更宽的基本类型 */
u32a = 2U + 125U; /* 赋值给更宽的基本类型 */
use_uint16(u8a); /* 赋值给更宽的基本类型 */
use_uint16(u8a + u16b); /* 赋值给相同的基本类型 */
下面的示例违规,它们的基本类型不相同:
uint8_t u8a = 1.0f; /* 无符号(整)型与浮点型 */
bool_t bla = 0; /* 布尔型与有符号型 */
cha = 7; /* 字符型与有符号型 */
u8a = 'a'; /* 无符号型与字符型 */
u8b = 1 - 2; /* 有符号型与无符号型 */
u8c += 'a'; /* u8c = u8c + 'a' 字符型与无符号型相加 */
use_uint32(s32a); /* 有符号型与无符号型 */
下面的示例违规,因为它们被赋值给较窄的基本类型:
s8a = K2; /* 常数值超出被赋值的类型的值域范围 */
u16a = u32a; /* uint32_t to uint16_t */
use_uint16 ( u32a ); /* uint32_t to uint16_t */
uint8_t foo1 ( uint16_t x )
{return x; /* uint16_t to uint8_t */
}
也就是说赋值带来的隐式类型转换只能从窄数据类型转换为同或宽数据类型
Rule 10.4 执行常规算术转换的运算符运算符的两个操作数应具有相同的基本类型
等级:必要
分析:可判定,单一编译单元
适用:C90,C99
展开:
该规则适用于常规算术转换中描述的运算符(请参阅 C90 第 6.2.1.5 节,C99 第 6.3.1.8 节)。此规则适用所有双目运算符,除了移位(<< >>)、逻辑与(&&)、逻辑或(||)和逗号“,”运算符。
注意:自增和自减操作符不包括在此规则中。
原理:C语言给了程序员相当大的自由,并允许自动执行不同算术类型之间的转换。然而,使用这些隐式转换可能会导致意想不到的结果,可能会丢失值、符号或精度。关于C类型系统的更多细节可以在附录C中找到。
MISRA 的基本类型模型强制使用了更强的类型限制,以保证隐式类型转换的结果与开发人员的期望一致。
例外:
以下操作被允许,用以保证常见的字符操作被识别为合规:
- 双目运算符“+”和“+=”允许一个操作数是字符型,而另一个操作数为有符号型或无符号型;
- 双目运算符“-”和“-=”允许字符型的左操作数和有符号型或无符号型的右操作数。
示例
enum enuma { A1, A2, A3 } ena;
enum enumb { B1, B2, B3 } enb;
下面的合规示例中,运算符两侧的数据具有相同的基本类型:
ena > A1
u8a + u16b
下面示例符合例外 1:
cha += u8a /* 符合一个操作数为字符型,另一个操作数为无符号型*/
下面示例既不合此规则,也违反了 Rule 10.3:
s8a += u8a /* 有符号与无符号数计算 */
下面示例违规:
u8b + 2 /* 无符号数与有符号数计算 */
enb > A1 /* 枚举<enumb>型 与 枚举<enuma>型 */
ena == enb /* 枚举<enuma>型 与 枚举<enumb>型 */
u8a += cha /* 无符号整型数与字符型数据 */
Rule 10.5 表达式的值不应强制转换为不适当的基本类型
等级:建议
分析:可判定,单一编译单元
适用:C90,C99
展开:
下表中显示了应避免的强制转换,表中第一列为强制转换(显示转换)的目标类型。
*:枚举型可以强制转换为枚举型,前提是强制转换为相同的基本枚举类型。这样的转换是多余的。即,应避免将一种枚举型转换为其他枚举型的操作。
不允许从void强制转换为任何其他类型,因为这会导致未定义的行为。规则1.3涵盖了这一点。
原理 :
出于合法功能的原因,可能会引入显式强制转换,例如:
•改变执行后续算术运算的类型;
有意截断一个值;
•为了清晰起见,使类型转换显式
但是,某些显式强制转换被一般是不合适的:
•在C99中,对_Bool类型强制转换或赋值的结果总是0或1。当转换为本质上定义为布尔类型的另一种类型时,情况不一定是这样;
•强制转换为本质上是枚举类型的值可能不在该类型的枚举常量集合内;
•从本质上的布尔类型强制转换为任何其他类型都不太可能有意义
•浮点和字符类型之间的转换没有意义,因为这两种表示之间没有精确的映射
例外
可以将带符号的值为 0 或 1 的整数常量表达式强制转换为布尔型。 这允许实现非C99 布尔模型。
示例:
( bool_t ) false /* 合规 - C99标准中'false'是布尔型 */
( int32_t ) 3U /* 合规 */
( bool_t ) 0 /* 合规 - 符合例外 */
( bool_t ) 3U /* 违规 */
( int32_t ) ena /* 合规 */
( enum enuma ) 3 /* 违规 */
( char ) enc /* 合规 */
8.10.3复合运算符和表达式
附录 C 中提到的某些问题,可以通过限制可能应用于非平凡表达式的隐式和显式转换来避免。 这些问题包括:
•关于整数表达式计算类型的混淆,因为这取决于任何整数提升后操作数的类型。算术运算结果的类型取决于int类型的实现大小;
•程序员中常见的误解是,执行计算的类型受到分配或强制转换结果的类型的影响。这种错误的期望可能会导致意想不到的结果
除了先前的规则外,基本类型模型还对操作数为复合表达式的表达式进行了进一步的限制,如下所述。以下内容在本文档中定义为复合运算符:
•乘除法(*,/,%)
•加减法(二进制+,二进制-)
•按位(&,|,^)
•移位 (<<, >>)
•条件运算(?:)如果第二个或第三个操作数为复合表达式
复合赋值相当于对其对应的复合操作符的结果进行赋值
复合表达式在本文中被定义为非常量表达式,它是复合运算符的直接结果
注意:
•复合赋值操作符的结果不是复合表达式;
•带括号的复合表达式也是复合表达式;
•常量表达式不是复合表达式
Rule 10.6 复合表达式的值不能赋给具有更宽基本类型的对象
等级:必要
分析:可判定,单一编译单元
适用:C90,C99
展开:
此规则涵盖 Rule 10.3 中描述的赋值操作。
原理:基本原理在关于复合运算符和表达式的介绍中进行了描述(请参见第 8.10.3 节)。
示例:
合规示例:
u16c = u16a + u16b; /* 相同的基本类型 */
u32a = (uint32_t)u16a + u16b;/* 强制转换, 表达式为uint32_t型加法 */
违规示例:
u32a = u16a + u16b;/* 赋值时隐式转换 */
use_uint32(u16a + u16b);/* 函数参数的隐式转换 */
Rule 10.7 如果将复合表达式用作执行常规算术转换的运算符的一个操作数,则另一个操作数不得具有更宽的基本类型
等级:必要
分析:可判定,单一编译单元
适用:C90,C99
展开:
基本原理在关于复合运算符和表达式的介绍中进行了描述(请参见第 8.10.3 节)。
限制复合表达式的隐式转换意味着,表达式中的算术运算序列必须以完全相同的基本类型进行。 这减少了可能发生的开发者困惑。
注意:这并不意味着表达式中的所有操作数都具有相同的基本类型。
表达式u32a + u16b + u16c是兼容的,因为这两个加法在理论上都是在uint32_t类型中执行的。在这种情况下,只隐式转换非复合表达式。
表达式(u16a + u16b) + u32c是不合规的,因为左边的加法在uint16_t类型中执行,右边的加法在uint32_t类型中执行,需要将复合表达式u16a + u16b隐式转换为uint32_t
示例:
合规示例:
u32a * u16a + u16b /* 无表达式转换 */
( u32a * u16a ) + u16b /* 无表达式转换 */u32a * ( ( uint32_t ) u16a + u16b ) /* * 两侧操作数类型一致 */u32a += ( u32b + u16b ) /* 无表达式转换 */
违规示例:
u32a * (u16a + u16b) /* 表达式(u16a + u16b)被隐式转换 */
u32a += (u16a + u16b) /* 表达式(u16a + u16b)被隐式转换 */
以上的违规示例需要将u32a强转为uint16,才合规,感觉不太合理,这样不就可能溢出么。。。按Rule10.8,不能将复合表达式的值转为更宽的基本类型
Rule 10.8 复合表达式的值不得转换为其他基本类型或更宽的基本类型
等级:必要
分析:可判定,单一编译单元
适用:C90,C99
展开:
基本原理在关于复合运算符和表达式的介绍中进行了描述(请参见第 8.10.3 节)。
不允许强制转换为更宽的基本类型,是因为不同的实现结果可能不同。考虑下面表达式:
( uint32_t ) ( u16a + u16b );
在16位机器上,加法将以16位执行,,舍弃高于 16 位的值作为结果,然后转换为 32 位。然而,在32位机器上,加法将在32位进行,并且将保留在16位机器上可能丢失的高阶位。
强制转换为具有相同基本类型的较窄类型是可以接受的,因为结果的显式截断导致相同的信息丢失。
示例:
(uint16_t)(u32a + u32b) /* 合规 */
(uint16_t)(s32a + s32b) /* 违规 - 不同的基本类型 */
(uint16_t)s32a /* 合规 - s32a并非复合表达式 */
(uint32_t)(u16a + u16b) /* 违规 - 强转为更宽的类型 */
不是很理解这个规则,按道理不强制转换会溢出,肯定会导致结果异常