2.4数据结构相关的例程

  编译器实现了基础数据类型(以及相关的强制转换机制),最小化的Delphi内核实现了可执行模块装载程序。因此,利用这样的最小化内核能够编译的应用只能做到“数据结构十内置运算符”。
  在Delphi中使用数据结构,并不是要编译器简单地理解这些数据结构。更重要的是,必须有一套相关的内核函数和过程的支持。在源代码中,这些与基本数据结构相关的例程名通常是以“”开始的,它们虽然声明在Interface节中,却无法在用户代码中直接调用。这些例程包括:

  • 标准Pascal的内置例程;
  • 短字符串操作例程;
  • 长字符串操作例程;
  • 宽字符串操作例程;
  • 未分类的字符串操作例程;
  • 集合操作例程;
  • 数组(含动态数组)操作例程;
  • 记录操作例程。

 

2.4.1标准Pascal的内置例程

procedure _COS;
procedure _EXP;
procedure _INT;
procedure _SIN;
procedure _FRAC;
procedure _ROUND;
procedure _TRUNC;
procedure _Pow10;

  Delphi通过内嵌汇编,用浮点运算指令完成了上述例程。在这部分的(包括其后的)代码中,有一个有趣的注释:

{Procedures and functions that need compiler magic)
//需要编译魔法的程序和函数

  应该注意到,COS()的接口定义与Delphi帮助中并不相同。在帮助中,定义是:

function Cos(X:Extended):Extended;

  这样的差异也表现在其他的例程中。因此,“compiler magic”表明了Delphi实际对这些内部例程并不进行强类型检测。在编译期,内部例程的参数入口、寄存器的使用、类型检测和返回值等,编译器中都是用专门的代码来处理的。
  有些时候,对于同一个函数声明,在内部需要使用多个函数来实现。这时,也会用到“compiler magic”。例如function writeln(),在内核中,使用了超过25个函数来实现writeln(),以应付不同的入口参数和自Pascal以来形成的古怪的语法。
  此外,“compiler magic”还表明可以用内置例程来替代语言的操作符和运算符。例如在后面将提到的“PChar类型字符串操作例程”和“集合操作例程”等例程中,就大量地使用了这种编译器支持的技术。
  function Pi也是使用“compiler magic”的一个典型例子。在System.pas中,不可能找到PI(O的实现代码,因为这个函数是在编译期计算的。看起来,它更像是一个常量,而不是函数。下面的汇编码是将“function Pi”的结果赋给局部变量e:

Unitl. pas.29:e:=pi;
0044C878 C745F035C26821   mov [ ebp-$10],$2168c235
0044C87F C745F4A2DAOFC9   mov [ ebp-$0c], Sc90fdaa2
0044C886 66C745F80040     mov word ptr [ ebp-$08],$4000

  在Delphi中,Pi的十六进制值是$4000C90FDAA22168C235。上面的代码,其实就是直接修改栈上的变量e,而不是通常理解的“将函数Pi()的结果返回到变量e”。


2.4.2字符串操作例程

■短字符串操作例程

//表达式、运算和运算符
procedure _PStrCat;
procedure _PStrNCat;
procedure PStrCpy(Dest:PShortstring; Source:PShortString);
procedure _PStrNCpy(Dest:PShortstring; Source:PShortString; MaxLen:Byte);
procedure _PStrcmp;
//操作例程
procedure _Copy{(s:Shortstring; index,count:Integer):ShortString};
procedure _Delete{(vars:openstring; index,count:Integer)}procedure _Insert{source:Shortstring;var s:openstring; index:Integer}procedure _Pos{substr:ShortString;s:Shortstring}:Integer};
procedure _SetLength{s:PShortstring;newLength:Byte};
//类型转换
procedure _SetString(s:PShortString; buffer:PChar; len:Byte);

  基本上,这些例程都是用来替代运算符或实现运算表达式的。下例对照地进行说明:

var
    s1,s2,s3:String[20];
begin
    s1:=s2i       //call procedure _PStrNCpy()
    s2:=s1+s2:    //call procedure _PStrCpy(),and more
    s1:=s1+s2;    //call procedure _PStrNCat()
end;

  在上面的源码所对应的汇编码中,“s2:=s1+s2;”是很有代表性的:

Unit1.pas.36:s2:=s1+s2;//call procedure _pStrCpy()
0044C8D6     8D55DB             1ea edx,[ebp-$25]             //edx暂存s1地址
0044C8D9     8D85B4FEFFFF      lea eax,[ebp-$0000014c]      //eax暂存中间字符串(temp)地址
0044C8DF     E83061FBFF         call epstrcpy                  //temp:=s1
0044C8E4     8D55C6             1ea edx,[ebp-$3a]
0044C8E7     8D85B4FEFFFF      1ea eax,[ebp-$0000014c]
0044C8ED     B128              mov c1,$28                    //最大长度为$28
0044C8EF     E8FO60FBFF         ca11 ePStrNCat                 //temp:=temp+s2
0044C8F4     8D95B4FEFFFF      1ea edx,[ebp-$0000014c]
0044C8FA     8D45C6             lea eax,[ebp-$3a]
0044C8FD     B114              mov cl,$14/最大长度为$14
0044C8FF     E81C61FBFF         ca11 ePStrNCpy                 //从temp中复制值到s2

  这里,[ebp-$0000014c]是暂存的一个中间字符串(temp)。三次内部例程调用都进行了复制操作,而且,从temp到s2的PStrNCpy()操作还表明:如果s2不够长,则在以前的代码中,“temp:=temp+s2”会有一些不必要的内存复制操作。
  简而言之,在Delphi内部实现上,短字符串是一个低效率的字符串类型——不要以为在Delphi中,短字符串的效率会如同它在Pascal中一样高。


■长字符串操作例程

//表达式、运算和运算符
procedure _LStrAsg(var dest; const source);
procedure _LStrLAsg(var dest; const source);
procedure _LStrCat{var dest:Ansistring;source:AnsiString)}procedure _LStrCat3{var dest:Ansistring;sourcel:AnsiString;source2:Ansistring};
procedure _LStrCatN{var dest:Ansistring;argcnt:Integer;…);};
procedure _LstrCmp{left:Ansistring;right:Ansistring};
//类型转换
procedure _IStrPromPCharLen(var Dest:Ansistring;Source:PAnsiChar;Length:Integer);
procedure _LStrFromPWCharten(var Dest:Ansistring;Source:PWidechar;Length:Integer);
procedure _LStrFromchar(var Dest:AnsiString;Source:Ansichar);
procedure _LStrFromwchar(var Dest:Ansistring;Source:Widechar);
procedure _LStrFrompChar (var Dest:Ansistring;Source:PAnsichar);
procedure _LStrFrompwChar (var Dest:Ansistring;Source:pwidechar);
procedure _LStrFromstring(var Dest:Ansistring;const Source:ShortString);
procedure _LStrFromArray(var Dest:Ansistring;Source:PAnsichar;Length:Integer);
procedure _LStrFromWArray(var Dest:Ansistring; Source:pWidechar; Length:Integer);
procedure _LStrFromwstr(var Dest:Ansistring; const Source:Widestring);
procedure _LStrTostring{(var Dest:Shortstring;const Source:Ansistring;MaxLen:Integer)};
function _LStrTopChar(const s:Ansistring):pChar;

//长度计数和引用计数位的操作
function _LStrLen(const:Ansistring):Longint;
function _LStrAddRef(var str):Pointer;
procedure _LStrClr(var S);
procedure _LStrArrayClr(var StrArray; cnt:longint);

//操作例程
procedure _LStrCopy{const:Ansistring;index,count:Integer):Ansistring}procedure _LStrpeletet{var s:Ansistring;index,count:Integer};
procedure _LStrInsert{const source:Ansistring;vars:Ansistring;index:Integer}procedure _LStrPos{const substr:Ansistring;consts:Ansistring):Integer}procedure _LStrSetLength{var str:Ansistring;newtength:Integer};
procedure _LStrofchar{c:Char;count:Integer):Ansistring)};
function _Newansistring(length:Longint):Pointer;

  很大一部分内核字符串例程的作用,是对运算符和表达式的替换。如下例:

var
    GAStr1:AnsiString;
procedure TestAnsiStr;
var
    AStr1,AStr2,AStr3:AnsiString;
begin
    AStr1:=;                        //call procedure _Lstrclrt)
    AStrl:='abcde';                    //cal1 Procedure _LStrLAsg()
    GAStr1 :='abcde';                //call Procedure LStrAsg()
    AStr1:=AStr1+'abcd';            //call procedure _LStrcat()
    if AStrl=GAStrl then             //call procedure _LStrCmp()
        AStr1:=AStrl+'abcd'+'gqq';    //call procedure _LStrCatN()
end;

  其中,向局部变量AStr1赋值调用的是LStrLAsg(),而向全局变量GAStr1赋值时,调用的却是LStrAsg(),这种差异是值得注意的。
  此外,运算中的类型兼容和强制类型转换也会导致内部函数的调用。如下例:

var
    AStr:Ansistring;
    CharArr:array [0..10] of char;
    D:PChar;
begin
    //类型兼容是通过内核例程来支持的
    Astrl := CharArr;//call procedure _LStrFromArray()
    
    //(部分)类型强制转换也需要询用内核例程
    p := PChar(AStr);// call procedure _LStrToPChar()
end;

  部分内核函数的代码是值得仔细研读的。例如长字符串的引用计数管理实际上由两个例程来实现:

function _LStrAddRef(var str):Pointer;
procedure _LStrClr(var S);

  PUREPASCAL版本的实现代码如下:

function _LStrAddRef(var str):Pointer;
    P:PStrRec;
begin
    P:=Pointer(Integer(str)-sizeof(StrRec));//取负偏移
    if P<>nil then
        if P.refcnt >=0 then
        InterlockedIncrement(P.refcnt);
    Result:=Pointer(str);
end;

procedure _LStrClr(var S);
var
    P:PStrRec;
begin
    if Pointer(S)<>nil then
    begin
    P:=Pointer(Integer(S)-Sizeof(StrRec));//取负偏移
    Pointer(S):=nil//字符串置空,相当于s:=
    if P.refCnt>0 then
        //减一,如果减一后P.refcnt=0,则
        //在堆上释放p指向的内存块(即字符串的内存占用,含负偏移)
        if InterlockedDecrement(P.refCnt)=0 then
            FreeMem(P);
    end;
end;

  代码中涉及了PStrRec记录。PStrRec记录是长字符串的内存占用的负偏移上的前8个字节的记录定义。定义如下:

type
PStrRec="StrRec;
StrRec=packed record
    refant:Longint;
    1ength:Longint;
end;

  因此,可以直接用P.refcnt来修正引用计数,也可以用P.1ength来修改字符串长度。
  _LStrAddRef()通过调用API InterlockedIncrement)将引用计数加1。
  InterlockedIncrement()是一个原子操作函数,可以保证不会有两个操作同时作用于P.refcnt。类似地,LStrC1r()中使用InterlockedDecrement()来将引用计数减1。
  在procedure_LStrLAsg()中可以见到对它们的调用:

procedure _LStrLAsg(var dest;const source);
var
P:Pointer;
begin
    P:=Pointer(source);
    L.StrAddRef (P)i//增加 source的索引计数
    P:=Pointer(dest);
    Pointer(dest):=Pointer(source);//将 dest的值直接指向 source
    LStrClr(P);//减少dest的索引计数.如果dest的内存空间不
    //再被其他变量引用,则释放
end;

可以看出,长字符串赋值时,是将dest的值直接指向source,并同时修正source和dest的引用计数。

 

■宽字符串操作例程

//表达式、运算和运算符
procedure _wStrAsg(var Dest:Widestring;const Source:Widestring);
procedure _WStrLAsg(var Dest:WideString;const Source:Widestring);
procedure _wStrcat(var Dest:WideString;const Source:WideString);
procedure _wstrcat3(var Dest:Widestring;const Sourcel,Source2:WideString);
procedure _wStrCatN{var dest:Widestring;argcnt:Integer;..};
procedure _wStrCmp(left:Widestring;right:widestring);

//类型转换
procedure _wStrFromPCharLen(var Dest:Widestring;Source:PAnsiChar;Length:Integer);
procedure _wStrFrompwCharLen(var Dest:Widestring;Source:PWideChar;CharLength:Integer);
procedure _WStrFromchar(var Dest:Widestring;Source:Ansichar);
procedure _WStrFromwChar(var Dest:Widestring;Source:WideChar);
procedure _WStrFromPChar(var Dest:Widestring;Source:PAnsichar);
procedure _WStrFrompWChar(var Dest:Widestring;Source:PwideChar);
rocedure _wStrFromString(var Dest:Widestring;const Source:ShortString);
procedure _WStrFromArray(var Dest:Widestring;Source:PAnsiChar;
Length:Integer);
procedure _WStrFromwArray(var Dest:Widestring;Source:PWidechar;Length:Integer);
procedure _wStrFromtStr(var Dest:Widestring;const Source:Ansistring);
procedure _wStrTostring(Dest:PShortstring;const Source:WideString;MaxLen:Integer);
function _wStrToPWChar(const S:Widestring):PWideChar;

//长度计数和引用计数位的操作
function _wStrLen(const s:Widestring):Integer;
procedure _WStrClr(var S);
procedure _wStrArrayClr(var StrArray;Count:Integer);
function _WStrAddRef(var str:WideString):Pointer;

//操作例程
function _wStrcopy (consts:Widestring;Index,Count:Integer):WideString;
procedure _wStrDelete(var S:WideString;Index,Count:Integer);
procedure _wStrInsert(const Source:Widestring;var Dest:Widestring;Index:Integer);
procedure _wStrpos{ const substr:Widestring;const s:Widestring):Integer)};
procedure _wStrSetLength(var s:widestring;NewLength:Integer);
function _wstrofwchar (Ch:widechar;Count:Integer):Widestring;
function _Newidestring(Chartength:Longint):Pointer;

  不难看出,宽字符串与长字符串的内核例程是一一对应的。
  但是,前面提到过“宽字符串没有引用计数位”。那么,内部例程中_WStrAddRef()有什么意义呢?显然,必须要有新的procedure_WStrAsg()实现方法以及虚拟的引用计数管理机制,才能适应宽字符串的内存结构。
  具体的实现代码如下:

procedure _wStrAsg(var Dest:Widestring;const Source:Widestring);
asm
    {->BAxPointer to Widestring }
    {EDX Pointer to data}
    TEST     EDX,EDX
    JE       _WStrClr//如果Source是空串,则Dest置空事
    MOV      ECX,[EDX-4]//取长度计数位
    SHR      ECX,1//Length(Source)div2
    JE       _WStrC1r//如果结果为0,则Dest置空串
    PUSH     ECX
    PUSH     EDX
    PUSH     EAX
    //按Source的长度重分配Dest的空间(以字为单位),并将Source内容复制到Dest
    CALL    SysReA1locStringLen
    TEST    EAX,EAX
    //函数调用错。内存不够。弹出系统错误:reoutofMemory
    JE        WStrError
end;

function _WStrAddRef(var str:Widestring):Pointer;
asm
    MOV     EDX,[EAX]
    TEST    EDX,EDX
    JE      @@1
    //如果str是空串,则返回空串
    PUSH   EAX//pointer(str)入栽暂存
    MOV   ECX,[EDX-4]
    SHR     ECX,1
    PUSH    ECX
    PUSH    EDX
    //调SysA11ocStringten(str,1ength(str));返回新字符率地址在EAX
    CALL    SysA1locStringLen
    POPE    DX//将暂存的 pointer(str)出栈
    TEST    EAX,EAX
    JE      WStrError
    MOV     [EDX],EAX//使str指向SysA1locstringten()返回的新串地址
@@1:
end;
procedure _wstrclr(var s); asm {->EAX Pointer to widestring} MOV EDX,[EAX] TEST EDX,EDX JE @@1//如果是空串,则退出.否则将释放串 MOV DWORD PTR[EAX],0//置字符串的#0结束符 PUSH EAX PUSH EDX CALL SysFreestring//释放串 POP EAX @@1: end;

  可以看到:

  •   _WStrASg()总是将一个源字符串的拷贝赋给目标字符串,而从不执行引用计数例程_WStrAddRef()。
  •   _WStrAddRefC)调用后,将会有一个新的字符串产生,且入口变量Str与返回值Resu1t都指向新字符串。
  •   WStrC1r()总是直接释放一个非空的字符串。

  因此,这些函数并没有与长字符串例程相对应的引用计数功能。Delphi保留这些例程,从而使编译器能够以近乎同样的流程来处理两种不同的字符串。

 

■未分类的字符串操作例程

//字符串类型转换
procedure _CToPasStr(Dest:PShortString;const Source:PChar);
procedure CLenToPasstr(Dest:PShortString;const Source:PChar;MaxLen:Integer);
procedure ArrayTopasstr(Dest:PShortString;const Source:PChar;Len:Integer);
procedure PasTocStr(const Source:PShortString;const pest:PChar);

//过程Str()和Val()
procedure _Str2Ext;
procedure _StrOExt;
procedure _StrlExt;
procedure _StrLong (val,width:Longint;s:PShortstring);
procedure _StrOLong(val:Longint;s:PShortString);
procedure _ValExt;
function _ValLong(const s:String;var code:Integer):Longint;

//字符串惟一化操作
procedure Uniquestring(var str:Ansistring);overload;
procedure Uniquestring(var str:Widestring);overload;
procedure _UniquestringA(var str:AnsiString);
procedure _Uniquestringw(var str:WideString);

  在CStr(C语言风格的字符串,即Nu11结尾字符串)与短字符串的相互操作中,会用到上面的内部例程。例如:

var
    str: string[20];
    cstr: PChar =' abcd';
    Arr: array [0..10] of Char;
begin
    str:=CStr;//call procedure _CToPasstr(), and _CLenopasstr()
    Str:=Arr;//call procedure CLenToPasStr()
end;

  过程Str()与Val()是Delphi中用来在字符串与数字之间进行转换的两个例程。根据入口参数的不同,编译器会将调用的实际代码指向上面的内部例程。例如:

var
    s:string[200];
    e:Extended;
    I,L:Integer;
begin
    Str(12:5,s);//call procedure _StrLong()
    Str(15,s);//call procedure _StrOLong()
    Str(PI,s);// call procedure _StrOExt()
    Val(s,I,L);// call function _ValLong()
    Val(s,e,i);// call procedure _ValExt()
end;

  字符串惟一化,是指创建一个引用计数为1的字符串的拷贝。四个例程的Linux版本都直接调用InternalUniqueString()函数。如果是Windows版本,则两个宽字符串的惟一化例程是无意义的:因为Windows上的宽字符串没有引用计数位,这在前面已经一再讲过。
  function InternalUniqueString()的实现过程,实际上是调用NewAnsiString()分配新字符串并返回值。然后,将原字符串的引用减1,如果减1后为0,则释放字符串。根据system.pas中的      InternalUniqueString()改写的PUREPASCAL版本如下:

function InternalUniqueString(var str):Pointer;
function _Ref:Integer;
begin
    Result:=PInteger(Integer(str)-8)^
end;

function _Len:Integer;
begin
    Result:=PInteger(Integer(str)-4)^
end;

var
    V:String;
begin
    if pChar(str)"=then
        Exit;
    if _Ref=1 then
        Exit;
    //模拟_NewAnsiString()例程
    if _Len>0 then
    begin
        SetLength(V,_Len);
        move(Pointer(str)",V[1],_Len);
    end;
    string(str):=V;/使得入口时的str引用计数减1,且变量V的引用计数加1
    Result:=Pointer(Str);
end;//过程结束时,局部变量v的引用计数会减1,使得返回时的str的引用计数为1

  function_UniqueStringAC)是内部例程中几个不多的提供了用户调用接口的函数。
  用户可以调用procedure UniqueString(),来完成相同的功能。

 

■集合操作例程

procedure _SetElem;
procedure _SetRange;
procedure _SetEq;
procedure _SetLe;
procedure _SetIntersect;
procedure _SetIntersect3;{BEG only}
procedure _SetUnion;
procedure SetUnion3;{BEG only}
procedure _SetSub;
procedure _SetSub3;{BEG only}
procedure _SetExpand;

  集合的内部例程用于实现集合运算,参见下例:

var
    aset,bset,cSet:TDemoSet;
begin
    aset:=aset+bset;//call procedure _SetUnion()
    aSet:=aSet *bset;//call procedure _SetIntersect()
    if(aset =bset)and //call procedure _SetEg()
    (aSet<>cSet)and //call procedure _SetEq()
    (cSet<= aset)and // call procedure _SetLe()
    (bSet>=aset)then // call procedure Sette()
        cSet:=aSet-bSet;//call procedure _SetSub()
end:

  并非每个对应的集合操作都会调用这些内部例程。通常情况下,Delphi试图以字节、字和双字类型的位运算来实现集合运算。并且集合运算总是优先享有使用寄存器的权利。但是,如果一个集合类型超过了32个元素,则编译器使用上述的内部例程来替代位运算。这意味着超过32个元素以后,集合运算的效率将大打折扣。

 

■数组(含动态数组)操作例程

//表达式、运算和运算符
procedure DynArrayHigh;
procedure _DynArrayclear (var a:Pointer;typeInfo:Pointer);
procedure _DynArrayLength;
procedure _DynArraySetLength;
procedure _DynArrayCopy(a:Pointer;typeInfo:Pointer;var Result:Pointer);
procedure _DynArrayCopyRange(a:Pointer;typeInfo:Pointer;index,count:Integer;var Result:Pointer);
procedure _DynArrayAsg;
procedure _DynArrayAddRef;

//可外部调用的动态数组操作例程
procedure DynArrayClear(var a:Pointer;typeInfo:Pointer);
procedure DynArraySetLength(var a:Pointer;typeInfo:Pointer;dimCnt:Longint;lengthVec:PLongint);
function DynArrayDim(typeInfo:PDynArrayTypeInfo):Integer;

//可用于静态数组的例程
procedure _AddRefArray;
procedure CopyArray;

  Delphi的动态数组采用跟字符串类同的内存结构。亦即是说,动态数组也具有引用计数位和长度计数位,也类似地采用了引用计数的机制来提高系统效率。下面的例子详细演示了内部例程的调用情况:

procedure DynArrayTest;

type
TDynArr=array of char;

var
    a,al:TDynArr;
    procedure TestDynArrayRef(constt:TDynArr);
    var
    c:array of char;
    begin //call procedure _ynArrayAddRef()
    TDynArr(c):=copy(t);// call procedure _DynArrayCopy()
    end;//call procedure _DynArrayClear()
begin
    if Length(a)>0 then //call procedure _DynArrayLength()
        al:=a//call procedure _DynArrayAsg()
    else
    begin
        SetLength(a,100);//call procedure _DynArraySetLength()
        a1:=copy(a,1,3);//call procedure _DynArrayCopyRange()
    end;
    a:=nil;//call procedure _DynArrayClear()
    TestDynArrayRef(al);
end;

  动态数组的引用计数并非基于元素的,而是基于变量的,这与长字符串的引用计数不一样:在长字符串中,访问串中的一个字符,会导致引用计数发生变化;而在动态数组中,读写一个元素,并不会使引用关系发生变化一—这与大多数人的设想并不一致。下例说明这一点:

type
DynArr=array of char;

procedure Testwritecopy;
var
    al,a2:TDynArr;
    s1,s2:String;
begin
    SetLength(a1,3);
    fillchar(al[0],3,ORD('a));
    s1:='aaa';
    a2:=al;
    al[1]:='b';
    writeln(pChar(al));//'aba'
    writeln(pChar(a2));//'aba'
    82:=s1;
    81[2]:='b';
    writeln(s1);//'aba'
    writeln(s2);//'aaa'
end;

  如果要避免修改数组元素而影响到被引用的动态数组,可以使用function Copy()来创建一个惟一的拷贝。这与在长字符串中调用procedure UniqueString()的效果一致。
  编译器会使function Copy()直接调用内存例程_DynArraycopy()。

procedure _DynArrayCopy(a:Pointer;typeInfo:Pointer;var Result:Pointer);
begin
    if a<>nil then
        _DynArrayCopyRange(a,typeInfo,0,PLongint(PChar(a)-4)",Result)
    else
        _DynArrayClear(Result,typernfo);
end;

  procedure_DynArrayCopy()做一个简单的检查:如果试图把nil赋给数组,则将结果数组Result清空。否则,将从动态数组负偏移上取出数组长度,并调用
  _DynArrayCopyRange()。这使得下面两行代码的效果是一致的:

a1:=copy(a2);//call procedure DynArraycopy()
al:=copy(a2,0,Length(a2));// call procedure _DynArrayCopyRange()

  但是真正的复制代码仍然不在例程_DynArrayCopyRange()里。因为procedure _DynArrayCopyRange()的实现代码是这样的:

procedure _DynArrayCopyRange(a:Pointer;typeInfo:Pointer;index,count:Integer;var Result:Pointer);
begin
    //1.修正数组边界,使参数index和count有效
    //2.从类型信息中取每个元素的长度到elsize局部变量
    //3.在堆上分配数组空间count*elsize+Sizeof(Longint)*2个字节,并
    //置引用计数位为1,长度计数位为count
    if typeInf <>nil then
    begin
        FillChar(p^,count*elSize,0);
        CopyArray(p,a,typeInf,count)
    end
    else
        Move (a^,p^,count*elSize);
end;

  例程_DynArrayCopyRange()构造了一个新的数组。如果原数组有类型信息,则调用CopyArray()复制数组,否则,直接使用move()操作复制数组的内容,CopyArray()调用内部例程_CopyArray()。
  所有动态和静态数组,如果具有类型信息,那么它的复制操作最终都将调用到_CopyArray()。
  数组的类型信息保存有一个引用列表,CopyArray()根据这个列表创建对旧数组的相应元素的引用。关于这个操作的细节,请参见“引用一计数一写复制与类型信息”小节。
  Delphi中,Copy函数有三种声明:

function copy(s;Index,Count:Integer):string;
function Copy(S;Index,Count:Integer):array;
function Copy(S:array):array;

  针对不同的数据类型,编译器会使Copy()函数调用该类型的内部例程。如下表:

类型调用函数
短字符串

procedure _Copy{s:ShortString;
index,count:Integer):Shortstring};

长字符串

procedure _LStrCopy (const s:Ansistring;
index,count:Integer}:Ansistring};

宽字符串

function _wStrCopy(const S:WideString;
Index,Count:Integer):Widestring;

动态数组

procedure _DynArrayCopyRange(a:Pointer;typeInfo:Pointer;
index,count:Integer;var Result:Pointer);

  同样地,Delphi对如下字符串例程也使用相同的技巧:

procedure SetLength(var S;NewLength:Integer);
procedure Delete(var S:string;Index,Count:Integer);
procedure Delete(var S:string;Index,Count:Integer);
procedure Insert(Source:string;var S:string;Index:Integer);
function Pos(Substr:string;s:string):Integer;

  这些都可以在各自的字符串内部例程中看到对应的版本。SetLength()可以同时作用于字符串和动态数组。

 

■记录操作例程

procedure AddRefRecord;
procedure _CopyRecord;

  Delphi的赋值运算符可以用于记录操作,编译器使该运算符指向内部例程CopyRecord()。因此,这样的语句是可以被编译的:

type
TRec=record
I:Integer;
S:String;
endvar
R1,R2:TRec;
begin
R1:=R2;//记录赋值,call procedure _CopyRecord()

  同_CopyArray()一样,例程_CopyRecord()也是通过类型信息来实现的。实现的效果也类同。

有关类型信息在记录和数组中更多的应用,以及AddRefRecord()例程的具体实现,在
“引用一计数一写复制与类型信息”小节中会有更进一步的描述。

 

■其他

//实数类型转换
procedure _Rea12Ext;
procedure _Ext2Real;

//添加引用
procedure _AddRef;

//浮点数除运算
procedure _FSafepivide;
procedure _FSafeDivideR;

//Conversion utility routines for Ct+ convenience.Not for Delphi code.
function CompToDouble(Value:Comp):Double;cdeclprocedure DoubleTocomp(Value:Double;var Result:Comp);cdeclfunction CompToCurrency(Value:Comp):Currency;cdec1;
procedure CurrencyTocomp (Value:Currency;var Result:Comp);cdec1;

{wide character support procedures and functions for C++}
function WidecharToString(Source:PWidechar):string;
function wideCharLenrostring(Source:PWidechar;SourceLen:Integer):string;
procedure widecharrostrVar(Source:PWideChar;var Dest:string);
procedure WidecharLenrostrVar(Source:PWidechar;SourceLen:Integer;var Dest:string);
function stringrowidechar(const Source;string;Dest:Pwidechar;DestSize:Integer):Pwidechar;

  上面例程中,AddRef()用于对任意一个变量添加引用。看起来,它试图将变量看作一个只有一个元素的数组处理,而并不关心该元素是什么类型或者有没有类型信息。因为这个例程总是直接调用AddRefArray()。然而事实上,这段仅仅两行代码的过程:

procedure AddRef{p:Pointer;typeInfo:Pointer);}
asm
    MOV ECX,1
    JMP AddRefArray
end;

  充分利用了数组、元素和类型信息的特性,利用AddRefArray()代码轻松地实现了对所有7种支持引用计数机制的数据类型的处理,并完成对其他数据类型的甄别。
  有关浮点数运算的两个例程_FSafeDivide()和_FSafeDivideR(),可以参阅参考书目的《Delphi技术手册》中,有关SSafeDivide编译指令和TestFDTV变量的描述。
  Real2Ext()和_Ext2Real()两个例程,用于编译器在Rea1和Extened两种浮点数类型间相互赋值时隐含地进行类型转换。

 

 

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值