Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CompareMem does not seem to work when using a value that came from an array

Tags:

delphi

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]);

like image 860
sav Avatar asked Dec 05 '22 02:12

sav


1 Answers

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.

like image 78
Remy Lebeau Avatar answered Jun 03 '23 08:06

Remy Lebeau