【Learncpp中文翻译版】【1.6 — 未初始化的变量和未定义的行为】

原文链接:原文链接

声明:

  • 本文旨在方便了解学习C++语法,切勿用于任何商业用途。
  • 由于本人英语水平有限,文章中可能存在语义错误,如有疑问请参照原文,也可以在评论区指出错误。

1.6 — 未初始化的变量和未定义的行为

未初始化的变量

与某些编程语言不同,C/C++ 不会自动将大多数变量初始化为给定值(例如零)。因此,当编译器为变量分配内存位置时,该变量的默认值是恰好已经在该内存位置中的任何(垃圾)值!

未赋予已知值(通常通过初始化或赋值)的变量称为未初始化变量

作者注

许多读者期望术语“初始化”和“未初始化”是严格对立的,但事实并非如此!初始化意味着在定义点为对象提供了一个初始值。未初始化意味着对象尚未被赋予已知值(通过任何方式,包括赋值)。因此,未初始化但随后被赋值的对象不再是未初始化的(因为它已被赋予已知值)。

回顾一下:

初始化 = 对象在定义点被赋予一个已知值。

赋值 = 对象被赋予一个超出定义点的已知值。

未初始化 = 对象尚未被赋予已知值。
作为旁白…


这种缺乏初始化是从 C 继承的性能优化,当时计算机速度很慢。想象一下,您要从文件中读取 100,000 个值。在这种情况下,您可能会创建 100,000 个变量,然后用文件中的数据填充它们。

如果 C++ 在创建时使用默认值初始化所有这些变量,这将导致 100,000 次初始化(这会很慢),并且几乎没有什么好处(因为无论如何您都在覆盖这些值)。

现在,您应该始终初始化您的变量,因为这样做的成本与收益相比是微不足道的。一旦您对该语言更加熟悉,在某些情况下,您可能会出于优化目的而忽略初始化。但这应该始终有选择地和有意识地完成。

使用未初始化变量的值可能会导致意外结果。考虑以下短程序:

#include <iostream>

int main()
{
    // define an integer variable named x
    int x; // this variable is uninitialized because we haven't given it a value

    // print the value of x to the screen
    std::cout << x; // who knows what we'll get, because x is uninitialized

    return 0;
}

在这种情况下,计算机会将一些未使用的内存分配给x。然后它将驻留在该内存位置的值发送到std::cout,后者将打印该值(解释为整数)。但它会打印什么值?答案是 “who knows!” ,每次运行程序时,答案可能(或可能不会)改变。当作者在 Visual Studio 中运行该程序时,std::cout打印了7177728,然后下一次打印5277592。随意编译和运行程序(您的计算机不会爆炸)。

警告

当您使用调试构建配置时,某些编译器(例如 Visual Studio)会将内存内容初始化为某个预设值。使用发布构建配置时不会发生这种情况。因此,如果您想自己运行上述程序,请确保您使用的是发布构建配置(请参阅第 0.9 课——配置您的编译器:构建配置以提醒您如何执行此操作)。例如,如果您在 Visual Studio 调试配置中运行上述程序,它将始终打印 -858993460,因为这是 Visual Studio 在调试配置中初始化内存的值(解释为整数)。

大多数现代编译器将尝试检测是否正在使用变量而没有给出值。如果他们能够检测到这一点,他们通常会发出编译时错误。例如,在 Visual Studio 上编译上述程序会产生以下警告:

c:\VCprojects\test\test.cpp(11) : warning C4700: uninitialized local variable 'x' used

如果你的编译器因为这个原因不允许你编译和运行上面的程序,这里有一个可能的解决方案来解决这个问题:

#include <iostream>

void doNothing(int&) // Don't worry about what & is for now, we're just using it to trick the compiler into thinking variable x is used
{
}

int main()
{
    // define an integer variable named x
    int x; // this variable is uninitialized

    doNothing(x); // make the compiler think we're assigning a value to this variable

    // print the value of x to the screen (who knows what we'll get, because x is uninitialized)
    std::cout << x;

    return 0;
}

使用未初始化的变量是新手程序员最常犯的错误之一,不幸的是,它也可能是最难调试的错误之一(因为如果未初始化的变量碰巧被分配到某个内存点,程序可能仍然可以正常运行有一个合理的值,比如 0)。

这是“始终初始化变量”最佳实践的主要原因。


未定义的行为

使用来自未初始化变量的值是我们未定义行为的第一个示例。未定义的行为(通常缩写为 UB)是执行 C++ 语言未明确定义其行为的代码的结果。在这种情况下,C++ 语言没有任何规则来确定如果您使用尚未被赋予已知值的变量的值会发生什么。因此,如果您真的这样做,将导致未定义的行为。

实现未定义行为的代码可能会出现以下任何症状:

  • 您的程序每次运行都会产生不同的结果。
  • 您的程序始终产生相同的错误结果。
  • 您的程序行为不一致(有时会产生正确的结果,有时不会)。
  • 您的程序似乎可以正常工作,但稍后会在程序中产生不正确的结果。
  • 您的程序立即或稍后崩溃。
  • 您的程序适用于某些编译器,但不适用于其他编译器。
  • 在您更改其他一些看似无关的代码之前,您的程序将一直有效。

或者,无论如何,您的代码实际上可能会产生正确的行为。未定义行为的本质是你永远不知道你会得到什么,你是否每次都会得到它,以及当你进行其他更改时该行为是否会改变。

如果您不小心,C++ 包含许多可能导致未定义行为的情况。我们将在以后的课程中每次遇到它们时指出它们。记下这些案例的位置,并确保避免它们。

规则

注意避免导致未定义行为的所有情况,例如使用未初始化的变量。
作者注

我们从读者那里得到的最常见的评论类型之一是:“你说我不能做 X,但我还是做了,而且我的程序有效!为什么?”。

有两个常见的答案。最常见的答案是您的程序实际上表现出未定义的行为,但这种未定义的行为恰好产生了您想要的结果……暂时。明天(或在另一个编译器或机器上)它可能不会。

或者,有时编译器作者在语言要求可能比需要的更严格时会随意使用这些要求。例如,标准可能会说,“你必须在 Y 之前做 X”,但编译器作者可能会觉得这是不必要的,即使你不先做 X,也要让 Y 工作。这应该不会影响正确编写的程序的运行,但可能会导致错误编写的程序仍然可以正常工作。因此,上述问题的另一个答案是您的编译器可能根本没有遵循标准!它发生了。您可以通过确保关闭编译器扩展来避免这种情况。

小测验时间

问题 #1

什么是未初始化的变量?为什么要避免使用它们?

answer: 未初始化的变量是没有被程序赋予值的变量(一般通过初始化或赋值)。使用存储在未初始化变量中的值将导致未定义的行为。

问题 #2

什么是未定义的行为,如果您做的事情表现出未定义的行为,会发生什么?

answer: 未定义的行为是执行代码的结果,其行为没有被语言很好地定义。结果几乎可以是任何东西,包括行为正确的东西。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值