今天来介绍c++中的static关键字,根据上下文有两种意思,一种是在类class或者结构体外使用static;另一种是在类class或者结构体内使用。总结一下:类外的static修饰的符号在link阶段是局部的,也就是它只对定义它的编译单元(.obj)可见。而类和结构体里面的static,表示这部分内存是这个类的所有实例共享的,简单来说,就算你实例化了很多次这个类或结构体,但那个静态(static)变量只会有一个实例(就是只有一个),类里面的静态方法也是一样,静态方法里没有该实例的指针(this)。之后会更详细的讲static在类或结构体里的意思—作用域(scope)。
一、类或者结构体外的static
1、正确使用static的方式
我们在一个static.cpp文件里面,只定义一个static变量。
除了前面的static,它看起来就跟普通的变量一样。这个static什么意思呢?它表示这个变量在link的时候只在这个编译单元(.obj)里可见。
(如果你不清楚c++的编译和链接link的原理,这里简要叙述下。
第一步:预处理
C/C++语言最常见的预处理就是将所有的“#define”删除,并且展开所有的宏定义。而预处理其实还包括:处理所有的条件编译指令,比如“#if”、处理“#include”预编译指令、删除所有的注释、添加行号和文件名标识等。
第二步:编译
编译会将源代码由文本形式转换成机器语言,编译过程就是把预处理完的文件进行一系列词法分析、语法分析、语义分析以及优化后生成相应的汇编代码文件。
编译后的.s也是ascii码文件。因为汇编也是人能看懂的文字编码形式,所以.s汇编文件也是ASCII码文件。
第三步:汇编
汇编过程调用汇编器AS来完成,是用于将汇编代码转换成机器可以执行的指令,每一个汇编语句几乎都对应一条机器指令。(非底层的程序员不需要考虑)
汇编后的.o文件是纯二进制文件。因为.o中放的是纯二进制的机器指令,所以我们打开后看不懂。
ASCII的源码被汇编为能被CPU执行的机器指令,.o文件中放的就是机器指令。但是.o文件还无法运行,需要链接后才能运行。
第四步:链接
链接是将所有的.o文件和库(动态库、静态库)链接在一起,得到可以运行的可执行文件(Windows的.exe文件或Linux的.out文件)等。它的工作就是把一些指令对其他符号地址的引用加以修正。链接过程主要包括了地址和空间分配、符号决议和重定向。)
static变量或者函数表示在link到它实际的定义时,linker不会在这个编译单元.obj外面找它的定义。我们来看另一个cpp文件(也就是另一个编译单元),这儿是main函数的cpp文件。我们创建一个跟前面静态变量名字一样的全局变量并赋值为10,然后输出它。
发现没有任何问题。
2、没使用static导致变量的重复定义
当我们把static.cpp文件里的static去掉。编译运行发现有一个重复定义的变量,如下图:
可以看到在linking阶段有个link错误。因为s_Variable已经在另一个编译单元里定义了,所以两个全局变量的名字不能一样。
3、如何使用另外cpp文件中的变量-extern
一种解决办法是把这个变成另一个的引用,就是把赋值去掉再加上extern关键字如下图:
加上extern关键字,它就会在另外的编译单元里找s_Variable的定义,这被称为external linkage或者external linking,这样运行代码就正常了。因为他是static.cpp文件中变量的引用。
但是加入我在变量声明前面加上static,那外部就找不到了,如下图:
这有点像在class里面声明私有(private)成员,其他的编译单元不能访问s_Variable。
linker在全局作用域下找不到它,所以就出现了上面的无法解析的外部符号,也就是因为linker在任何地方都找不到s_Variable的定义,因为我们把这个变量标记成了“私有”的。
4、函数和变量情况是一样的
和之前一样,我们来声明一个函数。
main里面也声明了个有同样签名、没有返回值的函数。
调试就会发现,多了一个错误,在链接阶段就出现错误重复定义。如下图: