【问题标题】:How to tell compiler the "range" of a #define (-Wtype-limits warning)如何告诉编译器#define 的“范围”(-Wtype-limits 警告)
【发布时间】:2021-10-25 11:49:18
【问题描述】:

在我的代码中,我需要在[MIN, MAX] 范围内使变量uint32_t var 饱和。 MINMAX 被定义为宏,因此我可以在头文件中轻松更改这些值。

标题:

#define MIN    (0)
#define MAX    (1000)

代码:

if(var > MAX){
    var = MAX;
}else if(var < MIN){
    var = MIN;
}

当我现在使用编译器标志 -Wtype-limits 时,我收到一条警告,第二次检查 var &lt; MIN 始终为假。此警告表明我可以删除第二个比较。

只要我定义了#define MIN (0),这就是有道理的,但是,当将其更改为#define MIN (10) 时,第二个比较是强制性的。

所以我的问题是:我如何告诉编译器 MIN 可以是大于或等于零的任何值?

【问题讨论】:

  • 使用#UNDEF 符号?而不是编译器,而是生成翻译单元的 CPP……

标签: c limit compiler-warnings


【解决方案1】:

最简单的解决方案就是use &lt;= instead of &lt;

if (var >= MAX) {
    var = MAX;
} else if (var <= MIN) {
    var = MIN;
}

但你应该尽可能避免使用宏,而只声明常量。除了在使用无符号值时始终使用U 后缀以避免有符号无符号不兼容警告。实际上很有趣的是,如果我像这样使用#define,我会得到warning in GCC but not Clang,当我更改为const 并添加U 后缀时,我会得到no warning with GCC but one with Clang

【讨论】:

  • 我也想过用&lt;=代替&lt;。唯一的“缺点”是,我将 0 重新分配给 var,即使它已经为零。这“需要时间”,我想让中断处理程序尽可能短。当然,损失的时间不会那么多......
  • @paul_schaefer 在这种情况下,优化编译器没有区别。只需检查编译器输出。例如,如果 MIN = 10,那么它只会在 var
  • @paul_schaefer Gcc 似乎已经足够聪明,不会为else if (unsigned_var &lt;= 0) unsigned_var=0; 生成 else if 分支,var &lt;= MIN &amp;&amp; var!=MIN 可以帮助 clang。见godbolt.org/z/YehPWx64Y
  • @phuclv:关于您答案中的编译器示例。在输出第 20 行有与值 10 的比较,在第 21 行写入命令ja。据我了解,这意味着“如果高于则跳转”,因此仅在 eax 中的值大于 10 时才执行跳转。因此,如果 eax 中的值为 10,则不执行跳转,将 10 写回。所以恕我直言,写作没有被跳过,还是我误解了什么?
  • @paul_schaefer 你是对的,这可能是 GCC 错过的优化。当 var = 10 或 1000 godbolt.org/z/nhcfqK846 时,ICC 可以避免该分配,但它也更喜欢无分支解决方案,因此对于任何 var 值,结果都是相同的。无论如何,这是微优化,除非你看到它真的是热点,否则不应该担心。始终先使用 PGO 和配置文件进行编译
【解决方案2】:

这解决了问题:

int main(void) {
    unsigned int var = rand();

    if (var > MAX){
        var = MAX;
    }
#if MIN > 0
    else if (var < MIN) {
        var = MIN;
    }
#endif

    printf("token = %u\n", var);
}

编辑:通过明确这两种情况来减少“肮脏”。

uint32_t saturate(uint32_t var)
{
#if MIN > 0
    if (var > MAX){
        var = MAX;
    }
    else if (var < MIN) {
        var = MIN;
    }
#else
    // Only need to do the upper bound
    if (var > MAX){
        var = MAX;
    }
#endif
    return var;
}

【讨论】:

  • 我也有这个想法,这是我目前的解决方案。但我不确定是否有更好的方法。
  • 这是一个相当肮脏的 hack,我希望看到很多 cmets 解释它为什么存在。总体而言不是一个好主意。
  • 为什么这是一个肮脏的黑客?您只想在有意义的时候进行测试。
  • 代码覆盖测试有点脏。
  • 这使得函数的可读性大大降低
【解决方案3】:

我如何告诉编译器,MIN 可以是大于或等于零的任何值?

你不能,你只是告诉它它正好是零。

此警告来自编译器在编译时评估整数常量表达式的能力,因此它会看到 var &lt; 0 并发现 var 是无符号类型。

我们可以通过使用变量来防止这种情况并消除警告,因此它不再是整数常量表达式:

const unsigned int MIN = 0;

或者,如果您出于某种原因必须使用宏,请使用复合文字:

#define MIN    (const unsigned int){0}

现在当然不要在没有 cmets 的情况下编写这样奇怪的东西,所以正确的生产代码例如是:

// MIN is using compound literal to block unsigned vs 0 comparison warnings in gcc 1.2.3
#define MIN (const unsigned int){0} 

【讨论】:

  • IIRC,如果没有 static 说明符,在 标题 中添加的 const unsigned int MIN = 0; 行将破坏“一个定义规则”,如果该标题包含在多个来源中文件。
  • @AdrianMole 是的,不要在标题中定义变量。如果它需要在标题中,则执行 extern const unsigned int MIN; 并将定义放入 .c 文件中。那么复合文字版本可能更方便。
猜你喜欢
  • 2013-04-18
  • 1970-01-01
  • 2011-05-09
  • 1970-01-01
  • 1970-01-01
  • 2020-10-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多