g++ 优化了发布版本中对 INT_MIN 的检查

问题描述 投票:0回答:1

我遇到了一个问题,g++ 优化了一些它不应该有的东西。我将问题简化为以下示例: 我有一个带有函数

bool my_magic_function(int* x)
的静态库,如果可以的话,它将
x
减 1,否则(
x == INT_MIN
),它返回
false
并且不触及原始值。 如果我在调试版本中使用该函数,那么它会按预期工作。但在发布版本中,检查被优化掉了。平台:

在 RHEL 9.3 上使用 g++ (GCC) 11.4.1 20230605 -> 存在问题

Ubuntu 22.04 g++ 11.4.0 g++ 或 10.5.0 g++ -> 存在问题

Ubuntu 22.04 g++ 9.5.0 -> 代码在发布时也按预期工作。

这是一个带有静态库和使用该函数的简单 main.cpp 的最小示例:

almalib.h:

bool my_magic_function(int* x); 

almalib.cc:

#include "almalib.h"
#include <cstring>
#include <limits>

bool my_magic_function(int* x) {
    int cp_new;
    // integer overflow is undefined, so lets make sure it becomes int max
    if (*x == std::numeric_limits<int>::lowest()) {
        cp_new = std::numeric_limits<int>::max(); 
    } else {
        cp_new = *x - 1;
    }  
    if (cp_new < *x) {
        *x = cp_new;
        return true;    
    }
    return false;
} 

主.cpp

#include "almalib.h"
#include <iostream>
#include <limits>

int main()
{
    for (int x : {0, std::numeric_limits<int>::lowest()})
    {
        int x2 = x;
        std::cerr << "Res for " << x << " " << (my_magic_function(&x2) ? "OK" : "NOT_OK") << " val: " << x2 << std::endl;
    }
}

编译:

g++ -c almalib.cc -o almalib.o
ar crf libalma.a almalib.o
g++ main.cpp -o run -L. -lalma

g++ -c almalib.cc -O3 -o almalibR.o
ar crf libalmaR.a almalibR.o
g++ main.cpp -O3 -o runR -L. -lalmaR

调试输出(./run):

Res for 0 OK val: -1
Res for -2147483648 NOT_OK val: -2147483648

发布的输出(./runR):

Res for 0 OK val: -1
Res for -2147483648 OK val: 2147483647

用gdb检查生成的程序集,

my_magic_function
减少到3行:

0x401320 <_Z17my_magic_functionPi>      subl   $0x1,(%rdi)                                                                                                                                                                                          
0x401323 <_Z17my_magic_functionPi+3>    mov    $0x1,%eax                                                                                                                                                                                                     
0x401328 <_Z17my_magic_functionPi+8>    ret               

我的问题是:

  • 这是一个已知问题吗?
  • 我可以采取哪些措施来防止这种情况发生? (我可以简单地重写示例函数,但不能重写原始问题)。是否有任何编译器提示,或者我应该禁用某种优化类型?
c++ optimization g++
1个回答
0
投票

这些可能很昂贵,但是

-fwrapv
-ftrapv
都能让您的问题消失。

-fwrapv
意味着编译器假定有符号整数的行为与无符号整数类似并且环绕。您的硬件几乎肯定会这样做。
-ftrapv
意味着它会在有符号整数环绕时添加陷阱(异常)(您可能可以在硬件上设置标志来实现这种情况,如果没有,它会添加逻辑来捕获它)。

使用任一标志,您的代码都会正确运行。

虽然

-fwrapv
看起来无害,但这意味着无法完成循环和比较中的一堆优化。

如果没有

-fwrapv
,编译器可以假定同时大于 0 的
a+b
大于
a
且大于
b
。有了它,就不能了。

据猜测,您的编译器首先采用早期分支代码

if (*x == std::numeric_limits<int>::lowest()) {
    cp_new = std::numeric_limits<int>::max(); 
} else {
    cp_new = *x - 1;
}

并说“在硬件目标上,这相当于”

cp_new = *x - 1;

因为它知道硬件目标已签署了回绕的下溢。重大优化,消除不必要的分支!

然后它会看

if (cp_new < *x) {
    *x = cp_new;
    return true;    
}

然后替换 cp_new:

if ((*x - 1)< *x) {
    *x = (*x - 1);
    return true;    
}

原因是“嗯,有符号下溢是未定义的行为,所以负 1 总是小于某个值”。从而优化为:

*x = *x-1;
return true;    

错误在于它在下溢是

定义
并且首先环绕的上下文中使用了cp_new = *x - 1,然后在不允许环绕的情况下重新使用它。

通过使下溢导致陷阱使其假设为真,我们阻止了让它进行第二次错误优化的假设。

但是这个故事 - 为什么

fwrapv
/
ftrapv
有效 - 是一个“就是这样的故事”,它并不是通过实际阅读 gcc 代码或错误报告来了解的。

© www.soinside.com 2019 - 2024. All rights reserved.