C/C++中存储类型修饰符的区别(auto、static、register、extern、volatile、restrict)

一、局部变量和全局变量:

(1)局部变量:局部变量也叫自动变量,它声明在函数开始,生存于栈,它的生命随着函数的返回而结束。

#include 

int main(void)
{
    auto int i = 9; 	//声明局部变量的关键字是 auto; 因可以省略,所以几乎没人使用

    printf("%d\n", i);  
    getchar();
    return 0;
}

(2)全局变量:全局变量声明在函数体外,一般应在函数前。每个函数都可以使用它,不过全局变量应尽量少用。

#include 

void add(void);
void mul(void);

int gi = 3; 		//全局变量(声明在函数外)

int main(void)
{    
    printf("%d\n", gi); //输出的是 3

    add();
    printf("%d\n", gi); //输出的是 5

    mul();
    printf("%d\n", gi); //输出的是 10
      
    getchar();
    return 0;
}

void add(void) {
    gi += 2;
}

void mul(void) {
    gi *= 2;
}
全局变量会被初始化为空, 而局部变量在没有赋值前是一个垃圾值:

#include 

int gi;			//全局变量

int main(void)
{
    int i;		//局部变量
    
    printf("%d, %d\n", gi, i);
      
    getchar();
    return 0;
}
当全局变量与局部变量重名时,使用的是局部变量:

#include 

int a = 111, b = 222;

int main(void)
{
    int a = 123;
    printf("%d,%d\n", a, b);	//输出的是 123,222

    getchar();    
    return 0;
}


二、对象的生存周期(lifetime)

(1)静态生存周期(即全局变量的生存周期)

具有静态生存周期的所有对象,都是在程序开始执行之前就被事先创建和初始化。它们的寿命覆盖整个程序的执行过程。如在函数内定义了一个static变量,那第一次调用该函数后,该变量的值将会被保留,当第二次被调用时,该变量的值还是第一次调用结束时的值。

(2)自动生存周期(即局部变量的生存周期)

自动生存周期的对象的寿命由“对象定义所处在的大括号{}”决定。每次程序执行流进入一个语句块,此语句块自动生存周期的对象就会被创建一个新实例,同时被初始化。


三、标识符的链接(linkage)

(1)外部链接

表示在整个程序中(多个程序文件)是相同的函数或对象。常见的有,在函数体外声明的extern变量。

(2)内部链接

表示只在当前程序文件中是相同的函数或对象。其它程序文件不能对其进行访问。常见的有,在函数体外声明的static变量。

(3)无链接

一般声明在函数内部的auto、register变量、还有函数的参数,都是无链接。它的作用域是函数内部。


四、存储类型修饰符总结:

存储类型修饰符可以修改标识符的链接和对应对象的生存周期;标识符有链接,而非生命周期;对象有生存周期,而非链接;函数标识符只可用static、extern修饰,函数参数只可用register修饰。


(1)auto(对应自动生存周期)

auto修饰符只能用在函数内的对象声明中,即仅在语句块内使用。

声明中有auto修饰符的对象具有自动生存周期。

它们仅存在于被定义的当前执行代码块中,即局部变量在进入模块时生成,在退出模块时消亡。

定义局部变量的最常见的代码块是函数。 语言中包括了关键字auto,它可用于定义局部变量。但自从所有的非全局变量的缺省值假定为auto以来,auto就几乎很少使用了。


(2)static(对应静态生存周期)

如果是定义在函数外,那么该对象具有内部链接,其它程序文件不能对其访问。如果是定义在函数内,那么该对象具有无链接,函数外不能对其访问。

(注意:static变量初始化时,只能用常量)

用 static 关键字修饰的局部变量称为静态局部变量。

静态局部变量存值如同全局变量,区别在于它只属于拥有它的函数,它也和全局变量一样会被初始化为空。

#include 

void fun1(void);
void fun2(void);

int main(void)
{    
    int i;
    
    for (i = 0; i < 10; i++) fun1();
    printf("---\n");
    for (i = 0; i < 10; i++) fun2();
    
    getchar();
    return 0;
}

void fun1(void) {
    int n = 0;			//一般的局部变量
    printf("%d\n", n++);
}

void fun2(void) {
    static int n;		//静态局部变量,会被初始化为空
    printf("%d\n", n++);
}

用 static 关键字修饰的全局变量是静态全局变量,静态全局变量只能用于定义它的单元。

//譬如在 File1.c 里面定义了:
static int num = 99;  /* 去掉前面的 static 其他单元才可以使用 */

//在 File2.c 里使用:
#include 

extern int num;

int main(void)
{    
    printf("%d\n", num);
 
    getchar();
    return 0;
}



用静态变量记录函数被调用的次数:

#include 

int fun(void);

int main(void)
{    
    int i;
    for (i = 0; i < 10; i++) {
        printf("函数被调用了 %2d 次;\n", fun());
    }     
    getchar();
    return 0;
}

int fun(void) {
    static int n;
    return ++n;
}

(3)const


(4)extern(对应静态生存周期)

extern 意为“外来的”。它的作用在于告诉编译器:这个变量或者函数的定义在别的地方,当遇到此变量或函数时应到其他模块中寻找其定义。

(PS:这个变量,它可能不存在于当前的文件中,但它肯定要存在于工程中的某一个源文件中或者一个Dll的输出中。)

#include 

extern int g1;

int main(void)
{    
    extern int g2;	//告诉编译器g2定义在其他地方

    printf("%d,%d\n", g1,g2);  
    getchar();
    return 0;
}

int g1 = 77;
int g2 = 88;

使用extern时,注意不能重复定义,否则编译报错,如:

程序文件一:

extern int a = 10; 	//编译警告,extern的变量最好不要初始化 

程序文件二:

extern int a = 20; 	//重复定义,应改为extern int a; 

(一般最好这样,如果需要初始化,可把extern修饰符去掉(但也不要重复定义),另外如果其它程序文件也需要用到该变量,可用extern来声明该变量。这样会比较清晰。)

另外,extern也可用来进行链接指定。


(5)volatile


(6)register(即寄存器变量,对应自动生存周期)

当声明对象有自动生存周期时,可以使用register修饰符。因此,register也只能用在函数内的声明中。

register修饰符暗示编译程序相应的变量将被频繁地使用,如果可能的话,应将其保存在CPU的寄存器中(而不是栈或堆),以加快其存储速度。然而,编译器不见得会这么做,因此效果一般般。了解一下就行,不建议使用。

#include 
#include 

#define TIME 1000000000

int m, n = TIME;		//全局变量

int main(void)
{   
    time_t start, stop;

    register int a, b = TIME;	//寄存器变量
    int x, y = TIME;		//一般变量
    
    time(&start);
    for (a = 0; a < b; a++);
    time(&stop);
    printf("寄存器变量用时: %d 秒\n", stop - start);

    time(&start);
    for (x = 0; x < y; x++);
    time(&stop);
    printf("一般变量用时: %d 秒\n", stop - start);

    time(&start);
    for (m = 0; m < n; m++);
    time(&stop);
    printf("全局变量用时: %d 秒\n", stop - start);
 
    getchar();
    return 0;
}

使用register修饰符有几点限制:

  首先,register变量必须是能被CPU所接受的类型。这通常意味着register变量必须是一个单个的值,并且长度应该小于或者等于整型的长度。不过,有些机器的寄存器也能存放浮点数。

  其次,因为register变量有可能被存放到寄存器中而不是内存中,所以不能用“&”来获取register变量的地址。


  总的来说,由于寄存器的数量有限,而且某些寄存器只能接受特定类型的数据(如指针和浮点数),因此真正起作用的register修饰符的数目和类型都依赖于运行程序的机器,而任何多余的register修饰符都将被编译程序所忽略。

  在某些情况下,把变量保存在寄存器中反而会降低程序的运行速度。因为被占用的寄存器不能再用于其它目的;或者变量被使用的次数不够多,不足以装入和存储变量所带来的额外开销。

  早期的C编译程序不会把变量保存在寄存器中,除非你命令它这样做,这时register修饰符是C语言的一种很有价值的补充。然而,随着编译程序设计技术的进步,在决定那些变量应该被存到寄存器中时,现在的C编译环境能比程序员做出更好的决定。实际上,许多编译程序都会忽略register修饰符,因为尽管它完全合法,但它仅仅是暗示而不是命令。


(7)缺省修饰符

函数内,与auto相同,函数外,与extern相同。




五、概括性例子:

int func1(void); 	//func1具有外部链接
int a = 10; 		//a具有外部链接,静态生存周期    
extern int b = 1; 	//b具有外部链接,静态生存周期。但编译会有警告extern变量不应初始化,同时也要注意是否会重复定义
static int c; 		//c具有内部链接,静态生存周期
static int e; 		//e具有内部链接,静态生存周期
static void func2(int d)
{
	//func2具有内部链接;参数d具有无链接,自动生存周期
	extern int a; 	//a与上面的a一样(同一变量),具有外部链接,静态生存周期。注意这里的不会被默认初始为0,它只是个声明
	int b = 2; 	//b具有无链接,自动生存同期。并且将上面声明的b隐藏起来
	extern int c; 	//c与上面的c一样,维持内部链接,静态生存周期。注意这里的不会被默认初始为0,它只是个声明
	//如果去掉了extern修饰符,就跟b类似了,无链接,自动生存周期,把上面声明的c隐藏起来
	static int e; 	//e具有无链接,静态生存周期。并且将上面声明的e隐藏起来;初始化值为0
	static int f; 	//f具有无链接,静态生存周期
}  


相关链接参考:

http://developer.51cto.com/art/201105/261465.htm

http://apps.hi.baidu.com/share/detail/30353645

http://www.cnblogs.com/del/archive/2008/12/04/1347305.html

你可能感兴趣的:(VC++编程积累库)