章节目录

未初始化的变量及未定义的行为

本节阅读量:

未初始化的变量

与某些编程语言不同,C/C++不会自动将大多数变量初始化为给定的值(如零)。因此,当变量被分配的内存地址来存储数据时,该变量的默认值是该内存地址中已经存在的任何(垃圾)值!尚未给定值的变量称为「未初始化变量」。

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#include <iostream>

int main()
{
    // 定义一个整形变量x
    int x; // 变量x未被初始化
    
    // 将x的值打印到屏幕上
    std::cout << x << '\n'; // 打印出来的值是未知的

    return 0;
}

在这种情况下,计算机将把一些未使用的内存分配给x。然后,它将在该内存位置的值发送到std::cout,将该值解释为整数并打印。但它将打印什么值?答案是“不知道!”,并且每次运行程序时,答案可能会(也可能不会)更改。

大多数现代编译器都会尝试检测是否在未给定值的情况下使用变量。如果它们能够检测到这一点,则通常会发出编译时警告或错误。例如,在Visual Studio上编译上述程序时产生以下警告:

1
c:\VCprojects\test\test.cpp11):警告C4700:使用了未初始化的局部变量“x

如果编译器不允许您编译和运行上述程序(例如,因为它将该问题视为错误),则有一种可能的解决方案可以解决此问题:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#include <iostream>

void doNothing(int&) // 暂时不需要理解&的用途,这里使用一点技巧,让编译器认为x已经被使用
{
}

int main()
{
    int x; // 定义一个未初始化的整形变量x

    doNothing(x); // 让编译器认为x会被写入数据

    // 将x的值打印到屏幕上
    std::cout << x << '\n'; // 打印出来的值是未知的

    return 0;
}

使用未初始化的变量是初学者最常见的错误之一,不幸的是,它也可能是调试问题程序时最具挑战性的错误之一(因为如果未初始化变量碰巧分配给内存中具有合理值的点,如0,则程序可能无论如何都会运行良好)。

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


未定义的行为

使用未初始化变量的值是未定义行为的第一个例子。一些代码,C++语言并未规定执行的结果,执行后的结果称为未定义的行为(Undefined behavior,通常缩写为UB)。C++语言没有任何规则,来确定如果使用尚未给定已知值的变量的值会发生什么。因此,如果确实这样做,将导致未定义的行为。

实现未定义行为的代码会出现以下问题:

  1. 程序每次运行时都会产生不同的结果。
  2. 程序始终会产生相同的错误结果。
  3. 程序的行为不一致(有时产生正确的结果,有时不产生)。
  4. 程序似乎正在工作,但稍后在程序中产生错误的结果。
  5. 程序立即或稍后崩溃。
  6. 程序可以在某些编译器上工作,但不能在其他编译器上工作。
  7. 更改了一些其他看似无关的代码,程序却无法正常运行。

或者,代码实际上可能会产生正确的行为。

C++包含许多情况,如果不小心,可能会导致未定义的行为。我们将在以后的课程中指出这些,如果遇到它们。注意这些情况的位置,并避免它们。


由实现定义的行为和未指定行为

由实现定义的行为(Implementation-defined behavior)意味着某些语法的行为由实现(编译器)定义。这样的行为必须一致并记录在案,但不同的编译器可能会产生不同的结果。

让我们看一个实现定义的行为的简单示例:

1
2
3
4
5
6
7
8
#include <iostream>

int main()
{
	std::cout << sizeof(int); // 打印一个int占多少内存

	return 0;
}

在大多数编译器上,这将输出4,但在一些编译器上,它输出2。

未指定的行为(unspecified behavior)几乎与实现定义的行为相同,行为由实现决定,但实现不需要记录该行为。

为了避免实现定义的和未指定的行为,意味着如果在不同的编译器上编译,程序可能无法按预期工作(甚至更改项目设置后,可能也无法在同一编译器上产出相同的结果!)


1.4 iostream:cout、cin和endl简介

上一节

1.6 关键字和变量命名规则

下一节