为什么GCC 4.8.2抱怨在严格溢出下添加?

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

 #include  #include  #include  static uint64_t pick_bits(unsigned char *bytes, size_t nbytes, int lo, int hi) { assert(bytes != 0 && nbytes > 0 && nbytes = 0 && lo = 0 && hi = lo); uint64_t result = 0; for (int i = nbytes - 1; i >= 0; i--) result = (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) = 0 && hi = lo); ^ cc1: all warnings being treated as errors 

我很困惑:海湾合作委员会如何抱怨增加? 该行中没有添加内容(即使在预处理时)! 预处理输出的相关部分是:

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

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

(我注意到这个错误据说是In function 'main':但是这是因为它能够将函数的代码强制内联到main 。)


进一步观察

答案引发了一些观察:

  • 出现问题是因为内联。
  • 移除static不足以避免此问题。
  • main工作分开编译function。
  • 添加__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 ... 

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

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

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

GCC然后优化hi >= lo ,因为它确实是u+3 >= u ,并且产生警告,因为它不足以确定u+3不会溢出。 丢人现眼。

文档

从海湾合作委员会文件第3.8节:

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

强调补充说。 我个人的建议是使用-Wno-error=strict-overflow ,或者使用#pragma来禁用违规代码中的警告。

我的假设是gcc正在内联pick_bits,因此在知道hi == lo+3编译,这允许它假设hi >= lo总是为真,只要lo足够低, lo+3不会溢出。