【问题标题】:The local variables are not static but why do I get this behaviour?局部变量不是静态的,但为什么我会得到这种行为?
【发布时间】:2013-06-13 18:32:55
【问题描述】:

当我教 C 时,有时我指望 GCC 来完成某些规则中“令人信服”的部分。例如,不应考虑函数上的局部变量在调用之间保留值。

GCC 总是帮助我向学生教授这些课程,将垃圾放在局部变量上,以便他们了解正在发生的事情。

现在,这段代码肯定让我很难受。

#include <stdio.h>

int test(int x)
{
        int y;
        if(!x)
                y=0;
        y++;
        printf("(y=%d, ", y);
        return y;
}

int main(void)
{
        int a, i;

        for(i=0; i<5; i++)
        {
                a=test(i);
                printf("a=%d), ", a);
        }
        printf("\n");
        return 0;
}

输出是:

(y=1, a=1), (y=2, a=2), (y=3, a=3), (y=4, a=4), (y=5, a=5),

但如果我评论这一行:

       /* printf("(y=%d, ", y); */

那么输出变成:

a=1), a=32720), a=32721), a=32722), a=32723),

我使用-Wall开关编译代码,但没有任何警告与使用局部变量相关但未初始化它们。

是否有任何 GCC 开关会导致警告,或者至少显式显示一些垃圾?我尝试了优化开关,这有助于代码输出变成这样:

$ gcc test.c -o test -Wall -Os
$ ./test 
(y=1, a=1), (y=1, a=1), (y=1, a=1), (y=1, a=1), (y=1, a=1),
$ gcc test.c -o test -Wall -Ofast
$ ./test 
(y=1, a=1), (y=1, a=1), (y=1, a=1), (y=1, a=1), (y=1, a=1),
$ gcc test.c -o test -Wall -O0
$ ./test 
(y=1, a=1), (y=2, a=2), (y=3, a=3), (y=4, a=4), (y=5, a=5),
$ gcc test.c -o test -Wall -O1
$ ./test 
(y=1, a=1), (y=1, a=1), (y=1, a=1), (y=1, a=1), (y=1, a=1),
$ gcc test.c -o test -Wall -O2
$ ./test 
(y=1, a=1), (y=1, a=1), (y=1, a=1), (y=1, a=1), (y=1, a=1),
$ gcc test.c -o test -Wall -O3
$ ./test 
(y=1, a=1), (y=1, a=1), (y=1, a=1), (y=1, a=1), (y=1, a=1),

但在所有情况下 y=1 都是一种诡计。标准是否改变了,所以局部变量现在用零初始化?

【问题讨论】:

  • 我不认为这是巧合,因为编译器会将 y 链接到某个相对的内存区域。它不会改变 y 与第一个堆栈的关系,所以如果前一个堆栈的大小没有增长,y 将始终在同一个位置!
  • @Magn3s1um:您假设y 在堆栈上。如果它在寄存器中怎么办?未定义的行为是未定义的行为。你根本不能指望结果,即使你认为你知道为什么它可能会做你想做的事。
  • 依靠编译器为具有未定义行为的程序显示明显的错误行为是一个高风险的提议。尤其是在涉及未初始化的变量和释放后使用错误时。
  • @Devolus 实际上我不同意。我见过很多人在面对一个行为未定义的程序时会说“但是当我之前尝试过它时它起作用了”。我认为让 C 和 C++ 程序员养成依靠尝试来查看它们是否有效的习惯是一个坏主意——这些语言就是不这样工作的。
  • @SebastianRedl,问题是当人们依靠“它对我有用”来了解它会产生什么影响时。许多人认为“如果它以前有效”就可以了,这不一定是正确的。

标签: c static initialization local


【解决方案1】:

这就是未定义行为的问题:它是“未定义”。

因此,任何一组结果都完全取决于编译器/设置/内存中的内容/中断的组合。

您可能有机会在某些输出您“预期”的设置上展示问题 - 但这只是运气。

您的发现实际上更重要 - 故障模式的数量比您想象的要多(尽管幸运的是,还没有人重新格式化您的硬盘),并且 最有害和最危险的类型“未定义的行为” 是指行为实际上在 99.99% 的时间内“符合预期”。

是 0.01% 让你受益。

【讨论】:

    【解决方案2】:

    实现此想法的另一种可能方法是在调用test 函数之间调用另一个函数。如果另一个函数使用堆栈空间,那么它可能最终会更改堆栈值。例如,可能添加这样的函数:

    int changeStack( int x )
    {
        int y2 = x + 100;
        return y2;
    }
    

    然后给它添加一个调用:

        for(i=0; i<10; i++)
        {
                a=test(i);
                printf("a=%d), ", a);
                changeStack( i );
        }
    

    这当然取决于优化级别,但是使用默认的 gcc 编译 (gcc test.c),我在更改后得到以下结果:

    (y=1, a=1), (y=101, a=101), (y=102, a=102), (y=103, a=103), (y=104, a=104), (y=105, a=105), (y=106, a=106), (y=107, a=107), (y=108, a=108), (y=109, a=109),
    

    【讨论】:

    • 鉴于 GCC 倾向于将所有内容存储在内存中(如果没有请求优化),这种方法可能会很有效。不过,添加 -O 标志几乎肯定会让它消失。
    • @CarlNorum:是的,这绝对是一个非常特定于编译器和优化的演示。使用任何优化确实会改变结果。试图展示未定义行为的棘手业务。
    • 马克,你明白了。我认为可以在课堂上使用这个例子。谢谢!
    【解决方案3】:

    您的程序导致未定义的行为。如果将非零值传递给test()y 永远不会被初始化。 printf 包含与否,你不能依赖结果。

    如果你想要一个警告,clang 会给你一个-Wsometimes-uninitialized

    example.c:6:12: warning: variable 'y' is used uninitialized whenever 'if'
          condition is false [-Wsometimes-uninitialized]
            if(!x)
               ^~
    example.c:8:9: note: uninitialized use occurs here
            y++;
            ^
    example.c:6:9: note: remove the 'if' if its condition is always true
            if(!x)
            ^~~~~~
    example.c:5:14: note: initialize the variable 'y' to silence this warning
            int y;
                 ^
                  = 0
    1 warning generated.
    

    我用我手头上的几个 GCC 版本进行了测试,但没有一个会对我产生警告。

    【讨论】:

    • 我知道。我想使用 GCC 来执行这个讲座(这样学生可以自己看到编译返回的一些错误代码)
    • 我刚刚把代码循环改成了for(i=1; i&lt;5; i++),结果是:(y=1, a=1), (y=2, a=2), (y=3, a=3), (y=4, a=4),。这在一个充满小丑的班级里是可怕的。 :)
    • 我使用 gcc 开关 (-Wmaybe-uninitialized) 进行了测试,但没有警告。还是非常感谢。很好的提示。
    猜你喜欢
    • 2021-08-23
    • 2021-05-29
    • 1970-01-01
    • 1970-01-01
    • 2012-08-29
    • 1970-01-01
    • 1970-01-01
    • 2012-05-05
    相关资源
    最近更新 更多