【问题标题】:GCC conversion warning when assigning to a bitfield分配给位域时的 GCC 转换警告
【发布时间】:2014-08-25 06:23:05
【问题描述】:

有什么办法可以抑制gcc在这段代码中产生的警告:

int main() {
    struct flagstruct {
        unsigned flag : 1;
    } a,b,c;

    a.flag = b.flag | c.flag;

    return a.flag;
}

警告是

warning: conversion to 'unsigned char:1' from 'int' may alter its value [-Wconversion]

看起来这两个标志在 ored 在一起时扩展为 int 。 我认为真正奇怪的是,将两个标志中的任何一个都设置为 unsigned 会抑制警告。

a.flag = (unsigned)b.flag | c.flag;

这是编译器错误还是应该以这种方式工作?

【问题讨论】:

  • 请给gcc版本。
  • 选择你最喜欢的。使用-Wconversion 编译时,它们都会生成警告
  • 我希望收到有关使用尚未设置为值的变量(3 个单位定义 a、b、c)的警告。
  • @user3629249 是的,但不是重点

标签: c gcc compiler-warnings suppress-warnings


【解决方案1】:

看起来这两个标志在 ored 在一起时扩展为 int 。

这是整数提升,它在 C99 标准的措辞奇怪的第 6.3.1.1:2 条中定义:

以下内容可以用在表达式中,只要是 int 或 unsigned 可以使用 int:

— _Bool、int、signed int 或 unsigned int 类型的位域。如果 int 可以表示原始类型的所有值,其值为 转换为 int;否则,它将转换为无符号整数。 这些被称为整数促销。所有其他类型不变 通过整数促销。

首先,处理器不直接计算位域,也可能没有指令来计算更窄的整数类型charshort。 C 标准通过仅在 intunsigned int 和更广泛的整数类型上定义算术运算来捕获这一点。上面的标准说“可以使用”,它试图(很差地)表示在参与算术之前必须将所有短类型和位域提升为intunsigned int

其次,所有宽度不足以包含无法表示为int 的值的无符号位字段都将提升为int。 换句话说,GCC 通过将您的无符号位字段提升为有符号的int 来按照标准行事,并且像您所做的那样添加显式强制转换似乎是未来防止意外意外的最佳策略(并且反对警告)。

我认为真正奇怪的是,将这两个标志中的任何一个都设置为 unsigned 会抑制警告。

通常的算术转换是 C 标准(C99 中的 6.3.1.8)中的另一个有趣概念,其结果是,如果两个操作数中的任何一个被显式转换为 unsigned int,那么其他操作数这次也隐式转换为unsigned int| 操作是unsigned int 操作,产生unsigned int 结果。

换句话说,(unsigned)b.flag | c.flag 严格等同于(unsigned)b.flag | (unsigned)c.flag。在这种情况下,编译器认为没有理由对赋值发出警告,因为计算的结果是unsigned int

【讨论】:

  • 感谢您的回答。仍然认为它是一个编译器错误。该标准没有告诉编译器写入何时发出警告。所以编译器可以肯定地知道,赋值中没有精度丢失。赋值 1-bit-bitfield = 32-bit-unsigned;也正在失去精度,并且没有警告......
  • 我刚刚尝试了gcc.godbolt.org 上所有可用的编译器。它们中的大多数都会产生警告,除了 clang>=3.2。 clang-3.0 确实产生了这个警告。所以 clang 似乎同意我的观点,它实际上是一个编译器错误。最奇怪的是所有可用的 gcc/g++ 版本生成的消息:warning: conversion to ‘unsigned char:1’ from ‘unsigned int’ may alter its value [-Wconversion]。将强制转换添加到 unsigned 到第一个标志会删除警告。将 unsigend 转换为 unsigned 会删除警告?!
【解决方案2】:

一年后我修改了问题:

刚刚使用不同的编译器版本再次测试。当我第一次遇到这个错误时(现在我很确定,我可以称它为错误),我已经意识到警告只存在于 clang

由于没有其他方法可以消除错误,唯一的解决方案是更新到 GCC > 5 或添加无符号转换 a.flag = (unsigned)b.flag | c.flag;

【讨论】:

    【解决方案3】:

    解决此警告的最佳方法是明确确认您不需要多余的位:

    a.flag = (b.flag | c.flag) & 0x00000001;

    我目前正在使用 arm-none-eabi-gcc.exe(用于 ARM 嵌入式处理器的 GNU 工具)5.4.1 20160609(发布)[ARM/embedded-5-branch 修订版 237715] ,这是摆脱它们的唯一一致方法。

    无法评论为什么在您发布的情况下显式转换为(unsigned) 可以解决此问题。充其量是奇特的,我怀疑你在其他情况下是否也会如此幸运。

    【讨论】:

    • @M.M 不,我没有。好像也有点奇怪。这只适用于您的位域只有 1 位宽的情况。摆脱使用位域引起的警告并不是一种一致的方法。当然,您必须根据您的位域大小屏蔽您要存储的值,即 & 0x00000003 表示 2 位等。
    • 正如前面所说的那样,这很丑陋且容易出错。如果更改位域成员的位大小,则还必须更改所有掩码(当然可以使用宏或常量,但仍然很难看)。编译器应该足够聪明,可以自己解决。刚刚测试 GCC >= 5.1:警告不再显示 -> 看起来确实是编译器错误
    猜你喜欢
    • 2020-09-04
    • 2022-01-24
    • 1970-01-01
    • 1970-01-01
    • 2010-12-16
    • 2015-12-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多