如题。起因是这个:
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 上测试得到了同样的结果:
OP 拿 g++ 又编译了一次,本来想多尝试几次 GDB 抓个现行,但现在又复现不出来错误结果了。
OP 感到十分困惑,希望能得到解答
OP 又一次发现:在命令行执行会出错的 build,用 GDB 执行一定会得到正确答案
Lorange
(主治医师李大华)
13
我算法很差来着。已经上床了,明天也许有空。正好最近打算重拾算法了
Lorange
(主治医师李大华)
16
我可以用 ubuntu 帮你测测看,或者 google 一下。不过可能帮不上什么忙,毕竟我连 gdb 都没用过
OP 觉得这真得可能是一个优化问题。因为:
- gdb 运行一定会得到正确结果
- 不指定优化等级
g++ -Wall -g -o p1023 p1023.cpp
的时候(默认使用 -O0
)也一定会得到正确结果
- 但是当指定了
-O2
或 -O3
后就会时不时出错
- 又试了一下指定
-O1
发现每次都一定会得到错误结果
不对,但朋友在 Ubuntu 上用 g++ filename.cpp -o filename.o
编译的,一样会跑飞
m0rsun
22
@aibot 这里可能存在的 undefined behavior 是什么?如果你能找到导致 OP 结果错误的 undefined behavior,我就会给你$10 小费
1 Like
aibot
(小AI同學)
23
在代码中,可能存在未定义行为的情况如下:
-
未初始化变量:变量 max_p
在第一次使用前未初始化。在下面这行代码中:
max_p = max(p, max_p);
max_p
在第一次迭代时未被赋值,这将导致未定义行为。这可能会导致不同的运行结果,尤其是在开启不同的优化等级时。
-
超出边界操作:在进行 map
操作时,可能存在访问未定义的行为。例如:
max_p_s = input[max_p];
要确保每次访问 max_p
和max_p_s
时它们在map
中有有效值。
-
浮点操作中的未定义行为:在计算 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 范围,但与本问题无关
aibot
(小AI同學)
25
好的,那我们再仔细查看和考虑代码中的其他潜在未定义行为。既然已经排除了浮点除零的问题,让我们着重分析剩余部分。
变量初始化问题
关键问题依然可能出在未初始化变量上。让我们再具体分析:
-
max_p
变量:在第一次使用前未初始化,可能会导致不确定的值及行为。
-
sales_at_expected
变量:虽然它在input[p] = s;
循环内被条件性赋值,但使用该值的先后顺序仍需确认,以避免未初始化的风险。
安全检查与修改
下面是改进代码以确保没有未定义行为的建议:
-
初始化 max_p
:
int max_p = INT_MIN; // 设置为一个较小值,确保在循环中能正确更新
这样确保在循环中 max_p
能正确获取最大值。
-
在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_p
和sales_at_expected
以防止未定义行为,可以提高代码稳定性。请测试修改后的代码,看看是否解决了 OP 遇到的问题。
ucw
26
1 Like