所以我的程序中有一个测试用例
procedure MemCheck<T>(x, y : T);
var
a, b : T;
vals : array of T;
c : T;
begin
a := x;
b := y;
c := a;
SetLength(vals, 4);
vals[0] := a;
vals[1] := a;
vals[2] := a;
vals[3] := a;
c := T(vals[2]); //c := a ; workes fine
Check
(
CompareMem(@c, @y, SizeOf(T)),
'Memory compare check'
);
end;
Run Code Online (Sandbox Code Playgroud)
这个测试用例失败了,我不知道为什么
MemCheck<String>('a', 'a');
我用的时候工作正常
c := a ; 代替 c := T(vals[2]);
Rem*_*eau 16
您正在将字符串文字传递给MemCheck().由于它们是相同的值,因此编译器将它们合并在可执行文件中.所以你实际上是将一个常量字符串文字传递给x和y参数.字符串文字是只读的,引用计数为-1.将字符串文字分配给String变量会按原样复制指向内存块的指针,而不会分配新内存或增加内存块的引用计数.
截至点之前您填充vals数组,你的a,b和c变量只是字符串常量指针的副本.它们都指向内存中的相同数据块.该System.StringRefCount()函数确认它们都返回引用计数-1,证明它们都指向字符串文字.
在语句中vals[2] := a;,a指向字符串文字,因此RTL分配新String实例并将字符串文字的内容复制到新的内存块中. a仍然具有-1的引用计数,但vals[2]现在引用计数为1,证明已执行分配.c := T(vals[2]);然后该语句将分配String给String变量,因此分配的数据的引用计数会递增. StringRefCount()确认c现在的引用计数为2.
CompareMem()然后失败,因为您正在比较指向两个不同内存块的两个变量的内部数据指针的String值,因此它们的指针值不同并且比较失败.
当您更改该语句c := T(vals[2]);来c := a;,a仍然是指向在字符串中存储的文字,使指针被原样复制的进入c而没有进行任何分配.这样CompareMem()成功,因为现在您正在比较String指向同一内存块的两个变量的内部数据指针的值,因此比较成功.
所以真正的问题是 - 为什么语句vals[2] := a;执行新的分配而不是仅仅复制数据指针就像任何其他String := String;赋值一样?
分配a := x;,b := y;并且c := a;正在调用System.@UStrLAsg()函数,该函数允许将String字符串文字指向原样String而不执行分配.
该语句vals[2] := a;正在调用System.@UStrAsg()函数,当源指向字符串文字时,它总是生成一个新的已分配副本String.
为什么编译器选择使用@UStrAsg()而不是@UStrLAsg()在vals[2] := a;语句中?这就是当赋值的左侧是String动态数组内部而不是独立变量时编译器的工作方式.如果vals是静态数组而不是(vals: array[0..3] of String;),编译器将选择使用@UStrLAsg()而不是@UStrAsg(),CompareMem()然后成功.
@UStrLAsg()通常在String为本地 分配a时使用String,因此通常可以安全地使用字符串文字.
@UStrAsg()通常在将全局分配String给全局时使用 String,其中需要复制文字以避免在文本可能存在于可在分配后卸载的DLL /包中时出现潜在的内存错误.
因此,在分配给动态数组时,编译器必须小心谨慎,因为它不知道或不能假设String数组中实例的生命周期.