2.2.1全局变量与局部变量
根据变量在源代码中定义的位置不同,可以分成局部变量和全局变量两种。此外,在多线程应用程序中,可以定义线程变量(ThreadVar)。下面的例子描述局部变量和全局变量在内存占用上的不同:
program Project3; {$APPTYPE CONSOLE} {$R *.res} uses System.SysUtils; var GVar1: Integer; GVar2: integer; function GetAddr(const aVar): string; begin Result := IntToHex(Integer(@aVar), 8); end; procedure WriteLocalVar; var LVar1: Integer; LVar2: Integer; begin Writeln('Loval Var1: ' + GetAddr(LVar1)); Writeln('Loval Var2: ' + GetAddr(LVar1)); end; begin Writeln('Globl Var1: ' + GetAddr(GVar1)); Writeln('Globl Var2: ' + GetAddr(GVar2)); WriteLocalVar; Readln; end.
输出结果是类似于这样的:
Globl Var1: 004258B8
Globl Var2: 004258BC
Loval Var1: 0019FF48
Loval Var2: 0019FF48
全局变量在应用程序的数据区分配,而局部变量在应用程序的栈上进行分配。因此,相对于定义的顺序,多个全局变量的地址是递增的,而局部变量递减。此外,由于每次调用函数时,栈顶可能发生变化,因此,局部变量的地址是变化的,而全局变量是不变的。
全局变量在编译期就被决定,它存在于可执行模块(.EXE或.DLL等)的文件影像内部。因此,在载入文件的同时,全局变量的内存就被分配了。而局部变量是在例程被调用的时候才被分配的。
局部变量总是在栈上分配。
2.2.2动态分配的内存
变量在运行期动态分配的内存是一个例外。这里说的“动态分配的内存”?是指用GetMem()等在代码中显式分配的内存。例如系统源码中对对象、字符串(短字符串除外)、动态数组~等进行的内存分配,以及在用户代码中使用GetMem()来分配的内存等。这些内存将在堆上进行分配。
堆分配与栈分配不同。但通常不会有太多人注意到这个细节。下面的例子阐述了这一点。
program Project3; {$APPTYPE CONSOLE} {$R *.res} uses System.SysUtils; function GetAddr(const aVar): string; begin Result := IntToHex(Integer(@aVar), 8); end; procedure WriteLocalVar; var StackVar: string; begin StackVar := '0123456789'; Writeln('Stack Var : ', GetAddr(StackVar), 'Length: ': 15, sizeof(StackVar)); Writeln('Variable Str : ', IntToHex(Integer(@StackVar[1]), 8), 'Length: ': 15, Length(StackVar)); end; begin WriteLocalVar; Readln; end.
输出结果是类似这样的:
Stack Var : 0019FF64 Length: 4 Variable Str : 04448104 Length: 10
在这个例子中。局部变量Stack Var只在栈上分配了4Bytes。但是作为字符串,它的10Bytes的内容都被分配在堆中。因此,输出结果中的“Stack Var”与“Variable Str”的地址并不一样。
2.2.3换一个方式来理解
通常可以认为:变量由两个部分组成,即变量自身和变量的内存占用。
“变量自身”遵循变量分配的基本原则,其大小可以参见表1-1,也可以直接由函数SizeOf()取出。如果是简单类型,则存放变量值。
如果变量是复杂的类型(如一些构造类型或字符串类型),则“变量自身”仅存放“变量的内存占用”的指针。“变量的内存占用”的大小取决于该变量的数据结构,通常有专用的或独立设计的函数来取其大小。例如函数GetLength()用来取字符串或者数组大小。
如果没有特殊的代码实现,“局部变量“存在于栈,而“变的内存占用”存在于堆。
基于:小节的例子,图2.1简单地描述了这种关系”。
全局变量和常量的内存分配的更多细节,可以参见第6章“Delphi的积木艺术(PE)”中第3节有关“数据节与代码节”的内容。
2.2.4常量
关于常量,上面已经提到:类型化常量与全局变量在同·内存空间中被分配,且使用相同的分配规则。——但这不包括有关“变量的内存占用”的规则。
此外,普通的、非类型化的常量(真常量)与变量也存在着·些不同。
基于Delphi的常量定义规则,编译器将常量作为立即数处理:将值直接编码到指令中。
因此它们不可能有内存占用,当然也不可能有具体的内存地址值。
字符串常量是例外的。显然字符串不能当作立即数处理,因此编译器总是将宁符串常量与代码放在一起,并将它的地址指针直接编码到指令中。
{$APPTYPE CONSOLE} {$DEFINE MORECONST} uses SysUtils; const DefineCon1 = '0123456789'; {$IFDEF MORECONST} DefineCon2 = '9876543210'; {$ENDIF} var DefineVar1 : string = '0123456789'; function GetAddr(Const aVar) : String; begin Result := IntToHex(Integer(@aVar), 8); end; function GetDataAddr(Const aVar) : String; begin Result := IntToHex(Integer(aVar), 8); end; begin Writeln('Const 1 Addr : ', GetAddr(DefineCon1), 'Length: ':15, SizeOf(DefineCon1)); Writeln('Const 1 Data : ', GetAddr(DefineCon1[1]), 'Length: ':15, Length(DefineCon1)); {$IFDEF MORECONST} Writeln('Const 2 Addr : ', GetAddr(DefineCon2), 'Length: ':15, SizeOf(DefineCon2)); {$ENDIF} Writeln; Writeln('Variable Addr : ', GetAddr(DefineVar1), 'Length: ':15, SizeOf(DefineVar1)); Writeln('Variable Data : ', GetDataAddr(DefineVar1), 'Length: ':15, Length(DefineVar1)); readln; end.
尝试定义和取消编译条件“MORECONST”并编译运行,可见输出的结果都是一致的。因此是否定义常量DefineCon2,对于变量DefineVar1的起始地址没有影响。这意味着字符串常量与变量使用的是不同的内存空间。在上例中,DefineCon1的地址与其数据在同一地址,这是由于字符串常量总是直接传入实际地址。
字符串常量总是被分配内存。关于其分配内存的细节,可以参考第6章中“数据节与代码节”的内容。
元素数为64-256个(即大小超过4)的集合常量,也使用与字符串常量类同的规则。使用类型强制转换方式声明的常数,按照转换到的目标类型来分配内存。但是,它不是类型化常量。例如:
const //普通常量,按约定,占用空间取决于能容纳该值的最小类型: Byte I_Byte=5; //类型强制转换的常量。占用空间取决于转换到的目标类型: DWORD I_DWord =DWORD(5); //类型化常量,与工_DWord占用空间大小相同,但却是在数据节中。 I_TypeDword:DWORD=5;
所有评论(0)