函数的内联版本与非内联版本返回的值不同

时间:2019-04-09 10:11:27

标签: c++

同一个函数的两个版本如何不同(仅一个不同是内联而另一个不相同)返回不同的值?这是我今天编写的一些代码,我不确定它如何工作。

date

我希望所有输出都等于input,但实际上它是输出的(g ++ 8.3.1,没有标志):

#include <cmath>
#include <iostream>

bool is_cube(double r)
{
    return floor(cbrt(r)) == cbrt(r);
}

bool inline is_cube_inline(double r)
{
    return floor(cbrt(r)) == cbrt(r);
}

int main()
{
    std::cout << (floor(cbrt(27.0)) == cbrt(27.0)) << std::endl;
    std::cout << (is_cube(27.0)) << std::endl;
    std::cout << (is_cube_inline(27.0)) << std::endl;
}

代替

1

编辑:clang ++ 7.0.0输出以下内容:

1
0
1

和g ++-快点:

1
1
1

2 个答案:

答案 0 :(得分:70)

说明

某些编译器(尤其是GCC)在编译时评估表达式时使用更高的精度。如果表达式仅依赖于常量输入和文字,则即使未将表达式分配给constexpr变量,也可以在编译时对其求值。是否发生这种情况取决于:

  • 表达式的复杂性
  • 编译器在尝试执行编译时评估时使用的阈值
  • 在特殊情况下(例如当clang elides循环时)使用的其他启发式方法

如果像第一种情况那样显式提供表达式,则它的复杂度较低,并且编译器可能会在编译时对其进行求值。

同样,如果将函数标记为内联,则编译器更有可能在编译时对其进行评估,因为内联函数会提高评估发生的阈值。

较高的优化级别也会增加此阈值,如-Ofast示例中那样,由于高精度的编译时评估,所有表达式在gcc上均评估为true。

我们可以在编译器资源管理器中观察此行为。用-O1编译时,仅在编译时评估标记为inline的函数,而在-O3时,在编译时评估两个函数。

NB:在编译器探索器示例中,我使用printf代替iostream,因为它降低了main函数的复杂性,从而使效果更加明显。

证明inline不会影响运行时评估

我们可以通过从标准输入中获取值来确保在编译时不对任何表达式求值,并且当我们这样做时,所有3个表达式都返回false,如下所示:https://ideone.com/QZbv6X

#include <cmath>
#include <iostream>

bool is_cube(double r)
{
    return floor(cbrt(r)) == cbrt(r);
}
 
bool inline is_cube_inline(double r)
{
    return floor(cbrt(r)) == cbrt(r);
}

int main()
{
    double value;
    std::cin >> value;
    std::cout << (floor(cbrt(value)) == cbrt(value)) << std::endl; // false
    std::cout << (is_cube(value)) << std::endl; // false
    std::cout << (is_cube_inline(value)) << std::endl; // false
}

this example进行对比,在这里我们使用相同的编译器设置,但在编译时提供值,从而可以进行更高精度的编译时评估。

答案 1 :(得分:21)

正如观察到的那样,使用==运算符比较浮点值已导致使用不同的编译器和不同的优化级别获得不同的输出。

比较浮点值的一种好方法是文章Floating-point tolerances revisited中概述的 相对公差 测试。

我们首先计算Epsilon(相对公差)值,在这种情况下为:

double Epsilon = std::max(std::cbrt(r), std::floor(std::cbrt(r))) * std::numeric_limits<double>::epsilon();

然后以这种方式在内联和非内联函数中使用它:

return (std::fabs(std::floor(std::cbrt(r)) - std::cbrt(r)) < Epsilon);

现在的功能是:

bool is_cube(double r)
{
    double Epsilon = std::max(std::cbrt(r), std::floor(std::cbrt(r))) * std::numeric_limits<double>::epsilon();    
    return (std::fabs(std::floor(std::cbrt(r)) - std::cbrt(r)) < Epsilon);
}

bool inline is_cube_inline(double r)
{
    double Epsilon = std::max(std::cbrt(r), std::floor(std::cbrt(r))) * std::numeric_limits<double>::epsilon();
    return (std::fabs(std::round(std::cbrt(r)) - std::cbrt(r)) < Epsilon);
}

现在,使用不同的编译器和不同的优化级别,输出将按预期([1 1 1])。

Live demo

相关问题