【问题标题】:Why is GCC 4.8.2 complaining about addition under strict overflow?为什么 GCC 4.8.2 抱怨严格溢出下的加法?
【发布时间】:2014-04-11 18:36:43
【问题描述】:

考虑一下这段代码 (bits.c):

#include <assert.h>
#include <inttypes.h>
#include <stdio.h>

static uint64_t pick_bits(unsigned char *bytes, size_t nbytes, int lo, int hi)
{
  assert(bytes != 0 && nbytes > 0 && nbytes <= 8);
  assert(lo >= 0 && lo < 64);
  assert(hi >= 0 && hi < 64 && hi >= lo);
  uint64_t result = 0;
  for (int i = nbytes - 1; i >= 0; i--)
    result = (result << 8) | bytes[i];
  result >>= lo;
  result &= (UINT64_C(1) << (hi - lo + 1)) - 1;
  return result;
}

int main(void)
{
  unsigned char d1[8] = "\xA5\xB4\xC3\xD2\xE1\xF0\x96\x87";
  for (int u = 0; u < 64; u += 4)
  {
    uint64_t v = pick_bits(d1, sizeof(d1), u, u+3);
    printf("Picking bits %2d..%2d gives 0x%" PRIX64 "\n", u, u+3, v);
  }
  return 0;
}

编译时带有严格警告(使用为 Ubuntu 12.04 衍生版本构建的 GCC 4.8.2):

$ gcc -g -O3 -std=c99 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes \
>     -Wold-style-definition -Wold-style-declaration -Werror  bits.c -o bits
In file included from bits.c:1:0:
bits.c: In function ‘main’:
bits.c:9:35: error: assuming signed overflow does not occur when assuming that (X + c) < X is always false [-Werror=strict-overflow]
   assert(hi >= 0 && hi < 64 && hi >= lo);
                                   ^
cc1: all warnings being treated as errors

我很困惑:GCC 是如何抱怨添加的?该行中没有添加(即使经过预处理)!预处理输出的相关部分是:

# 4 "bits.c" 2

static uint64_t pick_bits(unsigned char *bytes, size_t nbytes, int lo, int hi)
{
  ((bytes != 0 && nbytes > 0 && nbytes <= 8) ? (void) (0) : __assert_fail ("bytes != 0 && nbytes > 0 && nbytes <= 8", "bits.c", 7, __PRETTY_FUNCTION__));
  ((lo >= 0 && lo < 64) ? (void) (0) : __assert_fail ("lo >= 0 && lo < 64", "bits.c", 8, __PRETTY_FUNCTION__));
  ((hi >= 0 && hi < 64 && hi >= lo) ? (void) (0) : __assert_fail ("hi >= 0 && hi < 64 && hi >= lo", "bits.c", 9, __PRETTY_FUNCTION__));
  uint64_t result = 0;
  for (int i = nbytes - 1; i >= 0; i--)
    result = (result << 8) | bytes[i];
  result >>= lo;
  result &= (1UL << (hi - lo + 1)) - 1;
  return result;
}

显然,我可以添加 -Wno-strict-overflow 来禁止该警告,但我不明白为什么该警告首先被认为适用于该代码。

(我注意到错误据称是In function ‘main’:,但那是因为它能够积极地将函数的代码内联到main。)


进一步观察

答案引发的一些观察:

  • 由于内联而出现问题。
  • 删除static 不足以避免该问题。
  • main 单独编译该函数是可行的。
  • 添加__attribute__((noinline)) 也可以。
  • 使用-O2 优化也可以避免该问题。

附属问题

这在我看来像是 GCC 编译器的可疑行为。

  • 是否值得将可能的错误报告给 GCC 团队?

汇编器输出

命令:

$ gcc -g -O3 -std=c99 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes \
> -Wold-style-definition -Wold-style-declaration -Werror -S \
> -Wno-strict-overflow bits.c
$

汇编器(顶部):

    .file   "bits.c"
    .text
.Ltext0:
    .section    .rodata.str1.8,"aMS",@progbits,1
    .align 8
.LC0:
    .string "Picking bits %2d..%2d gives 0x%lX\n"
    .section    .text.startup,"ax",@progbits
    .p2align 4,,15
    .globl  main
    .type   main, @function
main:
.LFB8:
    .file 1 "bits.c"
    .loc 1 19 0
    .cfi_startproc
.LVL0:
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
.LBB8:
.LBB9:
    .loc 1 23 0
    movl    $3, %edx
.LBB10:
.LBB11:
    .loc 1 13 0
    movabsq $-8676482779388332891, %rbp
.LBE11:
.LBE10:
.LBE9:
.LBE8:
    .loc 1 19 0
    pushq   %rbx
    .cfi_def_cfa_offset 24
    .cfi_offset 3, -24
.LBB22:
    .loc 1 21 0
    xorl    %ebx, %ebx
.LBE22:
    .loc 1 19 0
    subq    $8, %rsp
    .cfi_def_cfa_offset 32
    jmp .L2
.LVL1:
    .p2align 4,,10
    .p2align 3
.L3:
    leal    3(%rbx), %edx
.LVL2:
.L2:
.LBB23:
.LBB20:
.LBB16:
.LBB12:
    .loc 1 13 0
    movl    %ebx, %ecx
    movq    %rbp, %rax
.LBE12:
.LBE16:
    .loc 1 24 0
    movl    %ebx, %esi
.LBB17:
.LBB13:
    .loc 1 13 0
    shrq    %cl, %rax
.LBE13:
.LBE17:
    .loc 1 24 0
    movl    $.LC0, %edi
.LBE20:
    .loc 1 21 0
    addl    $4, %ebx
.LVL3:
.LBB21:
.LBB18:
.LBB14:
    .loc 1 13 0
    movq    %rax, %rcx
.LBE14:
.LBE18:
    .loc 1 24 0
    xorl    %eax, %eax
.LBB19:
.LBB15:
    .loc 1 14 0
    andl    $15, %ecx
.LBE15:
.LBE19:
    .loc 1 24 0
    call    printf
.LVL4:
.LBE21:
    .loc 1 21 0
    cmpl    $64, %ebx
    jne .L3
.LBE23:
    .loc 1 27 0
    addq    $8, %rsp
    .cfi_def_cfa_offset 24
    xorl    %eax, %eax
    popq    %rbx
    .cfi_def_cfa_offset 16
.LVL5:
    popq    %rbp
    .cfi_def_cfa_offset 8
    ret
    .cfi_endproc
.LFE8:
    .size   main, .-main
    .text
...

【问题讨论】:

  • 你也可以分享生成的程序集吗?
  • 在 clang 下编译,我没有收到任何警告或错误。
  • “是否值得将可能的错误报告给 GCC 团队?”,您认为什么是错误? GCC 给出了一个错误,因为您要求将所有警告都视为错误。它发出警告是因为它正在生成假定没有发生溢出的代码。如果您不想要它,有一种方法可以禁用该警告。
  • 替代方案:assert(hi &gt;= 0 &amp;&amp; hi &lt; 64); assert(lo &gt;= 0 &amp;&amp; lo &lt;= hi);。我不认为问题出在 u+3 上,而是 2 个断言有 5 个测试,而 4 个可以做。
  • @AProgrammer:我认为错误是 GCC 没有注意到溢出不会发生,因为这些值受到如此限制,即使使用 int8_t 执行算术,添加3 永远不会溢出lo + 3

标签: c gcc


【解决方案1】:

它是内联函数,然后生成错误。你可以自己看看:

__attribute__((noinline))
static uint64_t pick_bits(unsigned char *bytes, size_t nbytes, int lo, int hi)

在我的系统上,原始版本会生成相同的警告,但 noinline 版本不会。

GCC 然后优化出hi &gt;= lo,因为它实际上是u+3 &gt;= u,并生成一个警告,因为它不足以确定u+3 不会溢出。可惜了。

文档

来自 GCC 文档,第 3.8 节:

如果所涉及的变量的值使得溢出实际上永远不会发生,则假设不会发生有符号溢出的优化是完全安全的。 因此,此警告很容易产生误报:关于实际上不是问题的代码的警告。 为了帮助关注重要问题,定义了几个警告级别。在估计循环需要多少次迭代时,特别是在确定是否要执行循环时,不会针对使用未定义的有符号溢出发出警告。

添加了重点。我个人的建议是使用-Wno-error=strict-overflow,或者使用#pragma 来禁用违规代码中的警告。

【讨论】:

  • 删除 static 是不够的。从main 单独编译函数可以工作,添加__attribute__((noinline)) 也可以。使用-O2 优化也可以避免这个问题。附属问题:是否值得将其作为错误报告给 GCC 团队?
  • 最糟糕的情况是他们会告诉您误报是不可避免的,您应该忽略它或将其关闭。该警告通常是误报的事实直接写入 GCC 文档。我倾向于有选择地关闭警告。静态分析很难。
  • 我对两点感到困惑:1.当然通过内联编译器比我看到的更多,但是为什么将警告定位在pick_bits()的断言中,而不是在调用站点加法真的发生了吗? (pick_bits(d1, sizeof(d1), u, u+3);)。 2. pick_bits 的作者不会仅仅因为调用站点的问题而删除明智的断言。为了抑制编译指示错误,需要荒谬的#ifdef GNUC #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wstrict-overflow" source line #pragma GCC diagnostic pop #endif
  • [arg,cmets 中的换行符] 真的吗?这是使用严格溢出来发现实际问题的唯一方法吗?为调用者提出的问题用五行预处理器乱扔代码?
  • @pdbj:未定义溢出一般是一团糟,C 一般是一团糟,在这种杂乱无章的环境中,除非您竭尽全力避免它们,否则不可避免地会收到误报警告。之所以在pick_bits内部产生警告,是因为是pick_bits中的代码触发了错误:hi &gt;= lo是触发代码。定义hi = lo + 3 是上下文的一部分。我认为如果你想完全避免#ifdef,你将不得不放弃 C 或可移植性,除非你的程序很简单。
【解决方案2】:

我的假设是 gcc 内联 pick_bits 并因此在编译时知道 hi == lo+3 允许它假设 hi &gt;= lo 始终为真,只要 lo 足够低以至于 lo+3 不会溢出.

【讨论】:

  • +1,这也是我的直觉。我猜-O3 中包含了激进的内联。
  • 从技术上讲,可以假设 lo+3 不会溢出,因为 C 标准是这样说的。即使lo = INT_MAX.
  • @DietrichEpp,同意,gcc 可能会警告代码假定没有溢出,并且用户要求 gcc 将所有警告视为错误。
猜你喜欢
  • 2016-02-27
  • 1970-01-01
  • 2012-05-27
  • 1970-01-01
  • 2013-05-08
  • 1970-01-01
  • 2022-10-25
  • 1970-01-01
相关资源
最近更新 更多