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

 

转载于:https://www.cnblogs.com/YiShen/p/9875177.html

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐