【问题标题】:Different output with different optimization levels in GCCGCC中不同优化级别的不同输出
【发布时间】:2020-07-06 00:32:48
【问题描述】:

我正在重写上学期为大学开发的光线追踪器,但遇到了以下问题: 当我在 Debug 中编译并运行我的代码时,输​​出符合预期

但是当我启用更高的优化级别时,例如"-O2" 结果完全不同:

而且我不确定为什么会发生这种情况。我追踪到了球体相交代码

//#pragma GCC push_options
//#pragma GCC optimize("O0")

Intersection Sphere::intersect(const Ray& ray, const float previous) const
{
    const auto oc = ray.origin - center_;
    const auto lhv = -dot(ray.direction, oc);
    const auto discriminant = lhv * lhv - (oc.lensqr() - radius_ * radius_);

    if (discriminant < 0.0F)
    {
        return Intersection::failure();
    }
    float distance;
    const auto rhv = std::sqrt(discriminant);
    const auto r = std::minmax(lhv + rhv, lhv - rhv);
    if (r.first <= 0.0F)
    {
        if (r.second <= 0.0F)
        {
            return Intersection::failure();
        }
        distance = r.second;
    }
    else
    {
        distance = r.first;
    }

    const auto hit = ray.getPoint(distance);
    const auto normal = (hit - center_).normalize();

    if (0.0F <= distance && distance < previous - epsilon)
    {
        return {distance, ray, this, normal, hit};
    }
    return Intersection::failure();
}

//#pragma GCC pop_options

如果我在发布模式下取消注释,我会再次得到预期的结果。也许我的代码中有一些未定义的行为会导致这种情况?

您也可以在这里查看,因为一个最小的可重现示例并不容易。 https://github.com/Yamahari/RayTracer/blob/master/rt/solid/Sphere.cpp

(你也可以clone repo并用cmake构建项目,你只需要SFML作为依赖。

将 -DSFML_INCLUDE_DIR="include_dir" 和 -DSFML_LIB_DIR="lib_dir" 与使用所需编译器编译的 sfml 库一起使用)

【问题讨论】:

  • 也许我的代码中有一些未定义的行为会导致这种情况? 99.44% 的可能性。您是否尝试过打开所有编译器警告?
  • 是的,我正在使用'-Wall -Wextra -Wpedantic',我得到的唯一警告是未使用的参数和非标准 gcc 扩展,但使用该扩展的代码未用于例子
  • 我会使用-O3,然后禁用所有可禁用的警告-fno-blahblah,然后分批打开它们,直到您可以隔离特定的优化。一旦你找到了优化的罪魁祸首,你就可以深入了解代码绊倒了哪些未定义的行为。虽然,并非所有优化都有明确的编译器标志来启用/禁用。提供代码 sn -p 不是minimal reproducible example
  • 你能用调试器逐步检查可疑代码吗?一次调试,一次发布
  • Intersection 是否有任何引用成员,或者构造函数是否复制了它获得的所有参数?

标签: gcc g++ c++17 sfml


【解决方案1】:

std::minmax 返回一对对其参数的引用。如果使用纯右值作为参数调用它,则这对中的引用将在完整表达式结束后悬空,这意味着访问r.first in

if (r.first <= 0.0F)

这里会有未定义的行为。

所以将lhv + rhvlhv - rhv 存储在变量中并在它们上调用std::minmax 或使用

std::minmax({lhv + rhv, lhv - rhv})

选择std::initializer_list重载,它返回一对实际值。


正如 @Jarod42 在 cmets 中所指出的,您实际上并不需要 std::minmaxrhvstd::sqrt 调用的结果,因此它始终为非负数,使 lhv + rhv 始终为更大(或相等)的值。

【讨论】:

  • @Yamahari 是的,这不是一个好的设计选择。我认为std::minmax 应该有另一个重载,如果任一参数是右值,则按值返回,或者如果参数是右值,则第一个重载应该是 SFINAEd。你最好自己写一个这样的版本,这样就不会再发生了。
  • const auto [min, max] = std::minmax(lhv + rhv, lhv - rhv); 会解决这个问题吗? minmax 不是立即解包返回的值(这应该仍然有效,因为执行没有到达创建临时变量的完整表达式的末尾)?鉴于auto保留引用,我认为它可以通过复制值来工作。
  • @Fureeish 不,结构化绑定仅引用它们绑定到的对象的成员。不会为各个成员创建新对象。所以const auto [min, max] 将实现一个const std::pair&lt;const T&amp;, const T&amp;&gt;,它一直存在到范围结束,minmax 将引用这个pairsfirstsecond 成员,它们是(此后悬空的)引用.
  • 该死,不幸。不过,谢谢!
  • 因为rhvsqrt 的结果所以“积极”,你可以直接做const auto [min, max] = std::pair(lhv - rhv, lhv + rhv);
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-04-27
  • 1970-01-01
  • 2020-12-11
  • 2010-12-19
  • 1970-01-01
  • 2012-01-04
相关资源
最近更新 更多