标题：存储级别关键字
简介
共有六个：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 <iostream>
using namespace std; 
void main(void) 
{ 
const int a=10; 
int b=20; 
const int *pi; 
pi=a; 
cout <<*pi << "|" << a <<endl; 
pi=b; 
cout <<*pi << "|" <<b <<endl; 
cin.get(); 
} 
上面的代码中最重要的一句是 const int *pi 
这句从右向座读作：pi是一个指向int类型的，被定义成const的对象的指针; 
这样的一种声明方式的作用是可以修改pi这个指针所指向的内存地址却不能修改指向对象的值。 
如果你在代码后加上*pi=10;这样的赋值操作是不被允许编译的！
好,看了上面的两个例子你对const有了一个基本的认识了，那么我们接下来看一个很容易混淆的用法！ 
请看如下的代码 
#include <iostream>
using namespace std; 
void main(void) 
{ 
int a=10; 
const int *const pi=a; 
cout <<*pi << "|" <<a <<endl; 
cin.get(); 
} 
上面的代码中最重要的一句是 const int *const pi 
这句从右向座读作:pi是一个指向int类型对象的const指针； 
这样的一种声明方式的作用是你既不可以修改pi所指向对象的内存地址也不能利用指针的解引用方式修改对象的值，也就是用*pi=10这样的方式； 
所以你如果在最后加上*pi=20，想试图通过这样的方式修改对象a的值是不被允许编译的！ 
所以结合上面的两点所说,把代码修改成如下形式后就可以必然在程序的任意的地方修改对象a的值或者是指针pi的地址了,下面的这种写法常被用语函数的形式参数,这样可以保证对象不会在函数内被改变值！ 
#include <iostream>
using namespace std; 
void main(void) 
{ 
const int a=10;//这句和上面不同，请注意！ 
const int *const pi=a; 
cout <<*pi << "|" <<a <<endl; 
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;
printf("%dn",n);
display();
return 0;
}
//teststatic2.c 
static int n; //定义全局静态变量，自动初始化为0，仅在本文件中可见
void display()
{
n++;
printf("%dn",n);
}
文件分别编译通过，但link的时候teststatic2.c中的变量n找不到定义，产生错误。
定义全局静态变量的好处：
<1>不会被其他文件所访问，修改
<2>其他文件中可以使用相同名字的变量，不会发生冲突。
2. 局部静态变量
在局部变量之前加上关键字static，局部变量就被定义成为一个局部静态变量。
1）内存中的位置：静态存储区
2）初始化：未经初始化的全局静态变量会被程序自动初始化为0（自动对象的值是任意的，除非他被显示初始化）
3）作用域：作用域仍为局部作用域，当定义它的函数或者语句块结束的时候，作用域随之结束。
注：当static用来修饰局部变量的时候，它就改变了局部变量的存储位置，从原来的栈中存放改为静态存储区。但是局部静态变量在离开作用域之后，并没有被销毁，而是仍然驻留在内存当中，直到程序结束，只不过我们不能再对他进行访问。
当static用来修饰全局变量的时候，它就改变了全局变量的作用域（在声明他的文件之外是不可见的），但是没有改变它的存放位置，还是在静态存储区中。
3. 静态函数
在函数的返回类型前加上关键字static，函数就被定义成为静态函数。
函数的定义和声明默认情况下是extern的，但静态函数只是在声明他的文件当中可见，不能被其他文件所用。
例如：
//teststatic1.c
void display();
static void staticdis(); 
int main()
{
display();
staticdis();
renturn 0;
}
//teststatic2.c
void display()
{
staticdis();
printf("display() has been called n");
}
static void staticdis()
{
printf("staticDis() has been calledn");
}
文件分别编译通过，但是连接的时候找不到函数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++;
printf(" I have been called %d",num,"timesn");
}
输出结果为：
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 变量
在一个源文件里定义了一个数组：
char a[6];在另外一个文件里用下列语句进行了声明：
extern char *a；请问，这样可以吗？ 
答案与分析：
1)、不可以，程序运行时会告诉你非法访问。原因在于，指向类型T的指针并不等价于类型T的数组。extern char *a声明的是一个指针变量而不是字符数组，因此与实际的定义不同，从而造成运行时非法访问。应该将声明改为extern char a[ ]。
2)、例子分析如下，如果a[] = "abcd",则外部变量a=0x61626364 (abcd的ASCII码值)，*a显然没有意义，如下图：
显然a指向的空间（0x61626364）没有意义，易出现非法内存访问。
3)、在使用extern时候要严格对应声明时的格式，在实际编程中，这样的错误屡见不鲜。
4)、extern用在变量声明中常常有这样一个作用，你在*.c文件中声明了一个全局的变量，这个全局的变量如果要被引用，就放在*.h中并用extern来声明。
问题：extern 函数1
常常见extern放在函数的前面成为函数声明的一部分，那么，C语言的关键字extern在函数的声明中起什么作用？
答案与分析：
如果函数的声明中带有关键字extern，仅仅是暗示这个函数可能在别的源文件里定义，没有其它作用。即下述两个函数声明没有明显的区别：
extern int f(); 和int f();当然，这样的用处还是有的，就是在程序中取代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
extern "C"{
#endif
#endif /* __cplusplus */ 
…
…
//.h文件结束的地方
#ifdef __cplusplus
#if __cplusplus
}
#endif
#endif /* __cplusplus */