在C和C++中,static都有两种基本的含义,并且这两种含义经常是互相有冲突的:
1) 在固定的地址上分配,也就是说对象是在一个特殊的静态数据区上创建的,而不是每次
函数调用时在堆栈上产生的。这也是静态存储的概念。
2) 对一个特定的编译单位来说是本地的(就像我们在后面将要看到的,这在C++中包括类
的范围)。这里static控制名字的可见性,所以这个名字在这个单元或类之外是不可见的。这也
描述了连接的概念,它决定连接器将看到哪些名字。
1.函数内部的静态变量
通常,在函数体内定义一个变量时,编译器使得每次函数调用时堆栈的指针向下移一个适
当的位置,为这些内部变量分配内存。如果这个变量有一个初始化表达式,那么每当程序运行
到此处,初始化就被执行。
然而,有时想在两次函数调用之间保留一个变量的值,我们可以通过定义一个全局变量来
实现这点,但这样一来,这个变量就不仅仅受这个函数的控制。C和C++都允许在函数内部创
建一个static对象,这个对象将存储在程序的静态数据区中,而不是在堆栈中。这个对象只在
函数第一次调用时初始化一次,以后它将在两次函数之间保持它的值。
如果没有为一个预定义类型的静态变量提供一个初始值的话,编译器会确保在程序开始时为它初始化为0(将转化为适当的类型)。
2.函数体内部的静态对象
用户自定义的静态变量同一般的静态对象的规则是一样的,而且它同样也必须有初始化操作。
但是,零赋值只对预定义类型有效,用户自定义类型必须用构造函数来初始化。因此,如果我们
在定义一个静态对象时没有指定构造函数参数,这个类就必须有缺省的构造函数。
tips:
在C++中,全局静态对象的构造函数是在main()之前调用的,所以我们现在有了一个在进
入main()之前执行一段代码的简单的、可移植的方法,并且可以在退出main()之后用析构函数
执行代码。在C中要做到这一点,我们不得不熟悉编译器开发商的汇编语言的开始代码。
3.控制连接
一般情况下,在文件范围内的所有名字(既不嵌套在类或函数中的名字)对程序中的所有
编译单元来说都是可见的。这就是所谓的外部连接,因为在连接时这个名字对连接器来说是可
见的,外部的编译单元、全局变量和普通函数都有外部连接。
有时我们可能想限制一个名字的可见性。想让一个变量在文件范围内是可见的,这样这个
文件中的所有函数都可以使用它,但不想让这个文件之外的函数看到或访问该变量,或不想这
个变量的名字与外部的标识符相冲突。
在文件范围内,一个被明确声明为static的对象或函数的名字对编译单元(一个.cpp文件)来说是局部变量;这些名字有内部连接。这意味着我们可以在其他的编译单元中使用同样的名字,而不会发生名字冲突。
内部连接的一个好处是这个名字可以放在一个头文件中而不用担心连接时发生冲突。那些
通常放在头文件里的名字,像常量、内联函数( inline function),在缺省情况下都是内部连接
的(当然常量只有在C + +中缺省情况下是内部连接的,在C中它缺省为外部连接)。注意连接只
引用那些在连接/装载期间有地址的成员,因此类声明和局部变量并没有连接。
• 冲突问题
下面例子说明了static的两个含义怎样彼此交叉的。所有的全局对象都是隐含为静态存储类,
所以如果我们定义(在文件范围)
int a=0;
则a被存储在程序的静态数据区,在进入main()函数之前,a即已初始化了。另外,a对全局都是可
见的,包括所有的编译单元。用可见性术语,s t a t i c(只在编译单元内可见)的反义是e x t e r n,它
表示这个名字对所有的编译单元都是可见的。所以上面的定义和下面的定义是相同的。
extern int a=0;
但如果这样定义:
static int a=0;
我们只不过改变了a的可见性,现在a成了一个内部连接。但存储类型没有改变—对象总是驻
留在静态数据区,而不管是s t a t i c还是e x t e r n。
一旦进入局部变量,s t a t i c就不会再改变变量的可见性(这时e x t e r n是没有意义的),而只
是改变变量的存储类型。
对函数名,s t a t i c和e x t e r n只会改变它的可见性,所以如果说:
extern void f();
它和没有修饰时的声明是一样的:
void f();
如果定义:
static void f();
它意味着f ( )只在本编译单元内是可见的,这有时称作文件静态。
4.其他的存储类型指定符
我们会看到s t a t i c和e x t e r n用得很普遍。另外还有两个存储类型指定符,这两种用得较少。
一个是a u t o ,人们几乎不用它,因为它告诉编译器这是一个局部变量,实际上编译器总是可以从
变量定义时的上下文中判断出这是一个局部变量。所以a u t o是多余的。还有一个是r e g i s t e r,它
也是局部变量,但它告诉编译器这个特殊的变量要经常用到,所以编译器应该尽可能地让它保
存在寄存器中。它用于优化代码。各种编译器对这种类型的变量处理方式也不尽相同,它们有
时会忽略这种存储类型的指定。一般,如果要用到这个变量的地址, r e g i s t e r指定符通常都会被
忽略。应该避免用r e g i s t e r类型,因为编译器在优化代码方面通常比我们做得更好。
相关文章
- 04-05C,C++中的static
- 04-05c++ 中lambda
- 04-05[Effective Modern C++] Item 6. Use the explicitly typed initializer idiom when auto deduces undesired types - 当推断意外类型时使用显式的类型初始化语句
- 04-05[Effective Modern C++] Item 4. Know how to view deduced types - 知道如何看待推断出的类型
- 04-05专注于C模板中的类型子集
- 04-05LeetCode - 13. Roman to Integer - 思考if-else与switch的比较 - ( C++ ) - 解题报告
- 04-05Libev源码分析01:Libev中的监视器结构(C结构体实现继承)
- 04-05[C++]类成员返回语句 return *this 的理解
- 04-05C++ 重写虚函数的代码使用注意点+全部知识点
- 04-05C++ 函数参数中&和&&区别