第7章 程序的结构 四、生存期与存储属性 五、extern关键字与外部连接属性 六、static关键字与内部连接属性
四、生存期与存储属性 生存期是指数据存在而不消失的时间。程序在内存被分 为四个区: 1.代码区,执行指令被加载到代码区。 2.静态数据区 3.动态数据区。 4.堆内存区。 内存的数据和代码由寄存器操作和周转。变量的生存期 是由其存储属性控制的,变量属于一个确定的存储属性。
系统有四个部分可供存放变量,相应的生存期与之密切 联系。 1. 寄存器register 关键字register允许用户将局部变量声明为寄存器存储 属性,如此存放的数据生存期是瞬态的。 编译器根据寄存器的使用情况做出优化处理,未必将 register变量一定存放在寄存器中,寄存器资源紧张的时 候,经关键字register限定的变量可能安排在堆栈中,因而 其生存期是类似于auto存储属性的局部变量。 32位通用寄存器最多可容纳4字节的数据,因此可将整 型变量声明为寄存器变量。例如: register int x; int f (register int x) {return x;}
2. 动态数据区和动态生存期 动态数据区也称为堆栈数据区。局部变量放置在堆栈空 间中,堆栈结构是一个先进后出动态的存储结构,堆栈空间 的变量常通过SS堆栈段寄存器:EBP基址索引寄存器寻 址。局部变量的生存期称为动态生存期。 先入栈的局部变量比后入栈的局部变量具有更长的生存 期。调用函数随即建立堆栈数据区,函数返回则自动释放堆 栈空间。 关键字auto指出其后的变量放置在堆栈空间中,在函 数内部定义的变量以及函数形参或在语句块中定义的局部变 量缺省地具auto存储属性,例如: {int x;} 等价于 {auto int x;} int f(auto int x){return x;} auto关键字在程序设计中倒是出现得非常少。
3.静态数据区和静态生存期 关键字static定义的变量称为静态变量,放置在静态数据 区中,静态数据区常通过DS:ESI 或ES: EDI 寄存器来寻址。 静态数据区中的变量的生存期与程序的运行期间是等长 的。全局变量和静态变量在全局数据区。 把贯穿程序运行期间的变量的生存期称为静态生存期。 静态全局变量、静态局部变量具有静态生存期,全局变量在 静态数据区,文件作用域的全局变量也具有静态生存期。 外部全局变量可以通过extern关键字为外部文件所共 享,静态全局变量的连接属性是内部的,拒绝外部模块的直 接访问.
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; }
指针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];
五、extern关键字与外部连接属性 一个完整的程序由分别单独编译的不同的源程序或目标. 模块连接而成,每一个独立的功能模块可称为程序单元。 在程序单元之间共享变量、对象或函数名称的方式称为 连接。外部连接属性的函数名称和全局数据名称由关键字 extern 来确定。它们可以在不同单元之间共享,因而跨单 元这些全局名称的定义必须是唯一的。 关键字extern可以加在全局变量、全局对象或全局引用 等名称前,说明其后的名称在相关的程序单元中已经定义, 全局数据名称的外部连接说明的语法格式为: extern 类名 全局数据名称; extern type variable; //type为已声明的类型
如上的extern连接语句是说明语句,不涉及变量的存储 分配,它仅仅说明其后的名称在另外的位置或模块定义过, 经此说明的名称可以在该模块内随后的语句中索引。 例如: extern int x; extern const int y; extern long & z; extern int a[ ]; 分别引入全局变量x,全局常数y,全局引用z以及全局数 组a。
外部数据连接说明语句不能省去关键字extern,省去关 此源程序中的函数若要为其它程序单元所调用,可在函 数原型前明显地加关键字extern: extern 类型 函数名 (类型1 形参1,类型2 形参2,…,类型n 形参n); extern type function (type1 parm1, type2 parm2, …,typen parmn); 这样明显地说明在该模块内出现的函数名是有定义的, 程序连接的时候在其它模块中搜寻函数的实现部分。
可以在函数的定义部分冠以关键字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; }是错误的。
六、static关键字与内部连接属性 1.静态函数或内部函数 在C中static关键字有两种主要含义: 作用域维持原先的性质 . b.限制其后的名称在本模块内访问. 内部连接属性的数 据名称和函数名称由static限定. 内部连接的函数名称和全局 数据名称限制在特定的单元。 在特定单元全局名称的定义必须是唯一的。不同单元的 相同内部名称是彼此无关的。
默认情况下,全局函数连接属性是外部的,这样程序中 的其它模块可以访问外部连接属性的函数。可以用static关 键字说明一个全局函数,限制这个static函数只在定义它的 模块或文件内可见。 static关键字修饰的函数为静态函数。 格式为(type, type1, typen 等为已声明的类型): static type f (type1 parm1, type2 parm2, …,typen parmn); …,typen parmn) { 函数体语句; }
[例] 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 };
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。
2.静态全局变量和静态局部变量 全局变量的连接属性允许是外部的,在全局变量前冠以 static关键字,则得到静态全局变量或内部全局变量。 全局变量与单文件结构中的全局变量用法是一致的。 在全局变量前加一个static关键字可以放心地使用该变 量而不必担心其它模块中同名全局变量的冲突,起到内部屏 蔽的作用。 static关键字定义静态变量或静态数组的语法格式为: static类名 变量名; static type variable; static类名 数组名[N]; static type a[ ]={初始化列表};
经static关键字定义的变量称为静态变量。extern引入 定义语句,导致静态变量的内存分配,是唯一的。 static关键字可以用于定义一个局部变量。在保持该变 量的局部作用城的同时,赋予这个变量与程序相同的生存 期。 非静态局部变量则位于堆栈空间中随着函数调用动态变 化,而静态局部变量则常驻不变的数据区中,在程序调用之 间保持其位置和数据的静态稳定性。可以用指针访问局部静 态变量,而指针指向内层非静态局部变量,其结果是靠不住 的。
#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); }
下面的代码输出1 21 21,去掉static关键字则输出: 1 1 1 void h ( ) { static int n=0; n++; printf ("%d ", n); n=20; } void main () { h(); h(); h(); } 全局变量和静态局部变量都存储在全局数据区。变量在 全局范围定义初始化一次。静态变量在局部范围定义仅分配 内存一次,仅在流程首次经过其定义点时被初始化一次。 非静态的局部变量随函数多次调用被重新分配不同的堆 栈内存被重新初始化。
[例] 全局范围的初始化语句[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 ();
void main( ) { printf ("y=%d, x=%d", y, x); } 程序某次交互运行结果为: 程序另一次交互运行结果为: 20 20 30 1 y=30,x=20 y=5,x=20 上面的例题表明:在进入main函数之前C++程序就进 入动态运行的交互过程,程序首先执行的是全局变量或引用 声明语句中的初始化函数,这是C语言不具备的特点。
[例]静态局部变量求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)); } }
[例]局部变量求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;
请打开“第7章(3).ppt”