static和extern是C/C++中和函数的声明有关的两个关键字,特别是涉及到全局变量时,所以做此总结。
1. static关键字
1.1 函数和变量声明(C/C++)
-
static全局变量:
当声明一个static全局变量,则表示静态全局变量,和其他变量一样,存放在.data(初始化了)或者.bss(未初始化)内,但只在定义它的源文件中有效,其余文件无法访问它。
-
static局部变量:
具有以下特点:
函数中声明一个static局部变量,不分配在堆或者栈上,也分配在.data(不分配在.bss)上
虽然是局部变量,整个进程生命周期都存在,不被释放。
虽然一直存在,其余函数不能访问,其余源文件更不能
会被自动初始化(所以不在.bss)上(普通局部变量不会,普通全局变量会自动初始化)
更进一步,每次调用这个变量时,其初始值都是上一次调用时修改的结果
-
static函数:
类似面向对象中private封装,其只能被所定义的源文件调用,不能再其他源文件中调用(即使包含头文件。。。。)
1.2 类成员的static(C++)
所有对象共有数据成员或者成员函数,类的静态成员,在类创建时就存在(不需要对象)。
static关键字只能用于类内部的声明,不能用于类外部的定义。
static成员函数没有this形参(因为不属于某个对象),可以直接访问static数据成员,不能直接使用非static成员。
static成员函数不能声明为const,因为const成员函数真正的含义是不修改该成员函数所属的对象,而static成员函数不属于任何对象。
同上,static成员函数不能声明为虚函数!
static不是对象的组成部分,只是类的组成部分
2. extern关键字
extern关键字常用于头文件中变量的声明,表示这个变量的定义在其他文件中的全局变量,提示编译器当遇到这个变量时,在其他文件中查找。
-
现代编译器一般采用按文件编译的方式,因此在编译时,各个文件中定义的全局变量是互相不透明的。也就是说,在编译时,全局变量的可见域限制在文件内部。
- 编译阶段是对各个文件进行单独编译,所以不会出错,链接阶段则出现了冲突,因为都是全局变量,变量名也一样
- 需要注意的是,此时两个文件并没有使用include包含,只是一同编译。(如果使用include包含,则不需extern)
结合static来说,举例说明:
a.cpp如下:
int i = 1;
b.cpp如下:
#include <iostream>
using namespace std;
extern int i;
int main() {
int a = i + 1;
cout << a << endl;
}
编译:
g++ -O0 -std=c++11 -o run ./a.cpp b.cpp
./run
结果:
2
- 如果将b.cpp中extern去掉,则因为i在a.cpp中已经定义为了全局变量,当两个文件编译后进行链接时,会产生冲突,报错如下:
/tmp/ccednFyK.o:(.data+0x0): multiple definition of `i'
- 如果将a.cpp中变量i加上static,同样出错,因为static限制了i只能在a.cpp中使用,报错如下:
b.cpp:(.text+0xa): undefined reference to `i'
值得注意的是:
C++将声明和定义分开就是为了防止重复编译导致大量不必要的开销,所以采用了“一处定义,多处声明”的策略,也就是头文件中只包含声明,将定义放在同名的代码源文件中。
Essential C++中强调了const object和inline函数是“一次定义”的例外:
inline函数因为需要编译器对其进行展开,所以需要编译器在每个函数的调用点上都需要知悉函数的定义,所以常常将inline函数置于头文件中。
-
const object因为一出文件之外便不可见(const关键字的特性),所以需要头文件中进行定义(因为include头文件,所以可以调用)
- 如果希望在其他文件中调用(非include包含)const全局变量,需要在变量的定义时显示声明为extern。这样在其他文件中,通过extern可以调用这个全局const object。(其实,非const的普通全局变量默认就是extern的)。
// a.cpp
extern const int i = 1;
int j = 2;
const int k = 3;
// b.cpp
extern const int i;
extern int j;
extern const in k;
int num = i + 1; // OK
int num = j + 1; // OK
int num = k + 1; // ERROR