【问题标题】:How are these two pieces of code different?这两段代码有何不同?
【发布时间】:2019-09-28 10:40:15
【问题描述】:

我尝试了这些代码行并发现了令人震惊的输出。我期待一些与初始化相关的原因,无论是在一般情况下还是在 for 循环中。

1.)

int i = 0;
    for(i++; i++; i++){
        if(i>10)    break;
    }
    printf("%d",i);

输出 - 12

2.)

int i;
    for(i++; i++; i++){
        if(i>10)    break;
    }
    printf("%d",i);

输出 - 1

我希望语句“int i = 0”和“int i”是相同的。它们之间有什么区别?

【问题讨论】:

  • 第二个代码可以输出任何东西,因为i是未初始化的
  • “它们之间的差异”正是您的代码所显示的:一个被初始化为确定的值,另一个不是。后者的完整代码因此调用未定义的行为。
  • 现在您必须记住,C 和 C++ 是两种非常不同的语言,具有非常不同的语义。您提供的代码是这种差异真正重要的一种情况。在一种语言中,它立即是未定义的行为
  • static int i; 在 C 中将 i 初始化为零(edit);可能也在 C++ 中,但不要相信我的话。
  • @Someprogrammerdude -- 整数可以有一个陷阱值。 C11 标准中 6.2.6.2/3 的最后一段说,对于符号大小和二进制补码表示,符号位 1 和值位全 0 可以是陷阱表示,而对于补码,一个符号位 1 和值位全 1 可以是陷阱表示。此类值是普通值还是陷阱表示是由实现定义的。

标签: c++ c loops for-loop


【解决方案1】:

我希望语句“int i = 0”和“int i”是相同的。

不,这是您的错误期望。如果一个变量是在函数之外声明的(作为一个“全局”变量),或者如果它是用static关键字声明的,即使你不写= 0,它也保证被初始化为0。但是在函数内部定义的变量(没有static 的普通“本地”变量)确实没有有这种保证初始化。如果您不显式初始化它们,它们开始包含不确定的值。

(但请注意,在这种情况下,“不确定”并不意味着“随机”。如果您编写一个使用或打印未初始化变量的程序,您通常会发现它开始时每次都包含相同的值你运行你的程序。碰巧,它甚至可能是 0。在大多数机器上,发生的情况是该变量采用之前调用的函数留在“堆栈上”的任何值。)

另请参阅以下相关问题:

Non-static variable initialization
Static variable initialization?

另请参阅these class notes 中的section 4.2section 4.3

另请参阅C FAQ list 中的question 1.30


附录:根据您的 cmets,听起来当您无法初始化 i 时,它开始时的不确定值恰好是 0,所以您现在的问题是:

"给定程序

#include <stdio.h>
int main()
{
    int i;                   // note uninitialized
    printf("%d\n", i);       // prints 0
    for(i++; i++; i++){
        if(i>10)    break;
    }
    printf("%d\n", i);       // prints 1
}

编译器会发出哪些可能的操作序列,使其计算出最终值 1?"

这可能是一个很难回答的问题。有几个人试图在这个问题的其他答案和 cmets 中回答它,但由于某种原因你没有接受那个答案。

这个答案又是,“未初始化的局部变量会导致未定义的行为。未定义的行为意味着任何事情都可能发生。”

这个答案的重要之处在于它说“任何事情都可能发生”,而“任何事情”绝对意味着任何事情。这绝对没有意义。

第二个问题,正如我所说的那样,实际上甚至没有意义,因为它包含一个内在的矛盾,因为它询问“编译器可能发出什么可能的操作序列”,但由于程序包含 Undefined行为,编译器甚至没有义务发出合理的操作序列。

如果你真的想知道你的编译器发出了什么操作序列,你必须问它。在 Unix/Linux 下,使用 -S 标志进行编译。在其他编译器下,我不知道如何查看汇编语言输出。但请不要指望输出有任何意义,也请不要让我向你解释(因为我已经知道它没有任何意义)。

因为允许编译器做任何事情,它可能会发出代码,就好像你的程序是这样编写的,例如,

#include <stdio.h>
int main()
{
    int i;                   // note uninitialized
    printf("%d\n", i);       // prints 0
    i++;
    printf("%d\n", i);       // prints 1
}

“但这没有任何意义!”,你说。 “编译器怎么能把“for(i++; i++; i++) ...”变成“i++”?答案——你听说过,但也许你还是不太相信——当程序包含未定义的行为时,编译器可以做任何事情

【讨论】:

  • 即使没有初始化它也会打印 0,即使在第二种情况下,结果也是 1。我想知道这背后的原因。
  • @KavishMadaan 我不知道你所说的“即使它没有初始化也打印 0”是什么意思——我以为你说它打印了 1。但无论如何,如果碰巧i 开始输出包含-1,程序将打印1
  • @KavishMadaan 我现在对你在问什么有点困惑。我认为问题是“为什么这两个程序不同?”,我希望我们已经回答了这个问题。如果您现在问“第一个程序如何打印 1?”,答案是“如果 i 最初是 -1”。或者您想知道 -1 的初始值如何导致它打印 1?
  • 如果我在 for 循环之前将 print 语句作为 "printf("%d ",i)" 那么案例 1 的输出是 - "0 12" 而案例 2 是 - "0 1"
  • 有两种可能。我将在两个单独的 cmets 中解决它们。但在此之前,我不得不说:我们可能不会追根究底,我们可能不会找到让你满意的答案。
【解决方案2】:

区别在于您已经观察到的。第一个代码初始化i 另一个没有。在 c++ 中使用未定义的值是未定义的行为 (UB)。编译器假定 UB 不会发生在正确的程序中,因此允许发出执行任何操作的代码。 更简单的例子是:

int i;
i++;

编译器知道i++ 不可能在正确的程序中发生,并且编译器不会为错误的输入发出正确的输出,因此当您运行此代码时,任何事情都可能发生。

更多阅读请看这里:https://en.cppreference.com/w/cpp/language/ub

这是一个经验法则(除其他外)有助于避免未初始化的变量。它被称为Almost-Always-Auto,它建议几乎总是使用auto。如果你写

auto i = 0;

您不能忘记初始化i,因为auto 需要一个初始化器才能推断出类型。

PS:C 和 C++ 是两种不同的语言,具有不同的规则。您的第二个代码是 C++ 中的 UB,但我无法回答您的 C 问题。

【讨论】:

  • 在 C 和 C++ 中, int 变量在我的例子中被初始化为 0。我也通过在声明变量后打印值来检查这一点(它打印 0)。我的问题是为什么对于相同的 i 值有 2 个不同的输出。
  • @KavishMadaan printf("%d",i); 可以在 i 未初始化时打印任何内容,0 只是您可以观察到的众多可能结果之一
猜你喜欢
  • 2021-10-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-12-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多