大家都知道,C/C++里,变量是分类型的。
常见的,变量可以分为局部变量和全局变量;也可以分为静态变量和非静态变量。
组合一下,有局部静态、局部非静态、全局静态、全局非静态的区分。
关于这些变量的区分,不再展开,需要了解的读者,可以网络搜索或者随便翻翻C/C++方面的书籍,都有介绍的。
今天,我们这里要看的是局部静态变量结合C++的new构建单例类。
简单来讲,就是在获取单例的接口函数中,使用局部静态变量定义一个类指针,然后让这个类指针被new初始化。最后,返回该局部静态变量。
运行时,因为局部静态变量只初始化一次,这样,当我们调用该接口时,如果指针未初始化,则其将被初始化为new的对象,之后,每次再调用该接口时,指针初始化部分将不被执行,因此达到了类只实例化一次的目的。且因为这件事是编译器搞得,所以也间接达到了多线程安全的目的。
看个例子:
class TestA {
public:
TestA(){}
~TestA(){}
private:
int a;
int b;
int c;
int d;
int e;
};
TestA * getInstance() {
static TestA* ptestA = new TestA;
return ptestA;
}
int a = 0;
void testAsss() {
a++;
int b = 1;
static TestA* ptestA = new TestA;
int c = 1;
int d = 1;
}
int main(int argc, char **argv)
{
testAsss();
testAsss();
return 0;
}
这里的getInstance就是实例写法。
不过这里,不是要研究单例。而是看到这块代码时候,有点晕,因为习惯思维原因,静态变量会在定义时候被编译器初始化,因而被这里的new给迷糊了。如果是个普通变量,可以理解编译器给变量分配地址空间,然后初始化。但是,对于new,编译器如何做到分配内存呢?如果说按照类占用空间分配内存可以做到,那么类中变量的初始化呢?这个是构造函数做的事情。
为了抹掉这个浆糊,特定写了上面这么一个简单的程序,然后分析汇编代码,看看编译器到底如何做的。
最终发现,静态变量只是让编译器在全局地址空间给其预留一份,定义时候,上述指针所在内存是被初始化为0的,也就是指针本身是NULL的。只有运行时,才会调用new,动态的分配对象内存,并将其地址给静态变量。
当第二次调用该函数时,编译器会检测,然后跳过这一行代码。
那么,编译是如何做的呢?其实,编译器会多分配一个空间,专门用于记录是否执行过静态变量所在行的代码
如果执行过后,该变量所在地址就会被写1,下次执行时,就跳过这部分,如上图红框所示。
以上是x86下的,arm下测试,也是同样的实现逻辑。
有时候,想来想去,看看汇编,再单步调试调试,就清醒了。