4.1 变量的初始化和结束化

  部分数据类型的变量是有初始化与结束化过程的。在什么时候调用这些初始化和结束化过程,取决于所定义变量的生命周期。对于局部变量来说,其生命周期自进入例程开始,到结束例程时终出。也就是说,局部变量的初始化与结束化过程,在例程的初始化与结束化过程中发:。
  具有初始化与结束化过程的数据类型如表4-1所示。

 

4.1.1初始化的必要性

  为什么要初始化?或者更进一步地提出问题:为什么仅有这些类型要被初始化呢?
  应该注意到,上表与“表2-3发生引用计数的数据类型”和“表2-4有‘增加引用’行为的数据类型”有相似之处。事实上,正是“引用计数机制”使得这些数据类型的变量必须被初始化。
  以长字符串为例,这样的代码:

var
    Str:String;
begin
    Str:='abc';
end;

  在执行“Str:='abc';”时,系统将调用_LStrAsg()来赋值。而_LStrAsg()中,如果“Pointer(Str)<oNi1”,则需要将Str的引用计数减1。这时,如果局部变量Str未经初始化就被使用,则Pointer(Str)就是一个指向随机地址的指针,这将导致_LStrAsg()中的引用计数操作可能访问非法地址。
  在这种情况下,必须将变量初值置为Nil,以使得其他内部例程可以确认该变量并没有在堆上分配内存。

 

4.1.2如何初始化

  初始化的目的就是将变量初值置为Nil。
  Delphi中,对于单独定义的长字符串、宽字符串、动态数组、变体以及接口变量,会直接操作栈来完成初始化。关于这一点,可以参见第3章中“完全汇编例程与内嵌汇编例程”的有关描述。
  记录和数组的初始化如同它们的“增加引用”一样,也是操作其中的一些子域。所使用的
  “初始化表”与“增加引用”中使用的“引用列表(数组)”是同一个表。
  记录的“引用列表”中记录了所有具有引用计数特征的域。数组的引用列表要相对简单一些,只有数组基于长字符串、宽字符串、动态数组、变体以及接口变量定义时,才会有一个记录来保存基类型的信息。
  以数组为例,其初始化代码如下:

procedure _InitializeArray(p: Pointer; typeInfo: Pointer;elemCount: Cardinal);
var
    FT: PFieldTable;
//
    case PTypeInfo (typeInfo). Kind of
    tkArray:
    begin
        FT:=PFieldTable(Integer(typeInfo)+
        Byte(PTypeInfo(typeInfo). Name[0]));
        while elemCount>0 do
        begin
        Initializearray(P, FT. Fields[0]. TypeInfo", FT. Count);
        Inc(Integer(P), FT. Size);
        Dec(elemCount);
        end;
    end;
end;

  这里使用FT变量来定义初始化记录的字段表。由于最多只有一个记录,所以FT.Fields[0].TypeInfo代表了所有数组元素的数据类型。考虑到多维数组的情况,这里递归调用_InitializeArray()来初始化每一个数组元素。

 

4.1.3如何结束化

  结束化过程要相对简单得多。局部变量在栈上占用的空间通过栈操作即可释放。记录和数组中的元素可以通过初始化表来结束化。对于不在初始化表中的域或数组元素,在释放记录和数组占用的栈空间的同时就被释放了。
  如果变量、域(记录)或元素(数组)具有引用计数特征,那么结束化过程中,都必将调用到相应数据类型中减少引用计数的例程—例如长字符的内部例程_LStrC1r()和_LStrArrayClr()。
  Newl与Initialize0如果是全局变量,则记录变量的空间被分配在数据区,这会发生两种情况:

  • 操作系统的文件装载程序会自动将BSS节初始化为0,因此无初值的记录变量的初始化可以自动完成;
  • 有初值的记录变量不存在初始化的问题。由于它被放在DATA节中,因而也不会被自动初始化。

  如果记录(而非记录指针)是局部变量,那么会有整个记录大小的空间直接在栈上分配,这时编译器会在例程初始化代码中加入_InitializeRecord()来初始化这个变量。例如:

type
TRec=record
    i:integer;
    s:String;
end;
procedure Initializerest;
var
    r:TRec;
begin
    r.i:=3;
end;

  这段代码在CPU窗体的汇编代码是这样的:

Project2. dpr.16: procedure Initializerest;
Project2. dpr.17: begin
00407D4055push ebp
00407D41 8BEC       mov ebp, esp
00407D43 83C4F8      add esp,-$08//在栈上分配Sizeof(TRec)大小的空间
00407D46 8D45F8      lea eax,[ebp-$08]//取变量r的地址
00407D49 8B15247D4000  mov edx,[$00407d24]// TypeInfo(TRec)
00407D4F E8D8BCFFFF   call @InitializeRecord//记录初始化
00407D54 33C0       xor eax,eax//以下代码向栈上填入一个异常帧
00407D56 55        push ebp
00407D57 68857D4000   push $00407d85
00407D5C 64FF30      push dword ptr fs:[eax]
00407D5F 648920      mov fs:[eax],esp
Project2.dpr.18:r.i:=3

  编译器在栈上填入异常帧,这使得无论例程InitializeTest()中的代码运行情况如何,例程结束化时的_FinalizeRecord()调用都必然被执行到。这相当于如下的代码:

InitializeRecord(r,TypeInfo(TRec));
try
//1.以上代码被编译在begin关键字的实现代码中
//在InitializeTest()中的代码
//2.以下代码被编译在end关键字的实现代码中
finally
_FinalizeRecord()
end;

  但如果是记录指针,那么开发人员通常会调用New()例程来为它分配空间。为此编译器在New()例程中调用了_Initialize()来初始化:

procedure _Initialize(p:Pointer;typeInfo:Pointer);
begin
    _InitializeArray(p,typeInfo,1);
end;

function _New(size:Longint;typeInfo:Pointer):Pointer;
begin
    GetMem(Result,size);
    if Result <>nil then
        _Initialize(Result,typeInfo);
end;

  接下来的代码,看起来像是一个怪圈:_InitializeRecord()将再调用例程InitializeArray()。_InitializeRecord()的实现代码如下:

procedure _InitializeRecord(p: Pointer; typeInfo: Pointer);
var
    FT: PFieldTable;
    I: Cardinal;
begin
    FT:=pFieldTable(Integer(typeInfo)+Byte(PTypeInfo(typeInfo). Name[0]));
    for I:=FT. Count-1 downto 0 do
        _InitializeArray(Pointer(Cardinal(P)+FT. Fields[I]. Offset),
    FT. Fields[I]. TypeInfo^,1);
end;

  _InitializeRecord()从类型信息记录(FieldTable)中列举每一个使用了引用机制的域,并将每个域作为“一个元素的数组”,再次调用_InitializeArray()来初始化该域的值。
  可以这样来说明_InitializeRecord()与_Initialize()之间的差异:
  _Initialize()中将记录作为“一个元素的数组”传入_InitializeArray();而在_InitializeRecord()中,将通过typeInfo列举记录中每一个需要初始化的域,并将该域(地址)作为“一个元素的数组”传入  _InitializeArray()。

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值