1
四、生存期与存储属性五,extern关键字与外部连接属性六,static关键字与内部连接属性
2
四、生存期与存储属性生存期是指数据存在而不消失的时间。程序在内存被分为四个区:
1.代码区,执行指令被加载到代码区。
2.静态数据区
3.动态数据区。
4.堆内存区。
内存的数据和代码由寄存器操作和周转。变量的生存期是由其存储属性控制的,变量属于一个确定的存储属性。
3
系统有四个部分可供存放变量,相应的生存期与之密切联系。
1,寄存器 register
关键字 register允许用户将局部变量声明为寄存器存储属性,如此存放的数据生存期是瞬态的。
编译器根据寄存器的使用情况做出优化处理,未必将
register变量一定存放在寄存器中,寄存器资源紧张的时候,经关键字 register限定的变量可能安排在堆栈中,因而其生存期是类似于 auto存储属性的局部变量。
32位通用寄存器最多可容纳 4字节的数据,因此可将整型变量声明为寄存器变量。 例如:
register int x; int f (register int x) {return x;}
4
2,动态数据区和动态生存期动态数据区也称为堆栈数据区。局部变量放置在堆栈空间中,堆栈结构是一个先进后出动态的存储结构,堆栈空间的变量常通过 SS堆栈段寄存器,EBP基址索引寄存器寻址。局部变量的生存期称为动态生存期。
先入栈的局部变量比后入栈的局部变量具有更长的生存期。调用函数随即建立堆栈数据区,函数返回则自动释放堆栈空间。
关键字 auto指出其后的变量放置在堆栈空间中,在函数内部定义的变量以及函数形参或在语句块中定义的局部变量缺省地具 auto存储属性,例如:
{int x;}
等价于 {auto int x;} int f(auto int x){return x;}
auto关键字在程序设计中倒是出现得非常少。
5
3.静态数据区和静态生存期关键字 static定义的变量称为静态变量,放置在静态数据区中,静态数据区常通过 DS:ESI 或 ES,EDI 寄存器来寻址。
静态数据区中的变量的生存期与程序的运行期间是等长的。全局变量和静态变量在全局数据区。
把贯穿程序运行期间的变量的生存期称为静态生存期。
静态全局变量、静态局部变量具有静态生存期,全局变量在静态数据区,文件作用域的全局变量也具有静态生存期。
外部全局变量可以通过 extern关键字为外部文件所共享,静态全局变量的连接属性是内部的,拒绝外部模块的直接访问,
6
4.堆区 heap area
堆区是通过系统内置的运算符 new或特定的函数如
malloc来建立的,堆空间是可以由用户控制的内存区域,运算符 new或特定的函数如 malloc为用户开辟一块内存区域用
new运算符分配的内存当它们从定义的所在范围退出时不自动释放。
new运算符返回它分配内存的首地址,须在合适的范围定义一个指针以访问这片堆中内存。 例如,
void main()
{ char * out = new char[20]; for (int i=0;i<20;++i)
{ if (i==0) { char * inner = new char[20]; } }
delete [ ] inner;
delete [ ] out;
}
7
指针 p可以指向堆空间,也可以指向其它内存空间。但在该指针指向堆空间的期间不要改动该指针的值,等到用
delete 释放该指针占有的堆空间后再将该指针另外派作它用。
如果改变了这个指针,当 delete运算符作用到改变后的指针上时,会引起系统内存管理的混乱。若必须改变这个指针,应将 new分配的地址保持在另一个指针中,例如:
int *p = new int [100]; int *ptemp =p; p++;
delete [ ] ptemp;
下面的运算导致堆空间的悬空,堆空间的首地址没有妥当保存:
int * p= new int [100];
int x; p=&x;
安全的用法是定义一个固定指针 a,例如:
int* const a=new int [100];
8
五,extern关键字与外部连接属性一个完整的程序由分别单独编译的不同的源程序或目标,
模块连接而成,每一个独立的功能模块可称为程序单元。
在程序单元之间共享变量、对象或函数名称的方式称为连接。外部连接属性的函数名称和全局数据名称由关键字
extern 来确定。它们可以在不同单元之间共享,因而跨单元这些全局名称的定义必须是唯一的。
关键字 extern可以加在全局变量、全局对象或全局引用等名称前,说明其后的名称在相关的程序单元中已经定义,
全局数据名称的外部连接说明的语法格式为:
extern 类名 全局数据名称 ;
extern type variable; //type为已声明的类型
9
如上的 extern连接语句是说明语句,不涉及变量的存储分配,它仅仅说明其后的名称在另外的位置或模块定义过,
经此说明的名称可以在该模块内随后的语句中索引。
例如:
extern int x;
extern const int y;
extern long & z;
extern int a[ ];
分别引入全局变量 x,全局常数 y,全局引用 z以及全局数组 a。
10
外部数据连接说明语句不能省去关键字 extern,省去关键字 extern之后导致其它的语义解释或语法错误。
此源程序中的函数若要为其它程序单元所调用,可在函数原型前明显地加关键字 extern:
extern 类型 函数名
(类型 1 形参 1,类型 2 形参 2,…,类型 n 形参 n);
extern type function
(type1 parm1,type2 parm2,…,typen parmn);
这样明显地说明在该模块内出现的函数名是有定义的,
程序连接的时候在其它模块中搜寻函数的实现部分。
11
可以在函数的定义部分冠以关键字 extern。
extern type function
(type1 parm1,type2 parm2,…,typen parmn)
{ 语句; }
例如,extern long* fg ();
extern long* fx ();
extern long f (int n)
{static int s=1; return s*=n; }
函数名具有全局的性质,其连接属性默认是外部的,通常在函数原型说明前不必加关键字 extern,这是大量库函数的简洁说明格式。
函数形参仅在堆栈空间,形参不用 extern 和 static修饰。即 int f ( extern int x ){ return x; }和
int f ( static int x ) { return x; }是错误的。
12
六,static关键字与内部连接属性
1.静态函数或内部函数在 C中 static关键字有两种主要含义:
a.将其后定义的变量始终驻留在静态数据区,但变量的作用域维持原先的性质,
b.限制其后的名称在本模块内访问,内部连接属性的数据名称和函数名称由 static限定,内部连接的函数名称和全局数据名称限制在特定的单元。
在特定单元全局名称的定义必须是唯一的。不同单元的相同内部名称是彼此无关的。
13
默认情况下,全局函数连接属性是外部的,这样程序中的其它模块可以访问外部连接属性的函数。可以用 static关键字说明一个全局函数,限制这个 static函数只在定义它的模块或文件内可见。
static关键字修饰的函数为静态函数。
格式为 (type,type1,typen 等为已声明的类型):
static type f (type1 parm1,type2 parm2,
…,typen parmn) ;
static type f (type1 parm1,type2 parm2,
…,typen parmn) { 函数体语句 ; }
14
[例 ] static关键字屏蔽函数在模块间连接
c.pp
#include<stdio.h>
extern int x; extern int y;
extern int a[ ];
void main ( )
{ printf ("%d,%d,%d,%d\n",x,y,a[0],a[1]); }
a.cpp
static long f (long x);
int x=f(1);
long f ( long x ) { return x; }
int a[ ]={ 3,4 };
15
b.cpp
static long f ( long x ){ return x; }
int y=f (2);
说明:
上面的工程文件由三个源文件 c.cpp,a.cpp,b.cpp构成。 a和 b文件中各有一个同名函数 f,因为前面加上 static限制,有效地屏蔽了目标模块单元之间的连接。
若去掉 static,则连接时弹出 Linking...b.obj,
error,"long __cdecl f(long)" already defined in a.obj。
16
2.静态全局变量和静态局部变量全局变量的连接属性允许是外部的,在全局变量前冠以
static关键字,则得到静态全局变量或内部全局变量。
static全局变量只在定义它的模块或文件内可见,静态全局变量与单文件结构中的全局变量用法是一致的。
在全局变量前加一个 static关键字可以放心地使用该变量而不必担心其它模块中同名全局变量的冲突,起到内部屏蔽的作用。
static关键字定义静态变量或静态数组的语法格式为:
static类名 变量名 ; static type variable;
static类名 数组名 [N]; static type a[ ]={初始化列表 };
17
经 static关键字定义的变量称为静态变量。 extern引入的语句是说明语句,可以不唯一。这里 static引入的是变量定义语句,导致静态变量的内存分配,是唯一的。
static关键字可以用于定义一个局部变量。在保持该变量的局部作用城的同时,赋予这个变量与程序相同的生存期。
非静态局部变量则位于堆栈空间中随着函数调用动态变化,而静态局部变量则常驻不变的数据区中,在程序调用之间保持其位置和数据的静态稳定性。可以用指针访问局部静态变量,而指针指向内层非静态局部变量,其结果是靠不住的。
18
#include <stdio.h> //定义静态变量并初始化为 0
void f()
{ static int n=0; n++; printf ("%d ",n); }
void main () { f(); f(); f(); }
输出结果,1 2 3
局部静态变量的一个特征是仅初始化一次。在上面的例子中,静态变量 n只有一次为零,第二次调用该函数后 n增值为 2,若去掉 static关键字则在每次函数调用时都把 n初始化为 0,此时输出结果 1 1 1。
下面的代码输出 4 5 6,去掉 static关键字则输出 4 8 1 。
void g ( int k )
{ static int n=k; n++; printf ("%d ",n); }
void main () { g(3); g(7); g(0); }
19
下面的代码输出 1 21 21,去掉 static关键字则输出:
1 1 1
void h ( )
{ static int n=0; n++; printf ("%d ",n); n=20; }
void main () { h(); h(); h(); }
全局变量和静态局部变量都存储在全局数据区。变量在全局范围定义初始化一次。静态变量在局部范围定义仅分配内存一次,仅在流程首次经过其定义点时被初始化一次。
非静态的局部变量随函数多次调用被重新分配不同的堆栈内存被重新初始化。
20
[例 ] 全局范围的初始化语句 [long x=fx();long& y=fy();]导致函数在 main之前调用。
#include<stdio.h>
typedef long LONG;
long fx () { LONG z; scanf ("%d",&z); return z;}
long& fy ()
{ static LONG q=5 ;
typedef long LINT;
static LINT z; scanf ("%d",&z);
if (z!=1) return z;
else return q;
}
LONG x=fx (); long& y=fy ();
21
void main( ) { printf ("y=%d,x=%d",y,x); }
程序某次交互运行结果为,程序另一次交互运行结果为:
20? 20?
30? 1?
y=30,x=20 y=5,x=20
上面的例题表明:在进入 main函数之前 C++程序就进入动态运行的交互过程,程序首先执行的是全局变量或引用声明语句中的初始化函数,这是 C语言不具备的特点。
22
[例 ]静态局部变量求 n! [例 ]静态全局变量求 n!
# include<stdio.h> # include<stdio.h>
extern long f (int n) static int s=1;
{ static int s=1; long f (int n)
return s*=n; { return s*=n; }
} void main ()
void main() { int k; for (k=1;k<3;k++)
{ int k ;for (k=1; k<4; k++) printf ("%d!=%d;",k,f(k));
printf ("%d!=%d; ",k,f (k));
printf ("%d!=%d; ",k,f (k));
} }
23
[例 ]局部变量求 n!阶乘
#include <stdio.h>
long f (int n)
{ long s=1;int j=n;
for (; j>0; j--) s*=j; return s; }
void main (void)
{ int k ; for ( k=1; k<4; k++)
printf ("%d!=%d; ",k,s*=k);
}
三个程序运行都输出,1!=1;2!=3;3!=6;
代码 { int s=1; for (int n=1;n<4;n++)
printf ("%d!=%d; ",n,s*=n); }
也输出 1!=1;2!=3;3!=6;
24