为什么VisualStudio在没有优化的情况下可以正确编译此函数,但在优化时却编译错误?

我正在做一些类似 y 组合器的 lambda 包装的实验(尽管我知道它们实际上并不是严格意义上的 y 组合器),但我遇到了一个非常奇怪的问题。我的代码在调试配置中完全按照我的预期运行(关闭优化),但跳过了发布中的大(而且很重要!)位(设置为Optimizations (Favor Speed) (/Ox))。

请注意,lambda 函数的内部基本上无关紧要,它们只是为了确保它可以正确递归等。

// main.cpp
#include <iostream>
#include <string>
#define uint unsigned int

// Defines a y-combinator-style thing to do recursive things. Includes a system where the lambda can declare itself to be obsolete.
// Yes, it's hacky and ugly. Don't worry about it, this is all just testing functionality.
template <class F>
class YCombinator {
public:
    F m_f; // the lambda will be stored here
    bool m_selfDestructing = false; //!< Whether the combinator will self-destruct should its lambda mark itself as no longer useful.
    bool m_selfDestructTrigger = false; //!< Whether the combinator's lambda has marked itself as no longer useful.

    // a forwarding operator:
    template <class... Args>
    decltype(auto) evaluate(Args&&... args) {
        // Avoid storing return if we can, 
        if (!m_selfDestructing) {
            // Pass itself to m_f, then the arguments.
            return m_f(*this, std::forward<Args>(args)...);
        }
        else {
            // Pass itself to m_f, then the arguments.
            auto r = m_f(*this, std::forward<Args>(args)...);
            // self-destruct if necessary, allowing lamdas to delete themselves if they know they're no longer useful.
            if (m_selfDestructTrigger) {
                delete this;
            }
            return r;
        }
    }
};
template <class F> YCombinator(F, bool sd)->YCombinator<F>;

// Tests some instances.
int main() {
    // Most basic test
    auto a = YCombinator{
        [](auto & self, uint in)->uint{
            uint out = in;
            for (uint i = 1u; i < in; ++i) {
                out += self.evaluate(i);
            }
            return out;
        },
        false
    };

    // Same as a, but checks it works as a pointer.
    auto b = new YCombinator{
        [](auto & self, uint in)->uint {
            uint out = in;
            for (uint i = 0u; i < in; ++i) {
                out += self.evaluate(i);
            }

            return out;
        },
        false
    };

    // c elided for simplicity

    // Checks the self-deletion mechanism
    auto d = new YCombinator{
        [&a, b](auto & self, uint in)->uint {
            std::cout << "Running d(" << in << ") [SD-" << self.m_selfDestructing << "]..." << std::endl;

            uint outA = a.evaluate(in);
            uint outB = b->evaluate(in);

            if (outA == outB)
                std::cout << "d(" << in << ") [SD-" << self.m_selfDestructing << "] confirmed both a and b produced the same output of " << outA << "." << std::endl;

            self.m_selfDestructTrigger = true;

            return outA;
        },
        true
    };

    uint resultA = a.evaluate(4u);
    std::cout << "Final result: a(4) = " << resultA << "." << std::endl << std::endl;

    uint resultB = (*b).evaluate(5u);
    std::cout << "Final result: b(5) = " << resultB << "." << std::endl << std::endl;

    uint resultD = d->evaluate(2u);
    std::cout << "Final result: d(2) = " << resultD << "." << std::endl << std::endl;

    resultD = d->evaluate(2u);
    std::cout << "Final result: d(2) = " << resultD << "." << std::endl << std::endl;
}

什么应该发生的是第一评价d工作正常,套d.m_selfDestructTrigger,并导致自身被删除。然后第二次评估d应该崩溃,因为d不再真正存在。这正是 Debug 配置中发生的情况。(注意:正如@largest_prime_is_463035818 在下面指出的那样,它不应该像遇到未定义的行为那样崩溃。)

但在 Release 配置中,据我所知,所有代码都evaluate被完全跳过,执行直接跳转到 lambda。显然,优化代码中的断点有点可疑,但这似乎是正在发生的事情。我试过重建项目,但没有骰子;VS 似乎对此非常坚定。

我疯了吗?有什么我错过了吗?或者这是VS(甚至编译器)中的实际错误?在确定这是代码问题还是工具问题方面的任何帮助将不胜感激。

注意:我在 VS2019 16.8.3 上,使用/std:c++ latest功能集。

回答

未定义行为是一种非局部现象。如果您的程序遇到 UB,则意味着该程序的整个行为是未定义的,而不仅仅是它做坏事的那一小部分。

因此,UB 有可能“时间旅行”,影响理论上应该在执行 UB 之前正确执行的代码。也就是说,在展示 UB 的程序中没有“正确”;要么程序正确,要么不正确。

能走多远取决于实现,但就标准而言,VS的行为与标准是一致的。


以上是为什么VisualStudio在没有优化的情况下可以正确编译此函数,但在优化时却编译错误?的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>