【问题标题】:Value of variable changes with unrelated code不相关代码的变量值变化
【发布时间】:2021-08-23 17:44:04
【问题描述】:

我从几个小时以来一直在与一个错误作斗争。基本上,我对 main.c 中的 uint64_t 数组进行了一些简单的位操作(没有函数调用)。它在 Debug 中的 gcc (Ubuntu)、MSVS2019 (Windows 10) 上正常工作,但不能在 Release 中。但是我的目标架构是 x64/Windows,所以我需要让它与 MSVS2019/Release 一起正常工作。除此之外,我很好奇问题的原因是什么。没有一个编译器显示错误或警告。

现在,只要我将一个完全不相关的命令添加到循环中(注释 printf()),它就会正常工作。

...
int q = 5;
uint64_t a[32] = { 0 };
// a[] is filled with data
for (int i = 0; i < 32; i++) {
    a[q] = (a[q] << 2) | 8;
    // printf("%i \n", i); // that's the line which makes it work
}
...

最初我认为我在 for() 循环之前的某个地方弄乱了堆栈,但我检查了多次......一切都很好!

  • 检查所有使用的变量以进行初始化
  • 没有返回局部变量的指针(在范围内)
  • 数组索引(读​​取和写入)均在声明限制(范围内)内

所有 Google/SE 帖子都根据上述一些原因解释了主题 UB,但这些都不适用于我的代码。此外,它在 MSVS2019/Debug 和 gcc 中工作的事实表明代码工作正常。

我错过了什么?

--- 更新 (24.08.2021 12:00) ---

我完全被卡住了,因为添加了printf() 修改了结果并且 MSVS/Debug 有效。那么如何检查变量呢?!

@Lev M 在显示的for() 循环之前和之后有相当多的计算。这就是为什么我跳过了大部分代码,只显示了 sn-p,我可以在其中影响代码以使其正常工作。我知道最终结果应该是什么(它只是一个 uint64_t),并且它与 MSVS 的 Release 版本有问题。我还检查了没有for() 循环。它没有“离开”优化。如果我完全忽略它,结果又会不同。

@tstanisl 这只是一个 uint64_t 数字的问题。我知道输入 A 应该输出 B。

@Steve Summit 这就是我发帖的原因(有点绝望)。我检查了各个方向,尽可能多地隔离了代码,但是……没有未初始化的变量或数组越界。快把我逼疯了。

@Craig Estey 不幸的是,该代码相当广泛。我想知道......错误是否也出现在未运行的部分代码中?

@Eric Postpischil 同意!

@Nate Eldredge 我在 valgrind 上进行了测试(见下文)。

...
==13997== HEAP SUMMARY:
==13997==     in use at exit: 0 bytes in 0 blocks
==13997==   total heap usage: 1 allocs, 1 frees, 1,024 bytes allocated
==13997==
==13997== All heap blocks were freed -- no leaks are possible
==13997==
==13997== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

--- 更新 (24.08.2021 18:00) ---

我找到了问题的原因(经过无数次反复试验),但还没有解决方案。我发布更多代码。

...
int q = 5;
uint64_t a[32] = { 0 };
// a[] is filled with data
for (int i = 0; i < 32; i++) {
    a[q] = (a[q] << 2) | 8;
    // printf("%i \n", i); // that's the line which makes it work
}
for (int i = 0; i < 32; i++) {
    a[q] = (a[q] << 3) | 3;
}
...

事实上,MSVS/Release 编译器就是这样做的:

...
int q = 5;
uint64_t a[32] = { 0 };
// a[] is filled with data
for (int i = 0; i < 32; i++) {
    a[q] = (a[q] << 2) | 8;
    a[q] = (a[q] << 3) | 3;
}
...

...这是不一样的。没见过这样的东西!

如何强制编译器将 2 个 for() 循环分开?

【问题讨论】:

  • 这可能是一个优化问题——也许 VS 在您进行发布构建时以某种方式优化了循环?您在“发布”中得到什么结果 - 数据没有改变?随机垃圾?
  • 预期的行为是什么?
  • 您所描述的症状几乎总是意味着您有一个未初始化的局部变量,或者您正在访问的本地数组超出了范围。我知道你说过你检查过那些,但我会继续看那里!
  • 您发布的代码已经可以工作。问题必须在您没有发布的代码中。 什么它做了[错误]和应该它做什么?我怀疑完整的代码足够小,您可以编辑您的问题并发布它。
  • 关于“事实上,它在 MSVS2019/Debug 中工作并且 gcc 显示代码工作”:这不是一个有效的逻辑推理。

标签: c undefined-behavior uint64


【解决方案1】:

总结:

MSVS/Release(默认解决方案属性)优化将更改此代码...

// Code 1
...
int q = 5;
uint64_t a[32];
// a[] is filled with data
for (int i = 0; i < 32; i++) {
    a[q] = (a[q] << 2) | 8;
    // printf("%i \n", i); // that's the line which makes it work
}
for (int i = 0; i < 32; i++) {
    a[q] = (a[q] << 3) | 3;
}
...

...变成下面的一个,和...不一样

// Code 2
...
int q = 5;
uint64_t a[32];
// a[] is filled with data
for (int i = 0; i < 32; i++) {
    a[q] = (a[q] << 2) | 8;
    a[q] = (a[q] << 3) | 3;
}
...

上面的摘录略有简化,因为不限于恒定的 32 个循环,而是保持变量 (% 8)。因此,64 位常量不能被用户评论使用。

发现:

MSVS/发布 - 失败
MSVS/调试 - 工作
gcc/Release - 工作
gcc/Debug - 工作

MSVS/Release 优化将两个for() 循环(代码1)合并为一个for() 循环(代码2)。

修复:

注释的printf() 提供了一个人为的修复,因为编译器看到了打印中间结果的要求。

另一种解决方法是将类型限定符 volatile 用于 a[]

问题的根源在于,MSVS 优化没有考虑索引q 在两个循环中保持不变,这意味着第一个循环需要在第二个循环开始之前完成。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-12-19
    • 2019-11-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多