crement(P.refcnt); Result := Pointer(str);
11. end; 12. {$ELSE}
其中用到了 PStrRec,其结构如下 type PStrRec = ^StrRec; StrRec = packed record refCnt: Longint; length: Longint; end;
简单解释下: 先解释下 StrRec 结构,在 delphi 中,String 类型的数据,实际上就是一个 StrRec 结 构的数据再加上字符串本身的内容,其中 refCnt 表示字符串被引用的次数,length 是字符 串的长度。refCnt 的作用是:当其值为 0 的时候,表示这个字符串没有任何一个地方使用 了,这时,delphi 会在适当的时候释放这个字符串所占用的内存。 再说下 LStrAddRef 函数完成的功能,首先得到指向传入字符串所对应的 StrRec 结构 体的指针,然后判断这个结构体是否为空,如果不为空,再判断这个结构体中的 refCnt 是 否大于等于 0,如果是,就将其加一,然后返回指向这个字符串本身的指针。
好了 mov eax,[ebp-$04] call @LStrAddRef 所完成的功能就是让字符串 ebp-$04 中保存的字符串的引用计数加一,而 ebp-$04 中 保存的,恰好是函数第一个参数传入的内容,也就是说,这两句完成的是让传入的字符串参 数的引用计数加一
xor eax,eax push ebp push $0045214b push d
word ptr fs:[eax] mov fs:[eax],esp 这几句是与 delphi 实现的异常处理有关,在这里不做说明了,和本文讨论的内容关系不 大。
问题描述到这里,我们可以看到,使用 const 修饰和不使用 const 修饰的差别:不使 用 const 会让函数参数多了一个增加引用计数的过程。好了,记住这个结论。实际上,问 题就出在这里
首先分析下程序的流程:
[delphi] view plaincopy
1. procedure TfrmMain.btnTestClick(Sender: TObject); 2. begin 3. 4. SS := 'Hello'; test1(SS);
5. end;
为全局的字符串变量 SS 赋值
为"Hello",然后以 SS 为参数调用 Test1 函数。
[delphi] view plaincopy
1. procedure test1(const s: String); 2. begin 3. try
4. 5. 6. 7.
SS := '你好'; ShowMessage(s); except end;
8. end;
在 Test1 函数中,修改 SS 的内容,并显示参数 S 的内容
整个流程并不复杂,下面,在汇编下,看看是怎么完成的: 在 SS := "Hello" 处下断点,跟踪。
图1 为 SS 赋值的语句为上面红框框出来的部分,调用了@LStrAsg 函数完成了赋值功能, 下面是@LStrAsg 函数
[delphi] view plaincopy
1. //两个参数,第一个参数为目标字符串,也就是要赋值的字符串
2. //第二个参数是原始字符串,也就是要赋值的原始值 3. procedure _LStrAsg(var dest; const source); 4. {$IFDEF PUREPASCAL} 5. var 6. 7. 8. S, D: Pointer; P: PStrRec; Temp: Longint;
9. begin 10. 11. 12. 13. S := Pointer(source); if S <> nil then begin P := PStrRec(Integer(S) - sizeof(StrRec)); 的 StrRec 结构指针 14. 15. 16. 17. 18. 19. 20. 21. 中 22. P := PStrRec(Integer(S) - sizeof(StrRec)); 的 StrRec 结构指针 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. end; //如果原来的目标不为空,则将其引用计数减一 //减一之后,如果引用计数为零,则释放这个字符串占的内存 if D <> nil then begin P := PStrRec(Integer(D) - sizeof(StrRec)); if P.refCnt > 0 then if InterlockedDecrement(P.refCnt) = 0 then FreeMem(P); //设置目标字符串 D := Pointer(dest); Pointer(dest) := S; //保存指向目标字符串原来的指针到 D //设置目标字符串为 S 指向的字符串 end; InterlockedIncrement(P.refCnt); //增加 P 所指向的字符串的引用计数 end; //将 P 设置为指向新字符串 //当原始字符串为常量时,其引用计数为-1 //这种时候,会引起字符串的复制操作 if P.refCnt < 0 then begin Temp := P.length; //获得原始字符串的长度 //开辟一块大小为原始字符串长度的内存 //将原始字符串的内容复制到新开辟的内存 // make copy of string literal //得到指向原始字符串 //得到指向原始字符串的地址