存储级别关键字共有六个:auto register volatile static extern const。
简介
下边分别介绍:
1. auto :声明自动变量 一般不使用
2. register:声明寄存器变量
3. const :声明只读变量
4. volatile:说明变量在程序执行中可被隐含地改变
5. static :声明静态变量
6. extern:声明变量是在其他文件正声明(也可以看做是引用变量)
下边详细介绍各关键字用法以及例子程序
Auto
auto是缺省的存储类型,当你定义了
变量以后系统就会为它分配内存。无论你是否使用它都是存在于内存中的
Register
register是
寄存器变量,在CPU里有“寄存器”这个东西,这个东西比内存更靠近CPU,所以速度更快。但是它是很小的,通常只有几个字节,所以只有在数据量很小,而且使用频繁的情况下才使用。既然是寄存器变量,所以它存放在寄存器中,不占用内存单元
Const
const
修饰符可以把对象转变成常数对象,什么意思呢?意思就就是说利用const进行修饰的
变量的值在程序的任意位置将不能再被修改,就如同常数一样使用!
使用方法是:
const int a=1;//这里定义了一个int类型的const常数
变量a;
但就于
指针来说const仍然是起作用的,以下有两点要十分注意,因为下面的两个问题很容易混淆!
我们来看一个如下的例子:
#include
using namespace std;
void main(void)
{
const int a=10;
int b=20;
const int *pi;
pi=a;
pi=b;
cin.get();
}
上面的代码中最重要的一句是 const int *pi
这句从右向座读作:pi是一个指向int类型的,被定义成const的对象的
指针;
这样的一种声明方式的作用是可以修改pi这个
指针所指向的
内存地址却不能修改指向对象的值。
如果你在代码后加上*pi=10;这样的赋值操作是不被允许编译的!
好,看了上面的两个例子你对const有了一个基本的认识了,那么我们接下来看一个很容易混淆的用法!
请看如下的代码
#include
using namespace std;
void main(void)
{
int a=10;
const int *const pi=a;
cin.get();
}
上面的代码中最重要的一句是 const int *const pi
这句从右向座读作:pi是一个指向int类型对象的
const指针;
这样的一种声明方式的作用是你既不可以修改pi所指向对象的内存地址也不能利用
指针的解引用方式修改对象的值,也就是用*pi=10这样的方式;
所以你如果在最后加上*pi=20,想试图通过这样的方式修改对象a的值是不被允许编译的!
所以结合上面的两点所说,把代码修改成如下形式后就可以必然在程序的任意的地方修改对象a的值或者是
指针pi的地址了,下面的这种写法常被用语函数的
形式参数,这样可以保证对象不会在函数内被改变值!
#include
using namespace std;
void main(void)
{
const int a=10;//这句和上面不同,请注意!
const int *const pi=a;
cin.get();
}
Volatile
volatile 影响
编译器编译的结果,指出,volatile
变量是随时可能发生变化的,与volatile变量有关的运算,不要进行编译优化,以免出错,(VC++ 在产生release版可执行码时会进行编译优化,加volatile关键字的变量有关的运算,将不进行编译优化。)。
例如:
volatile inti=10;
int j = i;
...
int k = i;
volatile 告诉
编译器i是随时可能发生变化的,每次使用它的时候必须从i的地址中读取,因而编译器生成的可执行码会重新从i的地址读取数据放在k中。
而优化做法是,由于
编译器发现两次从i读数据的代码之间的代码没有对i进行过操作,它会自动把上次读的数据放在k中。而不是重新从i里面读。这样以来,如果i是一个
寄存器变量或者表示一个端口数据就容易出错,所以说volatile可以保证对特殊地址的稳定访问,不会出错。
Static
在
函数体,一个被声明为静态的
变量在这一函数
被调用过程中维持其值不变。
在模块内(但在函数体外),一个被声明为
静态的
变量可以被模块内所用函数访问,但不能被模块外其它函数访问。它是一个本地的
全局变量。
在模块内,一个被声明为
静态的函数只可被这一模块内的其它
函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用。
面向过程程序设计中的static1. 全局
静态变量在
全局变量之前加上关键字static,全局变量就被定义成为一个全局
静态变量。
1、内存中的位置:
静态存储区(
静态存储区在整个程序运行期间都存在)
2、初始化:未经初始化的全局
静态变量会被程序自动初始化为0(自动对象的值是任意的,除非他被显示初始化)
3、
作用域:全局
静态变量在声明他的文件之外是不可见的。准确地讲从定义之处开始到文件结尾。
//teststatic1.c
void display();
extern int n;
int main()
{
n = 20;
display();
return 0;
}
//teststatic2.c
static int n; //定义全局
静态变量,自动初始化为0,仅在本文件中可见
void display()
{
n++;
}
文件分别编译通过,但link的时候teststatic2.c中的
变量n找不到定义,产生错误。
<1>不会被其他文件所访问,修改
<2>其他文件中可以使用相同名字的
变量,不会发生冲突。
在
局部变量之前加上关键字static,局部变量就被定义成为一个局部
静态变量。
1、内存中的位置:静态存储区
2、初始化:未经初始化的全局
静态变量会被程序自动初始化为0(自动对象的值是任意的,除非他被显示初始化)
3、
作用域:作用域仍为局部作用域,当定义它的函数或者语句块结束的时候,作用域随之结束。
注:当static用来修饰
局部变量的时候,它就改变了局部变量的存储位置,从原来的栈中存放改为
静态存储区。但是局部静态变量在离开
作用域之后,并没有被销毁,而是仍然驻留在内存当中,直到程序结束,只不过我们不能再对他进行访问。
当static用来修饰
全局变量的时候,它就改变了全局变量的
作用域(在声明他的文件之外是不可见的),但是没有改变它的存放位置,还是在
静态存储区中。
在函数的返回类型前加上关键字static,函数就被定义成为
静态函数。
函数的定义和声明默认情况下是extern的,但
静态函数只是在声明他的文件当中可见,不能被其他文件所用。
例如:
//teststatic1.c
void display();
static void staticdis();
int main()
{
display();
staticdis();
renturn 0;
}
//teststatic2.c
void display()
{
staticdis();
}
static void staticdis()
{
}
文件分别编译通过,但是连接的时候找不到函数staticdis()的定义,产生错误。
实际上编译也未过,vc2003报告teststatic1.c中
静态函数staticdis已声明但未定义 ;by imjacob
<1> 其他文件中可以定义相同名字的函数,不会发生冲突
<2> 静态函数不能被其他文件所用。
存储说明符auto,register,extern,static,对应两种存储期:自动存储期和静态存储期。
auto和register对应自动存储期。具有自动存储期的
变量在进入声明该变量的程序块时被建立,它在该程序块活动时存在,退出该程序块时撤销。
关键字extern和static用来说明具有静态存储期的
变量和函数。用static声明的
局部变量具有静态存储持续期(static storageduration),或静态范围(static extent)。虽然他的值在
函数调用之间保持有效,但是其名字的可视性仍限制在其局部域内。静态局部对象在程序执行到该对象的声明处时被首次初始化。
由于static
变量的以上特性,可实现一些特定功能。
1. 统计次数功能
声明函数的一个
局部变量,并设为static类型,作为一个计数器,这样函数每次被调用的时候就可以进行计数。这是统计函数被调用次数的最好的办法,因为这个
变量是和函数息息相关的,而函数可能在多个不同的地方被调用,所以从调用者的角度来统计比较困难。代码如下:
void count();
int main()
{
int i;
for (i = 1; i <= 3; i++)
count();
return 0;
}
void count()
{
static num = 0;
num++;
}
输出结果为:
I have been called 1 times.
I have been called 2 times.
I have been called 3 times.
惨痛教训:
假设在test.h中定义了一个static bool g_test=false;
若test1.c和test2.c都包含test.h,则test1.c和test2.c分别生成两份g_test,在test1.c 中置g_test=true,而test2.c中仍然为false并未改变!shit!!
C程序一直由下列部分组成: 1、正文段——CPU执行的
机器指令部分;一个程序只有一个副本;只读,防止程序由于意外事故而修改自身指令;
2、初始化
数据段(数据段)——在程序中所有赋了初值的全局变量,存放在这里。
3、非初始化
数据段(bss段)——在程序中没有初始化的
全局变量;内核将此段初始化为0。
4、栈——增长方向:自顶向下增长;
自动变量以及每次
函数调用时所需要保存的信息(返回地址;环境信息)。
5、堆——动态存储分。
|-----------|
| |
|-----------|
| 栈 |
|-----------|
| | |
| |/ |
| |
| |
| /| |
| | |
|-----------|
| 堆 |
|-----------|
| 未初始化 |
|-----------|
| 初始化 |
|-----------|
| 正文段 |
|-----------|
Extern
extern可以置于
变量或者函数前,以标示变量或者函数的定义在别的文件中,提示
编译器遇到此变量和函数时在其他模块中寻找其定义。
另外,extern也可用来进行链接指定。
问题:extern 变量
在另外一个文件里用下列语句进行了声明:
请问,这样可以吗?
答案与分析:
1、不可以,程序运行时会告诉你非法访问。原因在于,指向类型T的
指针并不等价于类型T的
数组。extern char *a声明的是一个
指针变量而不是
字符数组,因此与实际的定义不同,从而造成运行时非法访问。应该将声明改为extern char a[ ]。
显然a指向的空间(0x61626364)没有意义,易出现非法内存访问。
3、在使用extern时候要严格对应声明时的格式,在实际编程中,这样的错误屡见不鲜。
4、extern用在
变量声明中常常有这样一个作用,你在*.c文件中声明了一个全局的变量,这个全局的变量如果要被引用,就放在*.h中并用extern来声明。
问题:extern 函数1
常常见extern放在函数的前面成为函数声明的一部分,那么,C语言的关键字extern在函数的声明中起什么作用?
答案与分析:
如果函数的声明中带有关键字extern,仅仅是暗示这个函数可能在别的源文件里定义,没有其它作用。即下述两个函数声明没有明显的区别:
当然,这样的用处还是有的,就是在程序中取代include “*.h”来声明函数,在一些复杂的项目中,我比较习惯在所有的函数声明前添加extern修饰。
问题:extern函数2
当函数提供方单方面修改函数原型时,如果使用方不知情继续沿用原来的extern申明,这样编译时
编译器不会报错。但是在运行过程中,因为少了或者多了输入参数,往往会照成系统错误,这种情况应该如何解决?
答案与分析:
如今业界针对这种情况的处理没有一个很完美的方案,通常的做法是提供方在自己的xxx_pub.h中提供对外部接口的声明,然后调用方include该头文件,从而省去extern这一步。以避免这种错误。
宝剑有双锋,对extern的应用,不同的场合应该选择不同的做法。
问题:extern“C”
在C++环境下使用C函数的时候,常常会出现
编译器无法找到obj模块中的C函数定义,从而导致链接失败的情况,应该如何解决这种情况呢?
答案与分析:
C++语言在编译的时候为了解决函数的多态问题,会将函数名和参数联合起来生成一个中间的函数名称,而C语言则不会,因此会造成链接时找不到对应函数的情况,此时C函数就需要用extern “C”进行链接指定,这告诉编译器,请保持我的名称,不要给我生成用于链接的中间函数名。
下面是一个标准的写法:
//在.h文件的头上
#ifdef __cplusplus
#if __cplusplus
#endif
#endif /* __cplusplus */
…
…
//.h文件结束的地方
#ifdef __cplusplus
#if __cplusplus
}
#endif
#endif /* __cplusplus */