变量和函数的属性包括数据类型和数据的存储类别,存储类别指数据在内存中存储方式(静态和动态),包含auto,static,register,extern四种。
一、简要性比较
extern 外部变量声明,是指这是一个已在别的地方定义过的对象,这里只是对变量的一次重复引用,不会产生新的变量。
static 静态数据,数据存放在全局数据区,但作用域只是本 文件/函数 中,所以你可以在两个不同的文件/函数内部申明同名的static变量,但是 它们是两个不同的全局变量。 如果是定义在函数内,那么该对象具有无链接,函数外不能对其访问。如果是定义在函数外,那么该对象具 有内部链接,其它程序文件不能对其访问
auto 普通局部栈变量,是自动存储,这种对象会自动创建和销毁 ,建议这个变量要放在堆栈上面,调用函数时分配内存,函数结束时释放内 存。一般隐藏auto默认为自动存储类别。我们程序都变量大多是自动变量。
register 寄存器变量,请求编译器将这个变量保存在CPU的寄存器中,从而加快程序的运行.
系统的寄存器是有限制的,声明变量时如:register int i.这种存储类型可以用于频繁使用的变量。
二、补充注意点
1、auto存储类型的变量只能在某个程序范围内使用,他采用堆栈的方式分配空间。
在定义变量时,auto是可以省略的,如:auto int i;等效于int i.
2、全局变量不允许声明为auto变量,register不适用于全局变量。
3、register是不能取址的。比如 int i;(自动为auto)int *p=&i;是对的, 但register int j; int *p = &j;是错的,因为无法对寄存器的定址。
4.声明变量为register,编译器并不一定会将它处理为寄存器变量,毕竟,资源是有限的。
5、实际上现在一般的编译器都忽略auto和register申明,现在的编译器自己能够区分最好将那些变量放置在寄存器中,那些放置在堆栈中;甚至于将一些变量有时存放在堆栈,有时存放在寄存器中。
6.auto register 是用来修饰变量的,static extern 变量函数都可以
三、详细说明
从变量值存在时间(生存期)角度分:静态存储方式和动态存储方式。
从变量的作用域(空间)角度分:全局变量和局部变量。
在用户区的存储空间:
|程序区 |
|静态存储区|
|动态存储器|
静态存储方式程序运行期间系统分配固定存储空间,动态存储方式根据需求动态分配存储空间。全局变量都存放于静态存储区,动态存储区主要包括:函数的形参,在函数调用时分配存储空间;自动变量(未加static声明的局部变量);函数调用时的现场保护和返回地址等。
Auto自动变量:一般隐藏auto默认为自动存储类别。我们程序都变量大多是自动变量。
Static变量::static声明的局部变量在函数调用结束后不释放存储空间,再次调用函数时该变量已经有值。其他函数时不能引用它的。Static局部变量时在编译赋初值,自动变量的初值是函数调用时赋的。所以局部变量不赋初始值的话static变量自动赋值0或在空字符。而自动变量的初值则是不确定的。最好不要直接使用。函数中需要保留上次调用时的值或者初始化之后变量只被引用而不改变的可以考虑用static局部变量,这样比较耗内存。
存放在静态内存区的变量其初始值都为0. 在静态数据区,内存中所有的字节默认值都是0x00,某些时候这一特点可以减少程序员的工作量。
Register变量:动态和静态变量都是存放在内存中,程序中遇到该值时用控制器发指令将变量的值送到运算器中,需要存数再保存到内存中。如果频繁使用一个变量,比如一个函数体内的多次循环每次都引用该局部变量,我们则可以把局部变量的值放到CPU的寄存器中,叫寄存器变量。不需要多次到内存中存取提高效率。但是只能局部自动变量和形参可以做寄存器变量。在函数调用时占用一些寄存器,函数结束时释放。不同系统对register要求也不一样,比如对定义register变量个数,数据类型等限制,有的默认为自动变量处理。所以在程序一般也不用。
Extern外部变量:如果我们希望该外部变量只能在本文件内使用,而不能被其他文件引用可以在外部变量定义时加static声明。防止别人写的模块误用。在函数外部定义的全局变量,作用域开始于变量定义,结束于程序文件的结束。我们可以extern来声明外部变量来扩展它的作用域。同一个文件内,extern声明之后就可以作用域扩大到声明处到文件结束。比如在一个函数之后定义外部变量a,之后的函数可以使用该变量,但是之前的函数不能使用,加extern可以解决。多个文件时,可以在未定义该外部变量的文件内做extern声明即可以使用。但是需要注意可能执行一个文件时改变了该全局变量的值,影响其他文件的调用。编译时遇到extern,会先在文件内找是否定义了该外部变量。如果未找到则在链接时在其他文件中找。
四、示例
1、extern:
2.
#include <stdio.h> int main(void) { auto int i = 9; /* 声明局部变量的关键字是 auto; 因可以省略, 几乎没人使用 */ printf("%d\n", i); getchar(); return 0; }
2. 全局变量: 声明在函数体外, 一般应在函数前; 每个函数都可以使用它, 不过全局变量应尽量少用.
#include <stdio.h> 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 <stdio.h> int gi; /* 全局变量 */ int main(void) { int i; /* 句柄变量 */ printf("%d, %d\n", gi, i); getchar(); return 0; }
当全局变量与局部变量重名时, 使用的是局部变量:
#include <stdio.h> int a = 111, b = 222; int main(void) { int a = 123; printf("%d,%d\n", a, b); /* 123,222*/ getchar(); return 0; }
3. static 关键字: 修饰的局部变量是静态局部变量; 静态局部变量存值如同全局变量, 区别在于它只属于拥有它的函数; 它也会被初始化为空.
#include <stdio.h> 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++); }
//譬如在 File1.c 里面定义了: static int num = 99; /* 去掉前面的 static 其他单元才可以使用 */ //在 File2.c 里使用: #include <stdio.h> extern int num; int main(void) { printf("%d\n", num); getchar(); return 0; }
#include <stdio.h>
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;}
#include <stdio.h> #include <time.h> #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; }
4. extern 关键字:
使用外部全局变量应该用 extern 重新声明一下;
如果不使用 extern, 将是重新定义;
在链接阶段, 同一程序中不同单元的全局变量是共用的所以不能重新定义;
本例为了省事并没有涉及另一个文件, 只是把变量放在文件尾模拟了一下.
另外, extern 主要还是用于函数.
#include <stdio.h> extern int g1; int main(void) { extern int g2; printf("%d,%d\n", g1,g2); getchar(); return 0; } int g1 = 77; int g2 = 88;
5. volatile 关键字:
程序在使用变量时, 特别是连续多次使用变量时, 一般是载入寄存器, 直接从寄存器存取, 之后再还回内存;
但如果此变量在返回内存时, 假如内存中的值已经改变了(从外部修改了)怎么办?
为了避免这种情况的发生, 可以用 volatile 说明此变量, 以保证变量的每次使用都是直接从内存存取.
但这样肯定会影响效率, 幸好它并不常用.
另外: 如果 const volatile 同时使用, 这表示此变量只接受外部的修改.
#include <stdio.h> volatile int num = 123; int main(void) { printf("%d\n", num); getchar(); return 0; }
该关键字在多线程环境下经常使用,因为在编写多线程的程序时,同一个变量可能被多个线程修改,而程序通过该变量同步各个线程。 简单示例:
DWORD __stdcall threadFunc(LPVOID signal) {
int* intSignal=reinterpret_cast(signal); *intSignal=2; while(*intSignal!=1) sleep(1000); return 0; }
该线程启动时将intSignal 置为2,然后循环等待直到intSignal 为1 时退出。显然intSignal的值必须在外部被改变,否则该线程不会退出。但是实际运行的时候该线程却不会退出,即使在外部将它的值改为1,看一下对应的伪汇编代码就明白了:
mov ax,signal label: if(ax!=1) goto label
对于C编译器来说,它并不知道这个值会被其他线程修改。自然就把它cache在寄存器里面。C 编译器是没有线程
概念的,这时候就需要用到volatile。volatile 的本意是指:这个值可能会在当前线程外部被改变。也就是说,我们要在threadFunc中的intSignal前面加上volatile关键字,这时候,编译器知道该变量的值会在外部改变,因此每次访问该变量时会重新读取,所作的循环变为如下面伪码所示:
注意:一个参数既可以是const同时是volatile,是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它
restrict 有点和 volatile 相反; 不过 restrict 只是对指针.
被const修饰的东西都受到强制保护,可以预防意外的变动,能提高程序的健壮性。它可以修饰函数的参数、返回值,甚至函数的定义体。 作用:
1>修饰输入参数
a.对于非内部数据类型的输入参数,应该将“值传递”的方式改为“const引用传递”,目的是提高效率。例如将void Func(A a) 改为void Func(const A &a)。
b.对于内部数据类型的输入参数,不要将“值传递”的方式改为“const引用传递”。否则既达不到提高效率的目的,又降低了函数的可理解性。例如void Func(int x) 不应该改为void Func(const int &x)。 2>用const修饰函数的返回值
a.如果给以“指针传递”方式的函数返回值加const修饰,那么函数返回值(即指针)的内容不能被修改,该返回值只能被赋给加const修饰的同类型指针。 如对于: const char * GetString(void); 如下语句将出现编译错误:
char *str = GetString();//cannot convert from 'const char *' to 'char *'; 正确的用法是:
const char *str = GetString();
b.如果函数返回值采用“值传递方式”,由于函数会把返回值复制到外部临时的存储单元中,加const修饰没有任何价值。如不要把函数int GetInt(void) 写成const int GetInt(void)。
3>const成员函数的声明中,const关键字只能放在函数声明的尾部,表示该类成员不修改对象.
说明:
const type m; //修饰m为不可改变 示例:
typedef char * pStr; //新的类型pStr; char string[4] = "abc"; const char *p1 = string;
p1++; //正确,上边修饰的是*p1,p1可变 const pStr p2 = string;
p2++; //错误,上边修饰的是p2,p2不可变,*p2可变 同理,const修饰指针时用此原则判断就不会混淆了。 const int *value; //*value不可变,value可变 int* const value; //value不可变,*value可变
const (int *) value; //(int *)是一种type,value不可变,*value可变
//逻辑上这样理解,编译不能通过,需要tydef int* NewType; const int* const value;//*value,value都不可变
(1)外部链接
表示在整个程序中(多个程序文件)是相同的函数或对象。常见的有,在函数体外声明的extern变量。
(2)内部链接
表示只在当前程序文件中是相同的函数或对象。其它程序文件不能对其进行访问。常见的有,在函数体外声明的static变量。
(3)无链接
一般声明在函数内部的auto、register变量、还有函数的参数,都是无链接。它的作用域是函数内部。
二、对象的生存周期(lifetime)
(1)静态生存周期
具有静态生存周期的所有对象,都是在程序开始执行之前就被事先创建和初始化。它们的寿命覆盖整个程序的执行过程。如在函数内定义了一个static变量,那第一次调用该函数后,该变量的值将会被保留,当第二次被调用时,该变量的值还是第一次调用结束时的值。
(2)自动生存周期
自动生存周期的对象的寿命由“对象定义所处在的大括号{}”决定。每次程序执行流进入一个语句块,此语句块自动生存周期的对象就会被创建一个新实例,同时被初始化。
三、存储类修饰符
(1)auto
auto修饰符只能用在函数内的对象声明。声明中有auto修饰符的对象具有自动生存周期。
在ANSI C中,函数内的对象声明在默认情况下有自动生存周期,所以在函数内声明时auto可省略。
(2)register
当声明对象有自动生存周期时,可以使用register修饰符。因此,register也只能用在函数内的声明中。
此关键字告诉编译器:此对象的存取应该尽量快,最好存储在CPU的寄存器中。然而,编译器不见得会这么做。
另外要注意的是,当一个对象声明为register,就不可使用地址运算符&了,因为它有可能被放到寄存器中。
(3)static
函数标识符如果被声明为static,就具有静态生命周期。
如果是定义在函数外,那么该对象具有内部链接,其它程序文件不能对其访问。
如果是定义在函数内,那么该对象具有无链接,函数外不能对其访问。
注意:static变量初始化时,只能用常量。
(4)extern
如果声明在函数外,那么该对象具有外部链接,能够在其它程序文件使用。但要注意它有可能会被函数内定义的重名的变量所隐藏起来。
如果声明在函数内,该对象具有何种链接取决于当前程序文件中定义在函数外的相同名字的对象。如果在函数外也定义了一下相同名字的static对象,则该函数内的对象具有无链接,否则具有外部链接。
extern的对象都具有静态生命周期。
使用extern时,注意不能重复定义,否则编译报错,如:
程序文件一:
extern int a = 10; //编译警告,extern的变量最好不要初始化
程序文件二:
extern int a = 20; //重复定义,应改为extern int a;
一般最好这样,如果需要初始化,可把extern修饰符去掉(但也不要重复定义),另外如果其它程序文件也需要用到该变量,可用extern来声明该变量。这样会比较清晰。
(5)缺省修饰符
函数内,与auto相同;
函数外,与extern相同;
| | linkage | lifetime |
auto | 函数内 | no linkage | 自动 |
函数外 | 语法错 | 语法错 | |
register | 函数内 | no linkage | 自动 |
函数外 | 语法错 | 语法错 | |
缺省 | 函数内 | no linkage | 自动 |
函数外 | external linkage | 静态 | |
static | 函数内 | no linkage | 静态 |
函数外 | internal linkage | 静态 | |
extern | 函数内 | 与它在函数外所声明的一致 | 静态 |
函数外 | external linkage | 静态 |
int func1(void);
int a = 10;
extern int b = 1;
static int c;
static int e;
static void func2(int d){
}