为什么GCC 4.8.2抱怨除了在严格溢出? [英] Why is GCC 4.8.2 complaining about addition under strict overflow?
问题描述
考虑这个code( bits.c
)
的#include<&ASSERT.H GT;
#包括LT&;&inttypes.h GT;
#包括LT&;&stdio.h中GT;静态uint64_t中pick_bits(无符号字符*字节,为size_t为nbytes,INT LO,INT喜)
{
断言(字节= 0&放大器;!&放大器;为nbytes大于0&放大器;&放大器;为nbytes&下; = 8);
断言(LO> = 0&放大器;&安培; LO< 64);
断言(喜> = 0&放大器;&安培;喜< 64安培;&安培;喜> = LO);
uint64_t中结果为0;
的for(int i =的nbytes - 1; I> = 0;我 - )
结果=(结果<< 8)|字节[I]
结果>> = LO;
结果&安培; =(UINT64_C(1) - ;≤(喜 - LO + 1)) - 1;
返回结果;
}INT主要(无效)
{
unsigned char型D1 [8] =\\ xA5 \\ XB4 \\ XC3 \\ XD2 \\ XE1 \\ XF0 \\ X96 \\的x87
对于(INT U = 0; U< 64; U + = 4)
{
uint64_t中V = pick_bits(D1,的sizeof(D1),U,U + 3);
的printf(采位%2D ..%2D给出了0x%PRIX64\\ n,U,U + 3,V);
}
返回0;
}
在严格的警告(使用GCC 4.8.2一个Ubuntu 12.04衍生建)编译:
$ gcc的-g -O3 -std = C99 -Wall -Wextra -Wmissing的原型-Wstrict的原型\\
> -WOLD式高清-WOLD式声明-Werror bits.c -o位
1:0文件从bits.c包括:
bits.c:在函数'主':
bits.c:9:35:错误:假设签署溢出不会假定当(X + C)出现< X是始终为假[-Werror =严格的溢出]
断言(喜> = 0&放大器;&安培;喜< 64安培;&安培;喜> = LO);
^
CC1:所有的警告被视为错误
我不解:GCC是如何抱怨加法?有没有增加在该行(即使preprocessed)!在preprocessed输出的相关部分是:
#4bits.c2静态uint64_t中pick_bits(无符号字符*字节,为size_t为nbytes,INT LO,INT喜)
{
((字节= 0&放大器;&放大器;为nbytes大于0&放大器;&放大器;为nbytes&下; = 8)(无效)(0):!?!__assert_fail(字节= 0&放大器;&放大器;为nbytes大于0&放大器;&放大器;为nbytes&下; = 8,bits.c,7,__ preTTY_FUNCTION__));
((LO> = 0&放大器;&放大器; LO&下; 64)(无效)(0):__assert_fail(LO> = 0&放大器;&放大器; LO&下; 64,bits.c,8, __ preTTY_FUNCTION__));
((喜> = 0&放大器;&安培;喜< 64安培;&安培;喜> = LO)(无效)(0):__assert_fail(喜> = 0&放大器;&安培;喜< 64安培;&安培;喜> = LO,bits.c,9,__ preTTY_FUNCTION__));
uint64_t中结果为0;
的for(int i =的nbytes - 1; I> = 0;我 - )
结果=(结果<< 8)|字节[I]
结果>> = LO;
结果&安培; =(1UL<<(HI - LO + 1)) - 1;
返回结果;
}
很显然,我可以添加 -Wno严溢
共进晚餐preSS的警告,但我不明白为什么警告被认为是适用于这code摆在首位。
(我注意到,错误的是据说在函数'主':
,但那是因为它能够积极地内联函数的code成主
)
进一步观察
在回答引起了一些意见:
- 出现问题的原因内联的。
- 删除
静态
不足以避免该问题。 - 从单独编译功能
主
和 - 添加
__ __属性((noinline始终))
工作过。 - 使用
-O2
优化避免了这个问题了。
补充问题
这在我看来就像由GCC编译器可疑行为。
- 是否值得海合会队作为一个可能的错误报告?
汇编输出
命令:
$ gcc的-g -O3 -std = C99 -Wall -Wextra -Wmissing的原型-Wstrict的原型\\
> -WOLD式高清-WOLD式声明-Werror -S \\
> -Wno严格溢bits.c
$
汇编(顶部):
.filebits.c
。文本
.Ltext0:
.section伪.rodata.str1.8,AMS,@ PROGBITS,1
.align伪8
.LC0:
.string采位%2D ..%2D给出了0x%LX \\ n
.section伪.text.startup,斧头,@ PROGBITS
.p2align 4日,15
.globl主
.TYPE为主,@function
主要:
.LFB8:
.file 1bits.c
1的.loc 19 0
.cfi_startproc
.LVL0:
pushq%RBP
.cfi_def_cfa_offset 16
.cfi_offset 6,-16
.LBB8:
.LBB9:
1的.loc 23 0
MOVL $ 3%EDX
.LBB10:
.LBB11:
1的.loc 13 0
movabsq $ -8676482779388332891,RBP%
.LBE11:
.LBE10:
.LBE9:
.LBE8:
1的.loc 19 0
pushq%RBX
.cfi_def_cfa_offset 24
.cfi_offset 3,-24
.LBB22:
1的.loc 21 0
xorl%EBX,EBX%
.LBE22:
1的.loc 19 0
SUBQ $ 8%RSP
.cfi_def_cfa_offset 32
JMP .L2
.LVL1:
.p2align 4日,10
.p2align 3
.L3:
莱亚尔3(%RBX),EDX%
.LVL2:
.L2:
.LBB23:
.LBB20:
.LBB16:
.LBB12:
1的.loc 13 0
MOVL%EBX,ECX%
MOVQ%RBP,RAX%
.LBE12:
.LBE16:
1的.loc 24 0
MOVL%EBX,ESI%
.LBB17:
.LBB13:
1的.loc 13 0
SHRQ%CL,RAX%
.LBE13:
.LBE17:
1的.loc 24 0
MOVL $ .LC0,EDI%
.LBE20:
1的.loc 21 0
ADDL $ 4%EBX
.LVL3:
.LBB21:
.LBB18:
.LBB14:
1的.loc 13 0
MOVQ%RAX,RCX%
.LBE14:
.LBE18:
1的.loc 24 0
xorl%EAX,EAX%
.LBB19:
.LBB15:
1的.loc 14 0
和L $ 15%ECX
.LBE15:
.LBE19:
1的.loc 24 0
调用printf
.LVL4:
.LBE21:
1的.loc 21 0
CMPL $ 64%EBX
JNE .L3
.LBE23:
1的.loc 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为主,。,主
。文本
...
它内联函数,然后生成错误。你可以看到自己:
__ __属性((noinline始终))
静态uint64_t中pick_bits(无符号字符*字节,为size_t为nbytes,INT LO,INT喜)
在我的系统,原来的版本生成相同的警告信息,但 noinline始终
版本没有。
然后GCC优化了喜> = LO
,因为它是真正 U + 3> = U
,因为它不够好揣摩那个 U + 3
不溢出会产生警告。一个耻辱。
文档
从GCC文档,第3.8节:
这是假定有符号数溢出不会发生的优化是完全安全的,如果所涉及的变量的值是这样的溢出从不使用,其实发生。 因此这个警告可以很容易地给假阳性:约code,它实际上不是一个问题的警告为帮助聚焦在重要问题上,有几个警告级别的定义。估计循环多少次迭代需要,特别是确定是否一个循环将在所有被执行时,在没有发出警告的使用未定义的符号溢出的。
块引用>着重号。我个人的建议是使用
-Wno错误=严格的溢出
,或者使用的#pragma
禁用在警告违规code。Consider this code (
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; }
When compiled with stringent warnings (using GCC 4.8.2 built for an Ubuntu 12.04 derivative):
$ 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
I'm puzzled: how is GCC complaining about an addition? There are no additions in that line (even when preprocessed)! The relevant section of the preprocessed output is:
# 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; }
Clearly, I can add
-Wno-strict-overflow
to suppress that warning, but I don't understand why the warning is considered to apply to this code in the first place.(I note that the error is reputedly
In function ‘main’:
, but that is because it is able to aggressively inline the code of the function intomain
.)
Further observations
Some observations triggered by the answers:
- The problem occurs because of the inlining.
- Removing the
static
is not sufficient to avoid the problem.- Compiling the function separately from
main
works.- Adding the
__attribute__((noinline))
works too.- Using
-O2
optimization avoids the issue, too.Subsidiary question
This looks to me like dubious behaviour by the GCC compiler.
- Is it worth reporting to the GCC team as a possible bug?
Assembler output
Command:
$ gcc -g -O3 -std=c99 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes \ > -Wold-style-definition -Wold-style-declaration -Werror -S \ > -Wno-strict-overflow bits.c $
Assembler (top section):
.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 ...
解决方案It's inlining the function, and then generating the error. You can see for yourself:
__attribute__((noinline)) static uint64_t pick_bits(unsigned char *bytes, size_t nbytes, int lo, int hi)
On my system, the original version generates the same warning, but the
noinline
version does not.GCC then optimizes out
hi >= lo
, because it's reallyu+3 >= u
, and generates a warning because it's not good enough at figuring out thatu+3
doesn't overflow. A shame.Documentation
From the GCC documentation, section 3.8:
An optimization that assumes that signed overflow does not occur is perfectly safe if the values of the variables involved are such that overflow never does, in fact, occur. Therefore this warning can easily give a false positive: a warning about code that is not actually a problem. To help focus on important issues, several warning levels are defined. No warnings are issued for the use of undefined signed overflow when estimating how many iterations a loop requires, in particular when determining whether a loop will be executed at all.
Emphasis added. My personal recommendation is to use
-Wno-error=strict-overflow
, or to use a#pragma
to disable the warning in the offending code.这篇关于为什么GCC 4.8.2抱怨除了在严格溢出?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!