使用类型转换删除 gcc 编译器警告
Using typecasting to remove gcc compiler warnings
我正在使用 gcc 4.9 进行嵌入式 ARM 编程。我一直在使用 -Wconversion
开关,因为它在我公司的默认开发工具配置中。我正在使用 stdint.h types(uint8_t、uint32_t 等)。
每次我执行复合赋值甚至简单加法时,编译器都会发出警告。例如:
uint8_t u8 = 0;
uint16_t u16;
// These cause warnings:
u8 += 2;
u8 = u16 >> 8;
解决这个问题的 "common method" 是使用强制转换,正如所讨论的 here and here:
u8 = (uint8_t)(u8 + 2);
u8 = (uint8_t)(u16 >> 8);
除了这丑陋之外,我还 运行 提供令人信服的证据,证明转换通常是不好的做法。
我的问题:
- 为什么以这种方式使用类型转换不好?
- 如果简单地省略
-Wconversion
并让编译器为我进行隐式转换,我是否会失去任何东西?
这是一个品味问题。当您将算术类型的值分配给不同算术类型的对象时,C 永远不需要强制转换。有些人使用强制转换来记录隐藏的转换,并且他们是故意这样做的。
就我个人而言,我不喜欢放置额外无用的东西,我假设 reader 知道 C 的基础知识,所以我倾向于避免强制转换(而且我不使用 -Wconversion
不是 -Wall
甚至 -Wextra
的一部分)。请注意,在赋值中,如果右侧的文字无法放入左侧对象 gcc
通常会发出警告(没有 -Wall
或 -Wextra
),即使赋值不是 UB。
转换的问题在于它们会告诉编译器 "shut up, I know what I'm doing",即使您没有这样做。如果有人决定变量必须能够处理大于 255 的值并将类型更改为 uint16_t,u8 += 2 会起作用,但是 u8 = (uint8_t)(u8 + 2) 会破碎的。例如,假设变量的名称不是 u8,而是 "numberOfParagraphs",这可能是一个很难找到的错误。
最好首先使用更大的类型。除非你真的非常想存储 (u8 + 2) & 0xff,在这种情况下你可以这样写并将它存储到一个更大的变量中而不会出现问题。
(就我个人而言,我想要像
这样的语言的扩展
(uint8_t)u8 += 2;
将左值转换为它自己的类型的语义没有任何效果,并且在删除警告时它仍然是左值,但是将左值转换为不同的类型将是一个错误。这样可以安全地关闭编译器警告)
编译器向您展示了潜在的问题点,您应该考虑更改,而不是强制转换。您所拥有的代码中的问题是,在 C 中,算术永远不会在比 int
窄的类型上完成:编译器总是为它们执行到 int
的隐式转换。
如此有效,您的代码将窄无符号类型转换为 int
,执行操作,然后将它们转换回无符号类型。
通常使用窄类型进行算术运算并不是一个好主意。只有在存储大小有问题时才使用它们(通常是更大的结构,例如数组)。对于局部的、或多或少的临时变量,这些窄类型没有意义。
我正在使用 gcc 4.9 进行嵌入式 ARM 编程。我一直在使用 -Wconversion
开关,因为它在我公司的默认开发工具配置中。我正在使用 stdint.h types(uint8_t、uint32_t 等)。
每次我执行复合赋值甚至简单加法时,编译器都会发出警告。例如:
uint8_t u8 = 0;
uint16_t u16;
// These cause warnings:
u8 += 2;
u8 = u16 >> 8;
解决这个问题的 "common method" 是使用强制转换,正如所讨论的 here and here:
u8 = (uint8_t)(u8 + 2);
u8 = (uint8_t)(u16 >> 8);
除了这丑陋之外,我还 运行 提供令人信服的证据,证明转换通常是不好的做法。
我的问题:
- 为什么以这种方式使用类型转换不好?
- 如果简单地省略
-Wconversion
并让编译器为我进行隐式转换,我是否会失去任何东西?
这是一个品味问题。当您将算术类型的值分配给不同算术类型的对象时,C 永远不需要强制转换。有些人使用强制转换来记录隐藏的转换,并且他们是故意这样做的。
就我个人而言,我不喜欢放置额外无用的东西,我假设 reader 知道 C 的基础知识,所以我倾向于避免强制转换(而且我不使用 -Wconversion
不是 -Wall
甚至 -Wextra
的一部分)。请注意,在赋值中,如果右侧的文字无法放入左侧对象 gcc
通常会发出警告(没有 -Wall
或 -Wextra
),即使赋值不是 UB。
转换的问题在于它们会告诉编译器 "shut up, I know what I'm doing",即使您没有这样做。如果有人决定变量必须能够处理大于 255 的值并将类型更改为 uint16_t,u8 += 2 会起作用,但是 u8 = (uint8_t)(u8 + 2) 会破碎的。例如,假设变量的名称不是 u8,而是 "numberOfParagraphs",这可能是一个很难找到的错误。
最好首先使用更大的类型。除非你真的非常想存储 (u8 + 2) & 0xff,在这种情况下你可以这样写并将它存储到一个更大的变量中而不会出现问题。
(就我个人而言,我想要像
这样的语言的扩展(uint8_t)u8 += 2;
将左值转换为它自己的类型的语义没有任何效果,并且在删除警告时它仍然是左值,但是将左值转换为不同的类型将是一个错误。这样可以安全地关闭编译器警告)
编译器向您展示了潜在的问题点,您应该考虑更改,而不是强制转换。您所拥有的代码中的问题是,在 C 中,算术永远不会在比 int
窄的类型上完成:编译器总是为它们执行到 int
的隐式转换。
如此有效,您的代码将窄无符号类型转换为 int
,执行操作,然后将它们转换回无符号类型。
通常使用窄类型进行算术运算并不是一个好主意。只有在存储大小有问题时才使用它们(通常是更大的结构,例如数组)。对于局部的、或多或少的临时变量,这些窄类型没有意义。