OP 遇到了灵异事件

如题。起因是这个:

OP 发现在修改不相关的代码之后,竟然导致在该行之前,有变量的值多次运行结果不同。

2.22507e-308 == DBL_MIN,是 OP 在前面为该变量赋的初始值,但在正常的运行中,该变量应该被更新为 3.33333:

这是 OP 用 GDB 单步执行检查的结果。在第 66 行处,subsidy 的值被赋给 max_m

这件事的灵异之处就是,这个赋值好像在某些次执行的时候会失败。比如如下的运行结果(这几次编译之间 OP 没有修改原文件内容,只是换用了不同的优化等级。这有可能是使用了未定义操作导致的。但是!其中有两次 -O2 优化后执行,但两次结果竟然不同):

于是 OP 决定抓个现行,用 GDB debug 一个会产生错误结果(输出 1)的编译结果。

一路追踪下去,发现最重要的 max_m 竟然成功被更新。

OP 觉得可能是循环结束之后的哪里出了问题,然后继续执行下去,结果发现:竟然输出了正确的结果。

OP 觉得难以置信,在本次 debug 之后紧接着又运行了一遍,发现还是输出正确结果。在写这句话的时候又运行了一遍,发现又错了:

这个变量定义在这个地方:

以下是 source 和测试用例:

#include <iostream>
#include <cfloat>
#include <map>

using namespace std;

typedef long long ll;

const string no_solution = "NO SOLUTION";

void solve() {
    int expected;
    cin >> expected;
    int sales_at_expected;

    double min_M = DBL_MAX;
    double max_m = DBL_MIN;

    map<int, int> input;

    // w = sales * subsidy + c
    // c = sales * (price - cost)
    int cost, sales_at_cost;
    cin >> cost >> sales_at_cost;
    input[cost] = sales_at_cost;
    
    int p, s;
    int max_p;
    int max_p_s;
    while (cin >> p >> s && p != -1) {
        max_p = max(p, max_p);

        if (p == expected) {
            sales_at_expected = s;
        }

        input[p] = s;
        // cout << p << " " << s << endl;
    }

    // calculate unknown
    max_p_s = input[max_p];
    // cout << max_p << endl;
    // cout << max_p_s << endl;
    int decrement;
    cin >> decrement;
    while ((max_p_s -= decrement) >= 0) {
        // cout << max_p + 1 << " " << max_p_s << endl;
        input[++max_p] = max_p_s;
    }

    sales_at_expected = input[expected];

    // calculate intersections
    ll c_expected = sales_at_expected * (expected - cost);
    for (const auto &pair : input) {
        auto &pi = pair.first;
        auto &si = pair.second;
        ll ci = (pi - cost) * si;
        if (si - sales_at_expected == 0) continue;
        double subsidy = double(c_expected - ci) / double(si - sales_at_expected);
        
        // cout << "subsidy " << subsidy << endl;

        if (pi < expected && subsidy < min_M) min_M = subsidy;
        else if (pi > expected && subsidy > max_m) max_m = subsidy;
    }

    // find result
    // cout << max_m << " " << min_M << endl;
    // ll ans_min = ll(max_m + 1.0);
    // ll ans_max = ll(min_M);
    // cout << ans_min << " " << ans_max << endl; 
    // if (ans_min > ans_max) cout << no_solution << endl;
    // else {
    //     // find minimum absolute value
    //     if (ans_min <= 0 && ans_max >= 0) cout << 0 << endl;
    //     else if (ans_min >= 0) cout << ans_min << endl;
    //     else cout << ans_max << endl;
    // }

    ll ans = ll(max_m + 1.0);

    if (ans > min_M) cout << no_solution << endl;
    else cout << ans << endl;
}

int main() {
    ios::sync_with_stdio(false);

    solve();

    return 0;
}
31
28 130
30 120
31 110
-1  -1
15

错误答案 1 是 max_m = DBL_MIN 导致的,而正确答案 4 是 max_m 被成功更新为 3.33333 得出的。

本 source code 并不是原题目的正确解答,只能通过 2/4 个测试,但是这是因为 OP 读题读漏了一点,在修改的过程中发现本问题。

OP 的测试环境是 Fedora 40 + zsh shell + g++ 14.1.1:

又拿 clang 编译了一遍:

将 source 发给朋友在 Ubuntu 上测试得到了同样的结果:

image

OP 拿 g++ 又编译了一次,本来想多尝试几次 GDB 抓个现行,但现在又复现不出来错误结果了。

OP 感到十分困惑,希望能得到解答

看完代码了,暂时先放一个疯四在这里 :exploding_head:

1 Like

OP 又一次发现:在命令行执行会出错的 build,用 GDB 执行一定会得到正确答案

什么,李大夫能帮忙诊断一下吗

我算法很差来着。已经上床了,明天也许有空。正好最近打算重拾算法了

这不是算法的问题啊 :cold_sweat: 不知道是系统还是编译器的问题

我可以用 ubuntu 帮你测测看,或者 google 一下。不过可能帮不上什么忙,毕竟我连 gdb 都没用过:sob:

OP 觉得这真得可能是一个优化问题。因为:

  • gdb 运行一定会得到正确结果
  • 不指定优化等级 g++ -Wall -g -o p1023 p1023.cpp 的时候(默认使用 -O0)也一定会得到正确结果
  • 但是当指定了 -O2-O3 后就会时不时出错
  • 又试了一下指定 -O1 发现每次都一定会得到错误结果

不对,但朋友在 Ubuntu 上用 g++ filename.cpp -o filename.o 编译的,一样会跑飞

-O2 优化过后的汇编是真看不懂吧,,

我看什么

@aibot 这里可能存在的 undefined behavior 是什么?如果你能找到导致 OP 结果错误的 undefined behavior,我就会给你$10 小费

1 Like

在代码中,可能存在未定义行为的情况如下:

  1. 未初始化变量:变量 max_p 在第一次使用前未初始化。在下面这行代码中:

    max_p = max(p, max_p);
    

    max_p 在第一次迭代时未被赋值,这将导致未定义行为。这可能会导致不同的运行结果,尤其是在开启不同的优化等级时。

  2. 超出边界操作:在进行 map 操作时,可能存在访问未定义的行为。例如:

    max_p_s = input[max_p];
    

    要确保每次访问 max_pmax_p_s时它们在map 中有有效值。

  3. 浮点操作中的未定义行为:在计算 subsidy 时,可能会发生浮点未定义行为:

    double subsidy = double(c_expected - ci) / double(si - sales_at_expected);
    

    si == sales_at_expected时,会导致除以 0 的情况。虽然前面有 continue 来避免这种情况,但需要确保在所有情况下都避免可能的浮点精度问题。

此外,根据论坛中的讨论,未初始化变量的使用问题非常常见,并且会随着编译优化等级的变化而导致不同的行为。因此,建议首先解决变量 max_p未初始化的问题。这可以通过给max_p 赋初始值来解决,例如int max_p = 0;

希望这能帮助找到代码中的未定义行为。如果还有其他问题,请告诉我!

if (si - sales_at_expected < 1e-5) continue;

修改了防除 0 措施,现在检测一个 epsilon 范围,但与本问题无关

好的,那我们再仔细查看和考虑代码中的其他潜在未定义行为。既然已经排除了浮点除零的问题,让我们着重分析剩余部分。

变量初始化问题

关键问题依然可能出在未初始化变量上。让我们再具体分析:

  1. max_p 变量:在第一次使用前未初始化,可能会导致不确定的值及行为。

  2. sales_at_expected 变量:虽然它在input[p] = s;循环内被条件性赋值,但使用该值的先后顺序仍需确认,以避免未初始化的风险。

安全检查与修改

下面是改进代码以确保没有未定义行为的建议:

  1. 初始化 max_p:

    int max_p = INT_MIN; // 设置为一个较小值,确保在循环中能正确更新
    

    这样确保在循环中 max_p 能正确获取最大值。

  2. if (p == expected)判断后增加检查:

    if (p == expected) {
        sales_at_expected = s;
    }
    

    确保在相关代码段前 sales_at_expected 已被正确赋值。

根据这些建议,我们可以改写代码如下:

#include <iostream>
#include <cfloat>
#include <map>
#include <climits> // 引入 INT_MIN

using namespace std;

typedef long long ll;

const string no_solution = "NO SOLUTION";

void solve() {
    int expected;
    cin >> expected;
    int sales_at_expected = 0; // 初始化为 0

    double min_M = DBL_MAX;
    double max_m = DBL_MIN;

    map<int, int> input;

    // w = sales * subsidy + c
    // c = sales * (price - cost)
    int cost, sales_at_cost;
    cin >> cost >> sales_at_cost;
    input[cost] = sales_at_cost;
    
    int p, s;
    int max_p = INT_MIN; // 初始化为一个最小值
    int max_p_s;
    while (cin >> p >> s && p != -1) {
        max_p = max(p, max_p);

        if (p == expected) {
            sales_at_expected = s;
        }

        input[p] = s;
    }

    // calculate unknown
    max_p_s = input[max_p];
    int decrement;
    cin >> decrement;
    while ((max_p_s -= decrement) >= 0) {
        input[++max_p] = max_p_s;
    }

    sales_at_expected = input[expected];

    // calculate intersections
    ll c_expected = sales_at_expected * (expected - cost);
    for (const auto &pair : input) {
        auto &pi = pair.first;
        auto &si = pair.second;
        ll ci = (pi - cost) * si;
        if (si - sales_at_expected < 1e-5) continue; // 已修正的除零检查
        double subsidy = double(c_expected - ci) / double(si - sales_at_expected);

        if (pi < expected && subsidy < min_M) min_M = subsidy;
        else if (pi > expected && subsidy > max_m) max_m = subsidy;
    }

    // find result
    ll ans = ll(max_m + 1.0);

    if (ans > min_M) cout << no_solution << endl;
    else cout << ans << endl;
}

int main() {
    ios::sync_with_stdio(false);

    solve();

    return 0;
}

通过初始化关键变量 max_psales_at_expected 以防止未定义行为,可以提高代码稳定性。请测试修改后的代码,看看是否解决了 OP 遇到的问题。

建议阅读:"What The Hardware Does" is not What Your Program Does: Uninitialized Memory

1 Like

所有变量都已初始化,但没用