使用来自数组的值时,CompareMem似乎不起作用

sav*_*sav 4 delphi

所以我的程序中有一个测试用例

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().由于它们是相同的值,因此编译器将它们合并在可执行文件中.所以你实际上是将一个常量字符串文字传递给xy参数.字符串文字是只读的,引用计数为-1.将字符串文字分配给String变量会按原样复制指向内存块的指针,而不会分配新内存或增加内存块的引用计数.

截至点之前您填充vals数组,你的a,bc变量只是字符串常量指针的副本.它们都指向内存中的相同数据块.该System.StringRefCount()函数确认它们都返回引用计数-1,证明它们都指向字符串文字.

在语句中vals[2] := a;,a指向字符串文字,因此RTL分配新String实例并将字符串文字的内容复制到新的内存块中. a仍然具有-1的引用计数,但vals[2]现在引用计数为1,证明已执行分配.c := T(vals[2]);然后该语句将分配StringString变量,因此分配的数据的引用计数会递增. 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数组中实例的生命周期.