6.3.8 查看内存中的字符串
通过查看字符串的元数据,可以更好地了解字符串内存管理的工作原理,尤其是涉及引用计数时。为此,我在 StringMetaTest 示例中添加了一些代码。
该程序有两个全局字符串: MyStr1 和 MyStr2。程序将一个动态字符串赋值给两个变量中的第一个(原因在前面的说明中已经解释),然后将第二个变量赋值给第一个变量:
MyStr1 := string.Create(['H', 'e', 'l', 'l', 'o']);
MyStr2 := MyStr1;
除了处理字符串,程序还使用下面的 StringStatus 函数显示字符串的内部状态:
StringStatus(const Str: string): string;
begin
Result := 'Addr: ' + IntToStr(Integer(Str)) +
', Len: ' + IntToStr(Length(Str)) +
', Ref: ' + IntToStr(PInteger(Integer(Str) - 8)^) +
', Val: ' + Str;
end;
在 StringStatus 函数中,将字符串参数作为常量参数传递非常重要。复制传递该参数(如不使用 const)会产生副作用,即在函数执行过程中会对字符串产生一个额外的引用。相比之下,通过引用(var)或常量(const)传递参数并不意味着对字符串的进一步引用。在本例中,我使用了常量参数,因为函数不应该修改字符串。
为了获得字符串的内存地址(这对确定字符串的实际身份以及查看两个不同字符串是否引用了相同的内存区域非常有用),我简单地将字符串类型硬编码为整数类型。字符串是引用,实际上是指针: 它们的值是字符串的实际内存位置,而不是字符串本身。
用于测试字符串发生变化的代码如下:
Show('MyStr1 - ' + StringStatus(MyStr1));
Show('MyStr2 - ' + StringStatus(MyStr2));
MyStr1[1] := 'a';
Show('Change 2nd char');
Show('MyStr1 - ' + StringStatus(MyStr1));
Show('MyStr2 - ' + StringStatus(MyStr2));
最初,您应该得到两个内容相同、内存位置相同、引用计数为 2 的字符串。
MyStr1 - Addr: 51837036, Len: 5, Ref: 2, Val: Hello
MyStr2 - Addr: 51837036, Len: 5, Ref: 2, Val: Hello
随着应用更改两个字符串中的一个的值(无论是哪一个都无关紧要),更新后的字符串的内存位置将更改。这是写时复制技术的效果。这是输出的第二部分:
Change 2nd char
MyStr1 - Addr: 51848300, Len: 5, Ref: 1, Val: Hallo
MyStr2 - Addr: 51837036, Len: 5, Ref: 1, Val: Hello
您可以自由扩展此示例,并使用 StringStatus 函数来探索长字符串在其他多种情况下的行为,包括多引用、作为参数传递、分配给局部变量等。