C/C++拾遗之实例分析static的作用

C语言中static的作用

1.隐藏变量或函数。被static修饰的变量或者函数,对其他文件是不可见的
如果变量或函数没有被static修饰,那么一个文件可以通过extern关键字来引用在另一个变量中声明的变量或函数。如:

文件a中的代码

1
2
3
4
5
6
7
//static_a.cpp
static int a = 10;

int func()
{
return 5;
}

文件b中的代码:

1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>

extern int func();
extern int a;

int main(void)
{
std::cout << func() << std::endl;
std::cout << a << std::endl;

return 0;
}

当使用g++ -o static static_a.cpp static_b.cpp进行编译时,会得到如下错误:

1
2
3
/tmp/cc3q1SaL.o: In function `main':
static_b.cpp:(.text+0x24): undefined reference to `a'
collect2: error: ld returned 1 exit status

也就是说对于static_b.cpp文件来说,static_a.cpp中的a是不可见的,因为static关键字隐藏了它。

2.保持变量生命周期直到整个程序执行结束才结束
一般情况下,局部变量会被存储在栈区,栈区的内存会在离开该语句块时由系统自动回收。但static修饰的变量却会被存储在内存布局的.data区段,该段内存中的变量值会在整个程序运行期间保持。如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

#include <iostream>

int func()
{
static int a = 3;
return a--;

}
int main(void)
{
std::cout << func() << std::endl;
std::cout << func() << std::endl;
return 0;
}

上例输出结果为:

1
2
3
2

即func()函数的中的static变量a的值一直在保存着。第一次调用时是3,然后再次调用时并没有再次将a初始化为3,而是上次的2。

3.static修饰的变量未初始化时,会被编译器自动初始化为0
因为static修饰的变量像全局变量一样,当未初始化时,会被存放在.bss内存区段,而不是.data区域,.bss内存区域的变量会在程序执行前被自动初始化为0(详见实例分析C++内存布局),.bss段的内存中的内容具体是在编译时进行的0填充还是运行时不用管,总之是在程序运行前。如以下代码:

1
2
3
4
5
6
7
8
9
10
//file name: bss_ini.cpp
static int a;
int b;

int main(void)
{
static int c;
int d;
return 0;
}

其中全局变量b,全局静态变量a和局部变量c都是未初始化的,它们会被存放在.bss段,查看编译之后的可重定位的目标文件的section大小:
size bss_ini.o

1
2
text    data     bss     dec     hex filename
67 0 12 79 4f bss_ini.o

可见bss段刚好占12个字节=三个int所占的字节大小。
再来查看.bss段中的变量值:
反汇编命令:objdump -CS -s -j .bss bss_ini

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
bss_ini:     file format elf64-x86-64


Disassembly of section .bss:

0000000000601038 <__bss_start>:
601038: 00 00 add %al,(%rax)
...

000000000060103c <b>:
60103c: 00 00 00 00 ....

0000000000601040 <a>:
601040: 00 00 00 00
0000000000601044 <main::c>:
601044: 00 00 00 00

可以看到b, a, c的值全是0,注意输出中是以16进制表示,所以是8个0,共32位二进制。

C++中static的作用

由于类的静态数据成员要在程序一开始运行时就已经存在,但函数却是在程序运行过程中被调用,所以类的静态数据成员不能在任何函数内分配空间和初始化,当然可以在类中声明,而定义放在类外。因为类的声明只是声明一个类的“尺寸和规格”,而并不进行实际的内存分配,所以只能在类的内部声明,并在类的外部定义并初始化(当然可以在类内直接声明成const类型并初始化)。
已经定义的静态数据成员引用方式为:<类名>::<静态成员员>
1.static修饰类中的成员函数或变量时,类的所有对象及类本身共用该成员变量或成员函数
类的所有实例共用静态数据成员(包括成员变量和成员函数)。类的静态变量和静态函数可以直接被类引用,而不需要类对象来引用。注意类中使用static声明的变量,只是进行了声明,并没有定义,需要在类的外部(也必须是其他任何函数函数的外部进行定义)定义,或者在类的内部定义成const类型的常量。如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>

class A {
public:
static int a; //声明静态数据成员a
const static int c = 3; //在类中声明并定义const static类型的变量(其实是常量)
static void func(int i)
{
static int b = 100;
std::cout << "a = " << i << std::endl;
std::cout << "b = " << b++ << std::endl;
}
};

int A::a = 5; //必须在类外部及main函数外部进行定义并初始化
int main(void)
{
std::cout << "A::c = " << A::c << std::endl;
A::func(A::a);
A obj_a, obj_b;
A::a = 7;
obj_a.func(obj_a.a);
obj_a.a = 9;
obj_b.func(obj_b.a);
return 0;
}

输出结果为:

1
2
3
4
5
6
7
A::c = 3
a = 5
b = 100
a = 7
b = 101
a = 9
b = 102

根据a和b的输出结果可见类的所有对象及类本身共有静态成员变量和静态成员函数,且类可以直接引用静态成员函数和静态成员变量。

类的静态成员注意事项

1.类的静态成员函数属于整个类而非类的对象,所以它没有this指针,这就导致它仅能访问类的静态数据和静态成员函数。
2.不能将静态成员函数定义为虚函数
3.静态常量声明只可以是整型,即static const类型的成员变量只能是整型,或者使用constexpr来定义常量表达式,具体看这里
http://stackoverflow.com/questions/370283/why-cant-i-have-a-non-integral-static-const-member-in-a-class
如:

1
2
3
4
5
class Ba {
public:
const static int a = 1;
constexpr static double b = 2.0;
}