ReadingLab / Discussion-for-Cpp

C++ 中文讨论区
MIT License
88 stars 63 forks source link

定义在函数体/类的内置类型有「默认初始化(default initialized)」吗? #31

Closed acgtyrant closed 9 years ago

acgtyrant commented 9 years ago

中译版第 40 页则指出:

定义在函数体内部的内置类型变量将不被初始化(uninitialized)

问题有两个:

  1. 当定义在类(class/struct)内部的数据成员为内置类型时,它也不被初始化吗?
  2. 我们可以说这个内置类型在默认初始化中不被初始化,还是只能说它不被默认初始化呢?即前者相当于名词「阶段」,后者则如同动词「初始化」,我不知道 C++ 所谓的「默认初始化」到底该理解成哪一种语义。

原版第 44 页开头即是相关文。

pezy commented 9 years ago

当定义在类(class/struct)内部的数据成员为内置类型时,它也不被初始化吗?

我猜你想问的应该是 Non-static data members 且限制为内置类型的初始化规则,这个知识点会在 7.1.4 里准确介绍。

我举例简要说明:

如:

struct C { // or class
    int n; // non-static data member
};

这里的 n 将会被初始化为 0,但它是通过 默认构造函数 进行初始化的。如果你自定义了构造函数(舍弃默认构造),则需要手动初始化,如:

struct C {
    int n;
    C() : n(0) {}
};

c++11 允许另一种方式对其进行初始化,即 brace-or-equal initializer 的方式,如:

struct C {
    int n = 0;
    C() {}
};

那么问题可能会来,如果两种初始化方式都用到了呢?如:

struct C {
    int n = 0;
    C() : n(1) {}
};

请问 n 初始化为多少?

答案是 1,两种方式并存之际,brace-or-equal initializer 会被忽略。

我们可以说这个内置类型在默认初始化中不被初始化,还是只能说它不被默认初始化呢?即前者相当于名词「阶段」,后者则如同动词「初始化」,我不知道 C++ 所谓的「默认初始化」到底该理解成哪一种语义。

抱歉,这一段没太看明白。看 @Mooophy 能否能理解。

「默认初始化」,即 default initialization,定义如下:

This is the initialization performed when a variable is constructed with no initializer.

这里面关键的两个名词的含义:initializationinitializer,我还真的找不到应该用中文怎样描述清楚。。。

Mooophy commented 9 years ago

@acgtyrant 我手里只有电子版,电子版没有页码,贴下章节号。

acgtyrant commented 9 years ago

@pezy

我知道构造函数机制,问题在于:

class A {
    int a;
}

class A {
    A() {};
    int a;
}

这两个类当然一致,关键是它们对 a 没有提供类内初始值(in-class initializer),又没在构造函数里的初始值列表(member initializer list)初始化它。那么用 class A 声明了一个对象,后者的数据成员 a 将不被初始化(unitialized)吗,即其值未定义?

第二个问题我搞明白了。default initialization就是一个名词,意思就是当对象未被显式赋予初始值时,就要对其执行的一种「行为」。但局部作用域的对象经过默认初始化后,其值是未定义的,也就是说这个「默认初始化(名词)」不保证「初始化(动词)其值」。

@Mooophy 「默认初始化」的相关章节是 2.2.1.

pezy commented 9 years ago

这两个类当然一致,关键是它们对 a 没有提供类内初始值(in-class initializer),又没在构造函数里的初始值列表(member initializer list)初始化它。那么用 class A 声明了一个对象,后者的数据成员 a 将不被初始化(unitialized)吗,即其值未定义?

这两个类当然不一致,第二个 A 的数据成员 a 未被初始化,其值是未定义;但第一个 A 的数据成员 a 是会被默认构造函数进行初始化的,其值是 0。

acgtyrant commented 9 years ago

@pezy 但章节「7.1.4 构造函数」,中译版第 236 页,原版第 263 页指出:

编译器创建的构造函数又被称为合成的默认构造函数。对大多数类来说,这个合成的默认构造函数将按照如下规则初始化类的数据成员:

  • 如果存在类内初始值,用它来初始化成员。
  • 否则,默认初始化该成员。

我想了下,在我印象中,书上只提到过默认初始化在两种情况下不初始化内置类型变量,一是在函数体内,二是在局部作用域内。如果类的内部在语法上就是局部作用域,也没有对应的特殊规则,那么应该没有疑问了:第一个 A 的数据成员 a 其值未定义。

Joyes1989 commented 7 years ago

@acgtyrant @pezy 想讨论下第一个A的数据成员a其值是否是初始化为0的: 结论是,不确定,要根据A具化的对象的类型来判断:

  1. 全局变量,则A中的a会被初始化为0
  2. 静态变量(静态全局变量+静态局部变量),A中a也会初始化为0
  3. 局部变量(函数内,块内),A中a的值是未定义的 下面是测试代码:
#include <iostream>
using namespace std;

class A{
public:
    int v;
    A() {}  // 1 -- 默认构造函数
};
A g_var;

int main(){
    A l_var;
    static A l_static;
    cout<<g_var.v<<' '<<l_var.v<<' '<<l_static.v<<endl;
    return 0;
}

执行结果:

sh-4.2$ g++ -std=c++11 -o main *.cpp                                                                                                    
sh-4.2$ main                                                                                                                            
0 4197024 0 

看到2.2.1 (p40)这部分的时候,我也存在跟题主相同的疑惑,也上网找了一些资料,这里推荐一篇自认为讲的比较清楚的博文,感觉讲的挺有道理的: http://harttle.com/2015/10/05/cpp-variable-init.html 在该文中,作者认为内置类型是否初始化为0,是和内置类型对应的变量类型或者说所在的存储空间类型决定的: 栈中的变量(函数体中的自动变量)和堆中的变量(动态内存)会保有不确定的值; 全局变量和静态变量(包括局部静态变量)会初始化为零 这里的观点和书中的观点是一致的。

pezy commented 7 years ago
class A{
public:
    int v;
    A() {}  // 1 -- 默认构造函数
};

你这个写法, v 就是未定义。 跟 A 类在哪构造没关系。

Joyes1989 commented 7 years ago

@pezy 从上文g_var的执行结果看,v是初始化为0的,所以并非所有的v都是未定义的 且按照下面的写法,其执行结果和上述程序是一致的

#include<iostream>
using namespace std;

class A{
public:
    int v;
};
A g_var;

int main(){
    A l_var;
    static A l_static;
    cout<<g_var.v<<' '<<l_var.v<<' '<<l_static.v<<endl;
    return 0;
}

执行结果:

sh-4.2$ g++ -std=c++11 -o main *.cpp                                                                                                    
sh-4.2$ main                                                                                                                            
0 4196752 0 
pezy commented 7 years ago

从上文g_var的执行结果看,v是初始化为0的,所以并非所有的v都是未定义的

什么叫做未定义(UB)?就是可以是任何值,C++ 语法不对其进行一致性保证。所以 g_var.v 的值是 0 不能说明其值就不是 UB.

class A{
public:
    int v;
};

针对这种情况, 请注意我的之前的回复: 但第一个 A 的数据成员 a 是会被默认构造函数进行初始化的,其值是 0。

也就是说, 你得这么写, 才可以确保值为0:A l_var{};

~你代码中其他的写法, 其值皆为 UB。~

Joyes1989 commented 7 years ago

也就是说, 你得这么写, 才可以确保值为0:A l_var{};

你代码中其他的写法,其值皆为 UB。

@pezy 谢谢回复,按照上面的讨论我做了如下验证

#include <iostream>
using namespace std;

class A{
public:
    int *v;
};
A g_var;

int main() {
    A l_var{};
    static A l_static;
    cout<<g_var.v<<' '<<l_var.v<<' '<<l_static.v<<endl;
    return 0;
}

执行结果:

sh-4.2$ g++ -std=c++11 -o main *.cpp                                                                                                    
sh-4.2$ main                                                                                                                            
0 0 0

关于结果有如下两个疑问,希望帮忙解答下

  1. 关于A l_var{};的理解,的确如pezy所说,这里可以正确初始化为0: cp中指出:定义在函数内部的内置类型变量将不被初始化(Uninitialized). 所以我对这里v初始化为0存在一定的疑惑: A l_var{}; 和 A l_var; 这两种定义方式的本质区别在哪里,在我的理解中一直认为二者是一样的(都会调用默认构造函数,都不会初始化v,难道说v不算是局部变量?), 这里希望大神能够给出详细的解答,先谢下

  2. 关于 "你代码中其他的写法,其值皆为 UB" 这个说法,我故意在代码中使用int v的方式来验证: 结果显示,全局变量和静态变量中的v都正确初始化为0了(我认为这里使用int 是能够区分UB和初始化为0(NULL)的), 所以我理解:对于全局变量和静态变量,c++是能够保证其内部内置类型成员被正确初始化为0的,并非UB, 这点也请pezy确认下

pezy commented 7 years ago

这两种定义方式的本质区别在哪里

不一样, 前者明确是 zero-initialized, 后者仅仅是调用缺省构造函数(缺省构造函数的行为可参考这里)。

难道说v不算是局部变量?

v 是一个成员变量。

所以我理解:对于全局变量和静态变量,c++是能够保证其内部内置类型成员被正确初始化为0的,

是的, 这一点我理解有偏差, 对于静态和全局变量而言, 始终会发生 zero initialization (包括其全部成员。)

For all other non-local static and thread-local variables, Zero initialization takes place. --- http://en.cppreference.com/w/cpp/language/initialization

Joyes1989 commented 7 years ago

@pezy thx