题记
变量是我们编写程序的必不可少的元素(可谓程序大厦的一砖一瓦),当我们声明定义一个变量后(未手动初始化),这个变量的初始值是什么,你可能会有疑惑(如果没疑惑,请确保你是已经完全清楚而不是不关心或者不在意,因为不符合预期的变量值,往往会将程序带到undefine behavior的境地);
为了全面理解变量的初始化,按照下面几个方面进行分析:变量的种类(都有哪些变量),变量在进程内存中的存储位置、变量的初始值、变量的规范初始化
变量的种类
因为变量的存储类型决定变量何时创建、何时销毁以及它的值保持多久;我们从变量的存储类型来介绍变量,按照存储类型可以分为如下几类:
auto - 自动存储类型(内存存储在堆栈)
这种类型的对象在代码块开始的地方创建,代码块结束的地方销毁,所有的本地对象拥有这种存储类型,那些使用static、extern、thread_local关键字声明的除外;
void func()
{
// create j
int j; // auto storage duration
} // destory j
int main()
{
// create i
int i; // auto storage duration
func();
} // destory i
static - 静态存储类型(内存存储在静态内存)
这种类型的对象在程序开始的时候创建,在程序结束的时候销毁了;整个程序中只存在一个该对象实例;所有声明在命名空间(全局、匿名、有名)的对象拥有该类型的存储类型。当再加上extern、static时,初始化会有些区别。
namespace {
int i; // static storage duration
}
namespace yms {
int i; // static storage duration
}
int i; // static storage duration
static int k; // static storage duration
void func()
{
static int i; // static storage duration
}
int main()
{
}
thread - 线程存储类型(内存存储在TLS)
这种类型的对象在线程开始(默认每个线程含有一个TLS表,初始态每个线程针对线程变量的TLS表项内容相同,当线程访问变量时,会将TLS表项更新,关联至线程变量的拷贝)的时候创建,在线程结束时销毁;每个线程拥有一个该对象实例;只有当使用thread_local声明对象时,对象才拥有该存储类型。当和static、extern一起使用,只会影响链接属性。
thread_local int i; // thread storage duration
int main()
{
i++; // create i for main thread (copy i to main thread from static memory)
std::thread t1 = std::thread([](){
i++; // create i for t1 thread (copy i to main thread from static memory)
} // destroy i of thread t1
);
} // destory i for main thread
dynamic - 动态存储类型(内存存储在heap)
这种类型的对象创建和销毁依赖于动态内存分配相关的函数。
变量的存储位置
类型 | 存储位置 | 怎么识别 |
---|---|---|
auto | 栈 | 代码块中声明的对象,除了使用static、extern |
static | 静态内存(data) | 所有声明在命名空间的对象,加上代码块中使用static、extern声明的对象 |
thread | TLS(每个线程的内存) | 使用thread_local声明的对象 |
dynamic | 堆,heap | 使用动态内存分配函数创建的对象 |
变量的初始值
类型 | 存储位置 | 默认初始化 |
---|---|---|
auto | 栈 | 随机值 |
static | 静态内存(data) | Zero initialization |
thread | TLS(每个线程的内存) | Zero initialization |
dynamic | 堆,heap | 随机值 |
范例汇总
void func()
{
static int i; // static storage duration: Zero initialization,0
// create j
int j; // auto storage duration: 随机值
} // destory j
namespace yms {
int i; // static storage duration: Zero initialization,0
}
int i; // static storage duration: Zero initialization,0
static int k; // static storage duration: Zero initialization,0
thread_local int ii; // thread storage duration: Zero initialization,0,发生在线程第一次使用的时候。
int main()
{
// create i
int i; // auto storage duration:随机值
func();
int* val = new int(); // val point to value is dynamic storage duration:随机值; val is auto storage
} // destory i
类/结构体的成员变量
当使用static修饰时,static存储类型,存储在静态内存中,默认使用Zero initialization。
当没有static修饰时,类似auto,如果没有显示的初始化(类的初始化列表,成员初始化),值为随机值。
class Person {
private:
int id_;
static int number_;
};
int Person::number_; // static storage duration: Zero initialization,0
struct Date {
int year;
}
int main() {
Person p; // p.id_是随机值(这个例子大概率是0,具体取决于当时分配的内存的值), p.number_为0
Date d; // d.year是随机值(这个例子大概率是0,具体取决雨当时分配内存的值)
}
变量的规范初始化
除了static、thread存储类型的变量可以不进行显示的初始化(Zero initialization,编译器初始化为0);
剩余其他的都需要显示编写初始化,否则会是随机值,使用的时候会导致进程未定义行为。
基本变量使用operator=、{}进行初始化
对于类的成员,使用类的初始化列表,或者成员operator=,{}, 当都存在时以类的初始化列表为准。
class Person {
public:
Person():id_(1){} // 类的初始化列表
private:
int id_{1}; // 成员的初始化
};
struct Date {
int year{1970}; // 或者 int year = 1970;
}
int main() {
Person p;
Date d;
int i = 0;
}
最后
一定要初始化;没有初始化引起的问题,表现为未定义行为,这种是程序最糟糕的表现(比崩溃更糟糕)。