默认变量值

新手上路,请多包涵

如果我在声明变量时没有为变量赋值,它是默认为零还是只是之前在内存中的任何值?

例如

float x;

原文由 CaptainProg 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 656
2 个回答

声明的变量可以是 零初始化值初始化默认初始化

C++03 标准 8.55 恰当地定义了每个:

对 T 类型的对象进行 零初始化 意味着:

— 如果 T 是标量类型(3.9),则将对象设置为转换为 T 的值 0(零);

— 如果 T 是非联合类类型,则每个非静态数据成员和每个基类子对象

是零初始化的;

— 如果 T 是联合类型,则对象的第一个命名数据成员为零初始化;

— 如果 T 是数组类型,则每个元素都初始化为零;

— 如果 T 是引用类型,则不执行初始化。

默认初始化 T 类型的对象意味着:

— 如果 T 是非 POD 类类型(第 9 条),则调用 T 的默认构造函数(如果 T 没有可访问的默认构造函数,则初始化是非良构的);

— 如果 T 是数组类型,则每个元素都是默认初始化的;

— 否则,对象被零初始化。

对 T 类型的对象进行 值初始化 意味着:

— 如果 T 是具有用户声明的构造函数 (12.1) 的类类型(第 9 条),则调用 T 的默认构造函数(如果 T 没有可访问的默认构造函数,则初始化是错误的);

— 如果 T 是没有用户声明的构造函数的非联合类类型,则 T 的每个非静态数据成员和基类组件都是值初始化的;

— 如果 T 是一个数组类型,那么每个元素都是值初始化的;

— 否则,对象被零初始化

例如:

 #include<iostream>
using namespace std;

static int a; //Zero Initialized
int b; //Zero Initialized

int main()
{
    int i;  //Undefined Behavior, Might be Initialized to anything
    static int j; //Zero Initialized

    cout<<"\nLocal Uninitialized int variable [i]"<<i<<"\n";

    cout<<"\nLocal Uninitialized Static int variable [j]"<<j<<"\n";

    cout<<"\nGlobal Uninitialized Static int variable [a]"<<a<<"\n";

    cout<<"\nGlobal Uninitialized int variable [b]"<<b<<"\n";

    return 0;
}

您会注意到变量 i 的结果在不同的编译器上会有所不同。 永远不要 使用这种本地未初始化的变量。事实上,如果你打开严格的编译器警告,编译器会报告一个错误。以下是键盘报告错误的方式。

 cc1plus: warnings being treated as errors
In function 'int main()':
Line 11: warning: 'i' is used uninitialized in this function

编辑:正如@Kirill V. Lyadvinsky 在评论中正确指出的那样, 不应该 是一个非常强烈的词,并且可能有完全有效的代码可能使用未初始化的变量,因为他在评论中指出了一个例子。所以,我大概应该说:

除非您确切知道自己在做什么,否则永远不应该使用未初始化的变量。

原文由 Alok Save 发布,翻译遵循 CC BY-SA 3.0 许可协议

由于当前的最佳答案是在 2011 年编写的,并且仅涉及 C++03,因此我提供了一个更新的答案以考虑 C++11 之后所做的更改。请注意,我正在删除仅在 C++03 或 C++11 之前有效的任何信息以及可以在原始资源中看到的不必要的注释。我尽可能多地引用原始规范,以避免可能导致信息不准确的不必要的重新制定。如果您有兴趣深入研究某个主题,请查阅我提供的原始资源。另外,请注意,我主要关注有关 * 默认初始化 * 未定义行为 * 零初始化的规则 因为在我看来,这些是理解变量“默认”如何表现所需的最重要方面,因为问题是问。

在某些情况下会执行默认初始化

  1. 当声明具有自动、静态或线程本地存储持续时间的变量时没有初始化程序;
  2. 当具有动态存储持续时间的对象由没有初始化程序的新表达式创建时;
  3. 当构造函数初始值设定项列表中未提及基类或非静态数据成员并且调用该构造函数时。

这个默认初始化的效果是:

  • 如果 T 是非 POD( 直到 C++11 )类类型,则考虑构造函数并针对空参数列表进行重载决议。调用选择的构造函数(默认构造函数之一)为新对象提供初始值;

  • 如果 T 是数组类型,则数组的每个元素都是默认初始化的;

  • 否则,什么都不做:具有自动存储持续时间的对象(及其子对象)被初始化为不确定的值。

这意味着如果未初始化的变量是本地变量(例如, int 仅存在于函数的范围内),则其值是不确定的(未定义的行为)。 cppreference 强烈反对使用未初始化的变量

作为旁注,即使大多数现代编译器在检测到正在使用未初始化的变量时会发出错误(在编译时),但如果您“欺骗”他们认为您可能会这样做,它们通常不会这样做以某种方式初始化变量,例如:

 int main()
{
    int myVariable;
    myFunction(myVariable);  // does not change the variable
    cout << myVariable << endl;  // compilers might think it is now initialized
}

从 C++14 开始,以下内容成立(注意 std::byte 是在 C++17 中引入的):

使用通过默认初始化任何类型的非类变量获得的不确定值是未定义的行为(特别是,它可能是陷阱表示),但以下情况除外:

  • if an indeterminate value of type unsigned char or std::byte is assigned to another variable of type (possibly cv-qualified) unsigned char or std::byte (变量的值变得不确定,但行为不是未定义的);

  • if an indeterminate value of type unsigned char or std::byte is used to initialize another variable of type (possibly cv-qualified) unsigned char or std::byte ;

  • 如果 unsigned char 或 std::byte (C++17 起)类型的不确定值来自

    • 条件表达式的第二个或第三个操作数,
    • 逗号运算符的右操作数,
    • 强制转换或转换为(可能是 cv 限定的)的操作数 unsigned charstd::byte
    • 丢弃值表达式。

有关变量默认初始化及其行为的更多详细信息,请参见 此处

为了更深入地研究不确定的值,2014 年进行了以下更改(正如 Shafik Yaghmour 在此处指出 的其他有用资源):

如果没有为对象指定初始化器,则该对象是默认初始化的;如果不执行初始化,则具有自动或动态存储持续时间的对象具有不确定的值。 [注意:具有静态或线程存储持续时间的对象是零初始化的]

至:

如果没有为对象指定初始化程序,则该对象是默认初始化的。当获得具有自动或动态存储持续时间的对象的存储时,该对象具有不确定的值,并且如果没有对该对象执行初始化,则该对象保留一个不确定的值,直到该值被替换。 [注意:具有静态或线程存储持续时间的对象初始化为零] 如果评估产生不确定的值,则行为未定义,但以下情况除外:

  • 如果通过以下评估产生不确定的无符号窄字符类型值:

    • 条件表达式 (5.16 [expr.cond]) 的第二个或第三个操作数,

    • 逗号的右操作数,

    • 强制转换或转换为无符号窄字符类型的操作数,或

    • 丢弃值表达式,

    那么操作的结果是一个不确定的值。

  • 如果一个无符号窄字符类型的不确定值是由一个简单赋值运算符的右操作数的求值产生的,该运算符的第一个操作数是一个无符号窄字符类型的左值,则一个不确定值替换左操作数所引用的对象的值.

  • 如果在初始化无符号窄字符类型的对象时通过初始化表达式的评估产生无符号窄字符类型(3.9.1 [basic.fundamental])的不确定值,则该对象被初始化为不确定值。

最后,还有在以下情况下执行的 零初始化 主题:

  1. 对于每个具有静态或线程本地存储持续时间且不受常量初始化( 自 C++14 起)的命名变量,在任何其他初始化之前。

  2. 作为非类类型和没有构造函数的值初始化类类型成员的值初始化序列的一部分,包括未提供初始化器的聚合元素的值初始化。

  3. 当使用太短的字符串文字初始化任何字符类型的数组时,数组的其余部分将被初始化为零。

零初始化的效果是:

  • 如果 T 是标量类型,则对象的初始值是显式转换为 T 的整数常量零。

  • 如果 T 是非联合类类型,则所有基类和非静态数据成员都初始化为零,并且所有填充都初始化为零位。构造函数(如果有)将被忽略。

  • 如果 T 是联合类型,则第一个非静态命名数据成员初始化为零,并且所有填充都初始化为零位。

  • 如果 T 是数组类型,则每个元素都初始化为零

  • 如果 T 是引用类型,则什么都不做。

以下是一些示例:

 #include <iostream>
#include <string>

struct Coordinates {
    float x, y;
};

class WithDefaultConstructor {
    std::string s;
}

class WithCustomConstructor {
    int a, b;

public:
    WithCustomConstructor() : a(2) {}
}

int main()
{
    int a;    // Indeterminate value (non-class)

    int& b;   // Error

    std::string myString;    // Zero-initialized to indeterminate value
                             // but then default-initialized to ""
                             // (class, calls default constructor)

    double coordsArray[2];   // Both will be 0.0 (zero-initialization)

    Coordinates* pCoords;    // Zero-initialized to nullptr

    Coordinates coords = Coordinates();

    // x: 0.0
    // y: 0.0
    std::cout << "x: " << coords.x << '\n'
        "y: " << coords.y << std::endl;

    std::cout << a.a << a.b << a.c << '\n';

    WithDefaultConstructor wdc;    // Since no constructor is provided,
                                   // calls the default constructor

    WithCustomConstructor wcs;     // Calls the provided constructor
                                   // a is initialized, while b is
                                   // default-initialized to an indeterminate value
}

原文由 giacomo-b 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题