存储级别关键字

更新时间:2024-01-08 17:10

存储级别关键字共有六个: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>其他文件中可以使用相同名字的变量,不会发生冲突。

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();

}

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 */

免责声明
隐私政策
用户协议
目录 22
0{{catalogNumber[index]}}. {{item.title}}
{{item.title}}