存储持续性、作用域和链接性

作用域scope:描述了名称在文件(翻译单元)的多大范围内可见。

链接性linkage:描述了名称在不同单元间共享。链接为外部的名称可以在文件间共享,链接性为内部的名称只能由一个文件中的函数共享。自动变量的名称是没有链接性,因为他们不能共享。

在名字空间中声明的变量的作用域为整个名称空间,因此全局作用域是名称空间作用域的特例。

寄存器变量:是一种形式的自动变量,因此其存储持续性为自动,作用域为局部,但没有链接性。关键字register提醒编译器,用户希望它通过使用CPU寄存器,而不是堆栈来处理特定的变量,从而提供变量访问的快速。这种提醒并不意味着编译器一定满足这种请求。比如寄存器已经被占用,或者寄存器无法存储所请求的类型。如果变量被存储在寄存器中,那么他就没有内存地址,因此不能将地址操作符用于寄存器变量。

void gromb(int *)  

int main()  
{  
    int x;  
    register int y ;  
   gromb(&x);  
   gromb(&y);    //不能使用取地址符  
  ...  
5种变量存储方式
存储描述 持续性 作用域 链接性 如何声明
自动 自动 代码块 可使用auto
寄存器 自动 代码块 在代码块中使用register
静态,无链接 静态 代码块 在代码块中,使用static
静态,外部链接性 静态 文件 外部 在函数外面(全局变量)
静态,内部链接性 静态 文件 内部 在函数外面,使用static(全局静态变量)

 参见:

四种不同对象的生存方式(栈、堆、全局、局部静态)

补充:由于字符串文字是内部链接对象(因为两个具有相同名称但出于不同模块的字符串,是两个完全不同的对象),所以你不能使用它们来作为模板实参(C++ template —— 模板基础(一):4.3 非类型模板参数的限制);

template <char const* name>
class MyClass
{
};
MyClass<"hello"> x;   // ERROR:不允许使用字符文字"hello"
//////////////////////////////////////////////////////////
//另外,你也不能使用全局指针作为模板参数:
template <char const* name>
class MyClass
{
    ...
};
char const* s = "hello";
MyClass<s> x;   // ERROR:s是一个指向内部链接对象的指针
///////////////////////////////////////////////////////////
//然而,你可以这样使用:
template <char const* name>
class MyClass
{
    ...
};
extern char const s[] = "hello";
MyClass<s> x;   // OK
//全局字符数组s由“hello”初始化,是一个外部链接对象。

变量内部/外部链接性的一个例子:

int global=1000;            //static静态持续性,外部链接,可在文件外面使用  
static int one_file=50;    //static静态持续性,内部链接,只能在当前文件中使用  
int main()  
{  
   ……  
}  
void fun1(int n)  
{  
   static int count = 0;//static静态持续性,无链接性,只在函数中使用  
   int llama=0;       //自动变量
……  
}  
void fun2(int q)  
{  
  ……  
}  

这里的global具有外部链接性,因此,在另一个文件file2.cpp可以直接使用他 ,但是在使用他之前,需要提供变量的external声明,否则会导致重复定义;

//file2.cpp  
#include "iostream"  
using namespace std;  
external int global;     //外部变量使用前必须声明  
  
void showExternal()  
{  
   cout << global <<endl;  
}  

这里尤其要注意的是声明的两种形式:

  • 定义声明(defining declaration)或简称为定义definition,它主要给变量分配存储空间。定义只能进行一次;
  • 重新声明或称为引用声明(referencing declaration)简称为声明declaration,它不给变量分配空间,只是扩展他得作用域,因此不能在引用声明中初始化变量。声明可以多次;
external double warning = 0.5//不能给引用声明初始化  

仅当声明将变量分配存储空间时,即定义声明,才能在声明中初始化变量,如上面的:

int global=1000;

 

说明符和限定符

除了auto,register,static,extern之外,还有const,volatile,mutable

const修饰符

该关键字作用是改善编译器的优化能力,在读取某个变量时,将这个值缓存到寄存器中。这种优化假设变量的值在两次使用之间不会变化。

而使用volatile关键字仅针对const起作用,他表明,即使程序代码没有对内存单元进行修改,其值也可能发生变化。例如,一个指针指向某个硬件位置,其中包含了来自串行端口的时间或信息。在这种情况下,硬件(而不是程序)可能修改其中的内容。或者两个程序可能相互影响,共享数据。volatile关键字修改变量,则是告诉编译器不要进行编译优化,每次都去变量的内存地址取变量的值

再谈const(内部链接性)

  • 假设将变量(int globalVal=100;)或常量(extern const int states=50;)放在头文件中,多个cpp文件包含该头文件,这会导致重复定义,因为globalVal和带extern的const常量是外部链接性的;只能将常量放在一个cpp文件中,而其他cpp文件必须使用extern来提供引用声明,注意,此时只有未使用extern关键字的声明才能进行初始化;
  • 假设将常量(const int states=50;)放在头文件中,多个cpp文件包含该头文件,这并不会导致重复定义常量,因为不带extern的const常量是内部链接性的。
  • 如果外部定义的const数据的链接性是内部的,那么可以在所有文件中使用相同的声明。内部链接性还意味着,每个文件都有自己的一组常量,而不是所有文件共享一组常量。每个定义都是所属文件私有的,这就是能够将常量定义放在头文件中的原因(const常量的内部链接性)

如果出于某种原因,程序员希望某个常量的链接性为外部的,则可以使用extern关键字来覆盖默认的内部链接性。

// 定义声明 普通外部变量,外部链接性,无需在定义处使用extern,其他编译单元也可以链接到该变量;
int globalVal = 100;
// 定义声明 常量(const),const修饰符带有内部链接性,如果其他编译单元想要链接到该常量,就需要在定义处 添加extern来覆盖const默认的内部链接性
extern const int states = 50;  

这点与定义常规外部变量不同,常规外部变量定义时无需extern关键字。但这里需要,切记!

函数和链接性

  • 函数的存储持续性都是静态的,即在整个程序执行期间都是一直存在。
  • 默认情况下,函数的链接性为外部的,即可以在文件(多个编译单元)之间共享。要让程序在另一个文件中查找函数,该文件必须作为程序的组成部分被编译,或者是有链接程序搜索的库文件。
  • 还可以使用关键字static将函数的链接性设置为内部的,使之只能在一个文件中使用。如果该文件的函数原型指出该函数是静态的,则编译器将只在该文件中查找函数定义;否则,编译器(包括链接程序)将在所有的程序文件中查找。如果找到两个定义,编译器将发出错误消息,因为每个外部函数只能有一个定义,如果在程序文件中没有找到,编译器将在库中搜索。这意味着如果定义了一个与库函数同名的函数,编译器将使用程序员定义的版本,而不是库函数。

语言链接性

  • 链接程序要求每个不同的函数都有不同的符号名。在C语言中,一个名称只对应一个函数,因此很容易实现。为满足内部需求,C语言编译器,只能将spiff这样的函数翻译为_spiff,这种方法叫C语言链接性
  • 但C++中,同一个名称的函数可能对应多个函数,必须将这些函数翻译成不同的符号名。因此,C++编译器指向名称矫正或名称修饰,为重载函数生成不同的符号名称。例如spiff(int )转换成_spiff_i,spiff(double,double)转化成_spiff_d_d。这种称为C++语言链接

链接程序寻找与C++函数调用匹配的函数时,使用的方法与C语言不同,但如果要在C++程序中使用C库中预编译的函数,将出现什么情况?

假设

spiff(22);//调用C库中的spiff(int)函数

它在C语言库中的符号名为_spiff,但C++查询约定的符号名_spiff_i。为解决这类问题,可以用函数原型来指出要使用的约定。

extern "C" void spiff(int );  //use c protocol for name look-up  
extern void spoff(int) //use c++ protocol for name look-up  
extern "C++" void spaff(int); //use c++ protocol for name look-up  

动态分配

  • 通常,编译器使用3块独立的内存,一块用于静态变量(可能再细分,静态存储区),一块用于自动变量(栈)。一块用于动态存储(堆);
  • 请注意,使用new来设置指针的语句必须位于函数中,如果在函数外,就是静态存储,而静态存储,只能使用常量表达式来初始化静态存储变量

 

static 用来控制变量的存储方式和可见性

  函数内部定义的变量,在程序执行到它的定义处时,编译器为它在栈上分配空间,函数在栈上分配的空间在此函数执行结束时会释放掉,这样就产生了一个问题: 如果想将函数中此变量的值保存至下一次调用时,如何实现? 最容易想到的方法是定义一个全局的变量,但定义为一个全局变量有许多缺点,最明显的缺点是破坏了此变量的访问范围(使得在此函数中定义的变量,不仅仅受此 函数控制)。需要一个数据对象为整个类而非某个对象服务,同时又力求不破坏类的封装性,即要求此成员隐藏在类的内部,对外不可见。

  static 的内部机制

  静态数据成员要在程序一开始运行时就必须存在。因为函数在程序运行中被调用,所以静态数据成员不能在任何函数内分配空间和初始化。这样,它的空间分配有三个可能的地方,一是作为类的外部接口的头文件,那里有类声明;二是类定义的内部实现,那里有类的成员函数定义;三是应用程序的 main()函数前的全局数据声明和定义处。

  静态数据成员要实际地分配空间,故不能在类的声明中定义(只能声明数据成员)。类声明只声明一个类的“尺寸和规格”,并不进行实际的内存分配,所以在类声明中写成定义是错误的。它也不能在头文件中类声明的外部定义,因为那会造成在多个使用该类的源文件中,对其重复定义。

注意:“声明”和“定义”是两个不同的概念,“定义”会给变量分配内存,声明仅仅只是告知编译器有此变量,并不会分配内存。(个人理解,不准确)

  static 被引入以告知编译器,将变量存储在程序的静态存储区而非栈上空间,静态数据成员按定义出现的先后顺序依次初始化,注意静态成员嵌套时,要保证所嵌套的成员已经初始化了。消除时的顺序是初始化的反顺序。

  static 的优势

  可以节省内存,因为它是所有对象所公有的,因此,对多个对象来说,静态数据成员只存储一处,供所有对象共用。静态数据成员的值对每个对象都是一样,但它的值是可以更新的。只要对静态数据成员的值更新一次,保证所有对象存取更新后的相同的值,这样可以提高时间效率。引用静态数据成员时,采用如下格式:

  <类名>::<静态成员名>

  如果静态数据成员的访问权限允许的话(即 public 的成员),可在程序中,按上述格式来引用静态数据成员。

Ps:

  (1) 类的静态成员函数是属于整个类而非类的对象,所以它没有this指针,这就导致了它仅能访问类的静态数据和静态成员函数。

  (2) 不能将静态成员函数定义为虚函数。

  (3) 由于静态成员声明于类中,操作于其外,所以对其取地址操作,就多少有些特殊,变量地址是指向其数据类型的指针,函数地址类型是一个“nonmember 函数指针”。

  (4) 由于静态成员函数没有 this 指针,所以就差不多等同于 nonmember 函数,结果就产生了一个意想不到的好处:成为一个 callback 函数,使得我们得以将 c++ 和 c-based x window 系统结合,同时也成功的应用于线程函数身上。

  (5) static 并没有增加程序的时空开销,相反她还缩短了子类对父类静态成员的访问时间,节省了子类的内存空间。

  (6) 静态数据成员在<定义或说明>时前面加关键字 static。

  (7) 静态数据成员是静态存储的,所以必须对它进行初始化

  (8) 静态成员初始化与一般数据成员初始化不同:

    • 初始化在类体外进行,而前面不加 static,以免与一般静态变量或对象相混淆;
    • 初始化时不加该成员的访问权限控制符 private、public;
    • 初始化时使用作用域运算符来标明它所属类;
    • 所以我们得出静态数据成员初始化的格式:
    • <数据类型><类名>::<静态数据成员名>=<值>

   (9) 为了防止父类的影响,可以在子类定义一个与父类相同的静态变量,以屏蔽父类的影响。这里有一点需要注意:我们说静态成员为父类和子类共享,但我们有重复定义了静态成员,这会不会引起错误呢?不会,我们的编译器采用了一种绝妙的手法:name-mangling 用以生成唯一的标志。

 

posted @ 2014-10-08 19:18  小天_y  阅读(1448)  评论(1编辑  收藏  举报