So I have a test case in my program
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;
This test case fails and I'm not sure why
MemCheck<String>('a', 'a');
it works fine when I use
c := a ;
instead of c := T(vals[2]);
You are passing string literals to MemCheck()
. Since they are the same value, the compiler merges them together in the executable. So you are actually passing a single constant string literal to both the x
and y
parameters. String literals are read-only and have a reference count of -1. Assigning a string literal to a String
variable copies the pointer to the memory block as-is without allocating new memory or incrementing the memory block's reference count.
Up to the point before you populate the vals
array, your a
, b
, and c
variables are just copies of the string literal pointer. They are all pointing at the same data block in memory. The System.StringRefCount()
function confirms that they all return a reference count of -1, proving they are all pointing at a string literal.
In the statement vals[2] := a;
, a
is pointing at a string literal, so the RTL allocates a new String
instance and copies the content of the string literal into the new memory block. a
still has a reference count of -1, but vals[2]
now has a reference count of 1, proving that an allocation was performed. The statement c := T(vals[2]);
is then assigning an allocated String
to a String
variable, so the allocated data's reference count gets incremented. StringRefCount()
confirms that c
now has a reference count of 2.
CompareMem()
then consequently fails, because you are comparing the value of the internal data pointer of two String
variables that are pointing at two different memory blocks, so their pointer values are different and the comparison fails.
When you change the statement c := T(vals[2]);
to c := a;
, a
is still pointing at the string literal in memory, so that pointer gets copied as-is into c
without performing any allocation. Thus CompareMem()
succeeds because now you are comparing the value of the internal data pointer of two String
variables that are pointing at the same memory block, so the comparison succeeds.
So the real question is - why is the statement vals[2] := a;
performing a new allocation instead of just copying the data pointer as-is like any other String := String;
assignment?
The assignments a := x;
, b := y;
and c := a;
are calling the System.@UStrLAsg()
function, which permits a String
pointing at a string literal to be assigned as-is to another String
without performing an allocation.
The statement vals[2] := a;
is calling the System.@UStrAsg()
function instead, which always makes a new allocated copy when the source String
is pointing at a string literal.
Why is the compiler choosing to use @UStrAsg()
instead of @UStrLAsg()
in the vals[2] := a;
statement? That is just the way the compiler works when the left-hand side of the assignment is a String
inside a dynamic array, rather than a standalone variable. If vals
were a static array instead (vals: array[0..3] of String;
), the compiler would choose to use @UStrLAsg()
instead of @UStrAsg()
, and CompareMem()
would then succeed.
@UStrLAsg()
is typically used when assigning a String
to a local String
, thus it is usually safe to allow a string literal to be used as-is.
@UStrAsg()
is typically used when assigning a String
to a global String
, where a literal needs to be copied to avoid potential memory errors when the literal might exist in a DLL/Package that could be unloaded after the assignment.
So the compiler must be airing on the side of caution when assigning to a dynamic array as it does not know, or cannot assume, the lifetime of the String
instances in the array.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With