【问题标题】:gcc changing output depending on whether address is observed or notgcc 根据是否观察到地址来改变输出
【发布时间】:2020-12-12 18:48:31
【问题描述】:

我正在尝试制作一个打印“eggy weggy”的程序。通过将字符的值存储在 3 个变量(xyz)中。我已经尝试添加一个for 循环来打印几次,但这就是奇怪的开始。如果我将printf("%p\n%p\n%p\n%p\n", &x, &y, &z, &i); 放在代码中的任何位置,则输出符合要求。但是如果我排除这个,程序只会打印多次“eggy”。

#include <stdio.h>

int main(int argc, char* argv[])
{
    unsigned x = 2036819813; //sequence of 'y', 'g', 'g', 'e'
    unsigned y = 1734702880; //sequence of 'g', 'e', 'w', ' '
    unsigned z = 3045735; //sequence of '\0', '.', 'y', 'g'
    char* ptr = (char*)&x;
    int i = 0;
    for (i = 0; i < 10; i++)
    {
        printf("%s\n", ptr);
    }
    printf("%p\n%p\n%p\n%p\n", &x, &y, &z, &i);
    return 0;
}

输出:

eggy weggy.
eggy weggy.
eggy weggy.
eggy weggy.
eggy weggy.
eggy weggy.
eggy weggy.
eggy weggy.
eggy weggy.
eggy weggy.
0x7ffc2844d000
0x7ffc2844d004
0x7ffc2844d008
0x7ffc2844d00c

现在没有printf("%p\n%p\n%p\n%p\n", &amp;x, &amp;y, &amp;z, &amp;i);

输出:

eggy
eggy
eggy
eggy
eggy
eggy
eggy
eggy
eggy
eggy

另外,如果我不循环执行此操作,它会打印出“eggy weggy”。正确。这对我来说似乎很奇怪。我不知道变量的状态可以通过是否观察到它们的值来确定。这是一个错误吗?这是一个优化的东西吗?任何信息表示赞赏:)

注意:我正在使用 gcc (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0 和参数 -O0 -std=c89 -Wall(我没有在量子计算机上运行它)

编辑:当我在 clang 中编译代码时,代码会按预期运行,但我不得不切换 xz 的声明顺序,因为 clang 将较新的变量放在较低的内存地址中旧变量,不像 gcc 将它们放在更高的地址。

【问题讨论】:

  • 由于没有printf 的代码没有明确使用yz,并且代码无论如何都依赖于未定义的行为,那么可能yz 已经被优化了。
  • “我不知道变量的状态可以通过是否观察到它们的值来确定。”当您在量子计算机中运行程序时会发生这种情况
  • @WeatherVane 这可能是这种情况,但如果我删除ifor 循环并且不使用printf,它仍然可以正常工作
  • 只需编写正确的代码,不要费力去理解为什么不正确的代码可能有效,也可能无效。
  • 你试过用-O0编译吗?

标签: c gcc


【解决方案1】:

我不知道变量的状态可以通过是否观察到它们的值来确定。

C 标准最终仅根据其可观察行为来定义程序,即根据 C 2018 5.1.2.3 6:

对 volatile 对象的访问严格按照抽象机的规则进行评估。

——在程序终止时,写入文件的所有数据应与根据抽象语义执行程序所产生的结果相同。

——交互设备的输入和输出动态应按照 7.21.3 中的规定进行。这些要求的目的是尽快出现无缓冲或行缓冲的输出,以确保在程序等待输入之前实际出现提示消息。

因此,如果一个对象从未被使用过,编译器中的优化器可能会将其从程序中彻底删除,因为无论该对象是否被删除,程序的可观察行为都是相同的。

如果打印了对象的地址,则会导致输出行为是可观察的,编译器必须确保打印的地址与 C 标准描述的内存模型相匹配。优化器实际上不必将这些对象保留在内存中,但它必须为其内存提供名义上的预留,以便分配可以打印的地址。

这些要求导致您的两个版本的程序存在差异,而这些差异恰好导致您在 C 标准定义的方式之外使用 ptr 时观察到的变化。值得注意的是,当它被传递给printf 进行%s 转换时,它应该指向以空字符结尾的字符序列的第一个字符。此外,由于它是指向x 的指针,因此该序列不应超出x。由于违反了这两个要求,因此程序的行为不是由 C 标准定义的。在这些试验中,它可能向您显示了“超出”x 的数据,但这不是您应该依赖的。而“超出”x 的数据恰好依赖于上述优化或类似效果。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多