【问题标题】:Default variable value默认变量值
【发布时间】:2011-08-27 07:41:02
【问题描述】:

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

例如

float x;

【问题讨论】:

  • 谢谢 - 从这里开始,是否有一个快捷方式可以为以下所有内容分配零?:float x1, x2, x3, x4, x5, y1, y2, y3, y4, y5 ;
  • x1 = x2 = x3 = x4 = x5 = y1 = y2 = y3 = y4 = y5 = 0.0f;
  • float x[6] = {};float y[6] = {};
  • "有些编译器会填充它" -- 不,它们不会;没有编译器会这样做,如果你找到一个这样做的,不要使用它。 “Visual C++ 似乎不喜欢那样......”——只有当你误读和误用它时。
  • 请注意,此答案已被重新标记为仅涉及 C++,因为这是已接受的答案所讨论的内容。 C 和 C++ 是两种截然不同的语言,有自己的标准。这个问题对应的 C 语言是 here

标签: c++


【解决方案1】:

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

C++03 标准 8.5/5 恰当地定义了每一个:

零初始化一个 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 在 cmets 中正确指出的那样,不应该 是一个非常强的词,并且可能存在完全有效的代码,在他指出时可能使用未初始化的变量在他的评论中举了一个例子。所以,我可能应该说:
除非你确切地知道自己在做什么,否则你永远不应该使用未初始化的变量。

【讨论】:

  • “永远不应该使用本地未初始化的变量”——除非您正在播种随机数生成器并将未初始化的内存用作附加的熵源。 ;-)
  • “永远不要使用”是一个强声明,我们知道所有强声明都是错误的 :) 如果你通过引用将未初始化的变量传递给函数,编译器会给你同样的警告,但这绝对是正确的代码,例如:int x; const bool ok = get_value( x ); if ( ok ) { /* use x */ }
  • @Konrad:不,即使那样。不能保证它是未初始化的内存——编译器可以根据需要将其清零。因此,除非您希望额外的熵源可能不是一个,否则您不应该这样做。
  • @Konrad:实际上,它正在造成伤害。读取一个未初始化的局部变量不会给你一些垃圾值——它是未定义的行为。有些运行时环境会在读取未初始化的局部变量时关闭程序(Visual Studio Debug Builds 就是一个突出的例子)。
  • 使用未定义的值是 UB。 不应该是完全正确的。基里尔的评论几乎是正确的——他的代码是有效的(是的,编译器可能会错误地警告它),但这只是因为他实际上不使用未初始化的值。
【解决方案2】:

这取决于。如果这是一个局部变量(一个具有自动存储持续时间的对象),它将被初始化,如果它是一个全局变量(一个具有静态存储持续时间的对象),它将被零初始化。还要检查this answer

【讨论】:

  • 它还取决于类型 - 类类型的变量会调用其默认构造函数,这可能会做任何事情(包括什么也不做)。
  • 我认为最好使用static vs. automatic 而不是global vs. local 术语
【解决方案3】:

这取决于变量的生命周期。具有静态生命周期的变量在程序启动之前总是零初始化:基本类型、enums 和指针的零初始化与分配 0 相同,并适当地转换为类型。即使变量有构造函数,在调用构造函数之前也会发生这种情况。

【讨论】:

    【解决方案4】:

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

    Default initialization is performed in some cases:

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

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

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

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

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

    意味着如果未初始化的变量是局部变量(例如,int 仅存在于函数的作用域中),则其值是不确定的(未定义的行为)。 cppreference strongly discourages the usage of uninitialized variables.

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

    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 中引入的):

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

    • 如果unsigned charstd::byte 类型的不确定值分配给unsigned charstd::byte 类型(可能是cv 限定)的另一个变量(变量的值变得不确定,但行为不是未定义的);

    • 如果unsigned charstd::byte 类型的不确定值用于初始化unsigned charstd::byte 类型的另一个变量(可能是cv 限定);

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

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

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

    为了更深入地研究不确定的值,2014 年进行了以下更改(如Shafik Yaghmour pointed out here 以及其他有用的资源):

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

    到:

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

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

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

      • 逗号的右操作数,

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

      • 弃值表达式,

        那么运算的结果就是一个不确定的值。

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

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

    最后还有zero-initialization的主题,在以下情况下执行:

    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
    }
    

    【讨论】:

    • 感谢您的回答,但我认为它可以受益于一两个具有不确定值的变量和将被零初始化的变量的示例,以及一个演示确切位置的示例使用不确定的值会导致 UB。还有一些小问题:我不会将 cppreference 称为“C++ 文档”,因为它不是标准的官方参考,并且“其默认行为是不确定的”并不是真正正确的。它的 是不确定的,评估这样的值是(除了列出的例外情况)undefined,而不是 indeterminate
    • @walnut 感谢您指出这一点,我的错误。我更正了引用“文档”的部分。我还添加了一些示例,希望能够阐明变量默认初始化的时间/方式,以及零初始化的一些示例。
    • 不幸的是,您的一些示例现在是错误的。在调用默认构造函数之前,myString 不会发生零初始化。 coordsArraypCoords 不是零初始化的,因此它们将具有不确定的值。
    【解决方案5】:

    这取决于你在哪里声明它。全局范围内的变量初始化为 0,堆栈变量未定义。

    【讨论】:

    • 在我对@Kirill 的评论中,最好说static/automatic 而不是global/local。如您所知,局部变量也可以是静态的
    【解决方案6】:

    我认为它是未定义的。我认为一些编译器在调试模式下编译时会将其初始化为零。但也可以让它成为内存中已经存在的任何东西。基本上 - 不要依赖任何一种行为。

    更新:根据 cmets - 全局变量将被初始化为零。局部变量将是任意的。

    回答你的第二个问题:

    谢谢 - 从这里开始,是否有一个快捷方式可以将以下所有内容分配为零?:float x1, x2, x3, x4, x5, y1, y2, y3, y4, y5

    你可以的

    float x[5] = {0,0,0,0,0}; float y[5] = {0,0,0,0,0};
    

    并使用x[0] 而不是x1

    【讨论】:

    • float x[5] = {0};就够了
    【解决方案7】:

    在初始化之前使用任何变量的值(请注意,静态存储持续时间对象始终会被初始化,因此这只适用于自动存储持续时间)会导致未定义的行为。这与包含 0 作为初始值或包含随机值非常不同。 UB 表示任何事情都有可能发生。在带有陷阱位的实现中,它可能会使您的程序崩溃或生成信号。多次读取也可能导致不同不可预测的值,以及任何其他可以想象(或无法想象)的行为。只是不要使用未初始化变量的值

    注意:以下是基于cmets编辑的:

    请注意,除非您可以确保类型 foo_t 没有填充位,否则这样的代码是无效的:

    foo_t x;
    int i;
    for (i=0; i<N; i++) x = (x<<1) | get_bit();
    

    尽管意图是在循环结束之前丢弃最初在x 中的“随机值”,但程序可能会在它访问x 时立即调用UB 以在第一次迭代中执行操作x&lt;&lt;1 ,因此整个程序输出无效。

    【讨论】:

    • on implementations with trap bits it might crash your program 是什么意思?你能详细点吗?什么值可以放入x(256 个之一)中,在读取程序时会导致程序崩溃?我会理解x 是否是不属于该程序的内存,但它确实如此,因为它已在堆栈上分配。
    • 示例代码和陷阱位备注未连接。 uintN_t 类型不允许有填充/陷阱位。陷阱位只是 UB 机制/原理的一个可能示例; uint8_t 无法捕获位并不会改变您正在调用 UB 的事实。
    • @R.: 那为什么你给出的例子整个程序输出都失效了?
    • 因为它会调用未定义的行为。
    • @R.: 怎么样?可能会发生什么?
    【解决方案8】:

    它可以是特定于编译器的,但通常发布版本不会将变量初始化为任何特定值,因此您可以获得内存中剩余的任何内容。但是,某些编译器在调试版本中使用某些幻数来标记特定的内存区域。

    【讨论】:

    • “可以是编译器特定的”?这不是标准在这个主题上所说的......
    • 嗯,它是未定义的。但是 MSVC 使用某些幻数来标记调试版本中的内存区域。
    • 该值不保证是变量创建前内存中的值。
    【解决方案9】:

    C++ 不实例化变量。 x 的值是当时内存中发生的任何事情。永远不要假设它的初始值。

    【讨论】:

    • 这仅适用于局部变量。全局变量是零初始化的。
    • 甚至不能保证x 值。该实现很有可能将x 放在堆栈上,并且只有在分配x 时才会增加堆栈。这意味着在第一次写入之前没有为 x 分配内存。
    • 该值不保证是变量创建前内存中的值。
    【解决方案10】:

    我只是在了解这一点,并使用简短的示例发现这可能与编译器版本有关。在学习材料中,它清楚地说全局变量默认设置为零,局部变量使用该内存位置的当前值,将变量初始化为零是一个很好的做法。然后我尝试了这段代码

    #include <iostream>
    
    using namespace std;
    
    int rezultat;
    
    main()
    {
        int localVariabla1;
    
        int nr,result;
        cout << nr << endl;
        cout << result << endl;
    
        for (int i = 0; i<3; i++)
        {
            cout << "Enter number" << endl;
            cin >> nr;
            result += nr;
        }
    cout << "Result is : " << result;
    
    } 
    

    在我编译此代码后,所有“打印”值的结果值都为零,然后进入 if 条件并且结果正确。

    【讨论】:

      猜你喜欢
      • 2012-06-25
      • 2011-10-14
      • 2011-09-22
      • 2014-12-15
      • 1970-01-01
      • 1970-01-01
      • 2013-01-17
      相关资源
      最近更新 更多