为什么 printf() 在使用指针时绕过分段失败?

why does printf() circumvents segmentation fail when using pointers?

我一直在测试 C 中点的编码操作,我遇到了一个我无法解释的奇怪现象。

下面的代码简单地实例化了四个双精度变量并接收用户输入来填充它们。

奇怪的是被注释掉的行,当该行没有被注释时,代码会按预期工作,但是当我把它注释掉时,在循环中出现分段失败。

我一直在 c9.io

编写此测试

代码:

#include <stdio.h>

int main() {
    double i, a, b, c, d;
    double * cur = &a;
    char ch = 'a';
    // printf("a - %p\nb - %p\nc - %p\nd - %p\n",&a, &b, &c, &d);
    for (i=0; i<4; i++) {
        printf("Enter %c: ", ch++);
        scanf("%lf", cur++);
    }
    printf("a - %lf\nb - %lf\nc - %lf\nd - %lf\n",a, b, c, d);
    return 0;
}

我语塞了,为什么绕过分段的地址打印会失败?

在您的代码中,

 double * cur = &a;

然后做

 scanf("%lf", cur++);

并不像您预期​​的那样依次指向 abcd 变量。这只是未定义的行为。

FWIW,尝试访问超出限制的内存,调用 undefined behavior

如果您想保持相同的方法,您可以将 a 定义为数组(而不是单独的变量,如 abc , d) 然后你可以使用 cur遍历 数组。数组成员始终是连续的。

您正在使用 scanf() 将值写入 *cur。由于 cur 指向一个堆栈变量,导致段错误的最可能原因是堆栈没有按照您的想法进行布局,并且当您增加指针时, scanf() 正在覆盖堆栈上的重要内容,例如 return 地址,或者堆栈帧指针。这两者都很容易导致段错误。

编辑:添加我在下面评论中的另一个想法:

另一种可能性是递增 cur 实际上导致它不是从 &a&b,而是另一个方向,从 &a&cur.如前所述,无法保证堆栈的顺序。那会导致 scanf() 破坏 cur 本身。添加 printf() 可能会以某种方式改变堆栈分配,因为 printf() 之前需要 a,b,c,d,但 cur 直到之后才使用。

已经解释了代码中的问题所在以及如何修复它。我就不重复了。


Undefined behaviour 并不意味着代码工作正确或不正确,它意味着无法预测行为。这意味着它在不同的机器上可能不同,甚至在同一台机器上连续运行。

当您包含 printf() 行时,代码仍然具有未定义的行为。看起来它工作正常,但事实并非如此。

在您的程序的特定情况下,地址运算符 (&) 与变量 bcdprintf() 中的用法line 强制编译器将它们存储在内存中。没有规则告诉它如何将变量放在内存中;大多数编译器按照变量定义的顺序将变量放在连续的内存位置。这可能也是这里发生的事情。这就是为什么 cur++ 可能前进到 b 然后 c 然后 d 的地址并且程序显然可以正常工作。

printf()行被注释掉时,编译器可能优化了内存使用,选择将一些变量bcd存储在寄存器。这样 cur++ 无法联系到他们。因为 a 是局部变量,所以在许多体系结构中它存储在堆栈中。 cur++ 遍历堆栈,scanf("%lf", curr++) 通过覆盖函数的 return 地址和其他值来破坏堆栈。当函数完成时,它 returns 到一个无效的内存地址。砰!分段错误。