无法绕过gcc的-Wconversion

10
int main() {

    struct { unsigned int a:20; } s;
    unsigned int val = 0xaabbc000;

    s.a = val & 0xfffff;         // 1) works
    s.a = (val >> 12) & 0xfffff; // 2) generates -Wconversion warning
}

我正在使用-Wconversion编译一个项目,但遇到了一种情况,即无法让编译器相信我已经完成了转换。

  • In case 1, I'm using the same solution proposed in c++ bit fields and -Wconversion and it works great. It forces the compiler to accept the conversion because of the bitmask which limits value's range.

  • In case 2, however, because of the shift (but why?) the compiler refuses to accept the conversion. And complains in the following way:

    $ gcc wconv.c -Wconversion -Werror
    wconv.c: In function ‘main’:
    wconv.c:8:11: error: conversion to ‘unsigned int:20’ from ‘unsigned int’ may alter its value [-Werror=conversion]
     s.a = (val >> 12) & 0xfffff; // 2) generates -Wconversion warning
           ^
    cc1: all warnings being treated as errors
    

    (Interesting note: with clang the code compiles without issues. I've observed so far that clang's -Wconversion is much less stricter than GCC's.)

问题:

  • 我该如何说服GCC编译第二种情况?
  • 同时,为什么右移操作会改变所有内容呢?在我的理解中,对于一个unsigned int类型的表达式,位移操作不应该改变它的类型。
  • 最后,这可能是编译器的bug吗?

注[1]:此问题与c++ bit fields and -Wconversion不重复,因为那里提出的解决方案在我的情况下根本不起作用。

注[2]:此问题与Why >>24 causes -Wconversion but >>23 doesn't?不重复,因为它涉及到一个不同的bug(或者是相同核心bug的不同表现),并且有一个简单的解决方法,就像c++ bit fields and -Wconversion中提出的那样,至少在GCC 7.3中可以使用强制转换来解决。


3
有趣的问题...看起来gcc的逻辑中存在一些 off-by-one 错误;掩码 0x7ffff 可以正常工作且不会产生警告 ;) 即使只移动了11位也是如此。 - Ctx
1
@KrzysztofSzewczyk 这个警告并不合适,因为转换由于之前应用的掩码而无法真正改变值。 - Ctx
这似乎是由于相同的错误引起的,就像为什么>>24会导致-Wconversion而>>23不会?。这与@Ctx的观察一致,即当掩码或移位调整一个位时,警告不会发生。 - ShadowRanger
可能是为什么>>24会导致-Wconversion而>>23不会?的重复问题。 - ShadowRanger
2个回答

4
我刚刚发现在GCC的错误跟踪器中,有几个与-Wconversion相关的错误。具体来说:https://gcc.gnu.org/bugzilla/show_bug.cgi?id=39170
特别是,评论#18 (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=39170#c18)报告了一个几乎与我的例子相同的例子。
#include <stdint.h>

struct foo
{
   unsigned bar: 30;
   unsigned fill: 2;
};

struct foo test(uint32_t value)
{
   struct foo foo;

   foo.bar = (value >> 2) & 0x3fffffffU;

   return foo;
}

因此,我认为这个问题明显是一个gcc的错误。
个人解决方案
考虑到编译器的错误,我的个人解决方案是将右移操作包装在一个静态always_inline函数中,即使我对这种hack并不是特别满意。
#include <stdint.h>

static __attribute__((always_inline)) inline uintptr_t
rshift(uintptr_t val, uintptr_t bits)
{
   return val >> bits;
}

int main() {

    struct { unsigned int a:20; } s;
    unsigned int val = 0xaabbc000;

    s.a = val & 0xfffff;                // 1) works
    s.a = (rshift(val, 12)) & 0xfffff;  // 2) works
}

PSkocik建议的解决方法

   s.a = (unsigned){(val >> 12)} & 0xfffff; // works

目前为止,这是我最喜欢的了。


3
一个解决方法是使用一个临时变量。虽然不太理想,但可以消除警告。
const unsigned t = val >> 12u;
s.a = t & 0xfffffu;

除此之外,您还可以明确地关闭该行的警告:

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wconversion"
    s.a = (val  >> 12u) & 0xfffffu;
#pragma GCC diagnostic pop

1
一个复合字面量也可以工作s.a = (unsigned){(val >> 12)} & 0xfffff; - Petr Skocik
@bolov,你的解决方案确实可行,但我正在寻找对代码影响最小的方法。在某些情况下,使用临时变量可能非常丑陋/侵入性。#pragma甚至更糟。无论如何,还是谢谢你的回答。 - vvaltchev
@PSkocik,我在我的回答中提到了你的聪明解决方法(希望这没问题)。如果你用这个解决方法发表自己的答案,当然,我会从我的答案中删除它并投票支持你的答案。 - vvaltchev
@vvaltchev 没什么太厉害的,只是知道复合字面量本质上是匿名变量而已。 :) - Petr Skocik

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接