Reputation: 726
Please someone explain me how can it works? TObject and String are incompatible, it shouldn't work, I thought.
var
xObj: TObject;
begin
// xObj := TObject('yyy'); // invalid typecast
xObj := TObject('yyy ' + ClassName); // no error
ShowMessage(String(xObj)); // no error too
end;
Also I'm curious when the xObj's memory gonna be freed?
Upvotes: 0
Views: 1433
Reputation: 108963
This is a hard question to answer, because
If you know how Delphi internal data formats and memory management work, the question is trivial.
If you don't know how Delphi internal data formats and memory management work, a single SO answer cannot teach you that.
Being highly naïve, however, I will give you a very brief introduction. This introduction contains a few links to the documentation. I hope you will read those doc pages. I also use quite a few technical words. I hope you will make an effort to truly understand each of these concepts.
In Delphi, an object variable is nothing but a pointer to the object. Hence, it is a native-sized integer. For instance, on Win64, it is a 64-bit integer -- the address to the object in your computer's memory.
If A, B: TBitmap
and you do A := B
, then only such an integer is copied, so that A
and B
afterwards point to the same bitmap object on the heap (or are both nil
pointers). In other words, NativeInt(B) = NativeInt(A)
.
Objects are (if we forget about interfaced objects for the moment) not managed by the compiler. You must create them manually, and you must free them manually:
var Frog := TFrog.Create;
try
// Use Frog
finally
Frog.Free;
end;
Obviously, you must be very careful not to create memory leaks or use dangling pointers. Always use the try..finally
idiom (and its friends) like your life depends on it.
Similarly, a variable of type string
is a pointer to a string heap object. Hence, a string variable is too nothing but a native-sized integer. For instance, on Win64, it is a 64-bit integer -- the address to the string heap object in your computer's memory.
Like objects, strings are reference types. However, unlike objects, they are reference counted and managed by the compiler, so you never need to create or free them yourself.
Even though strings are reference types, they "appear" like value types because they use copy-on-write (COW) semantics.
Hence, in a string-variable assignment like
b := a;
the assignment will (1) make b
point to the same string heap object as a
(so that afterwards NativeInt(b) = NativeInt(a)
), and (2) change the a
string heap object by increasing its reference count by 1, since now b
is also pointing to it. In addition, the heap object that b
used to point to, if any, will have its reference count reduced by one, since b
no longer points to it. If it reaches zero, the heap object is freed.
Now, if A
is an object and s
a string, then both A
and s
are nothing but native-sized integers, so of course you can tell the compiler to perform the assignment.
However, this is (almost always) a horribly bad idea. If you use string(X)
, where X
is a non-nil
pointer that doesn't point to a string heap object, the compiler will still treat this part of memory as a string. Then horrible things will happen, like memory corruption (if you are unlucky) and AVs (if you are very lucky).
Similarly, if s
is a string and you start to work with TBitmap(s)
, the compiler will treat the memory that s
points to (a string heap object) like a TBitmap
instance. Again, horrible things will happen.
In fact, horrible things may happen even if you only (seemingly) "read" from an incorrectly typed pointer, if you mix managed and non-managed types. For instance, at end
, perhaps the compiler will add code to reduce the reference count of a "string" variable that doesn't point to a string heap object.
Or, relevant to the OP's actual case: a string may be freed behind your back.
Consider this program:
var
p: Pointer;
procedure TForm1.Button1Click(Sender: TObject);
var
s: string;
begin
s := Random(1024).ToString;
p := Pointer(s);
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
ShowMessage(string(p));
end;
When you click Button1
, a local string variable s
will be set to point to a random number string heap object with reference count 1. Then we save the address to this string heap object in a non-managed pointer variable.
When we hit the end
of Button1Click
, the reference count of this heap object drops to 0, so it is freed. Hence, p
is now a dangling pointer.
So if you press Button2
, you will be accessing a dangling pointer: p
doesn't point to string heap object, but to some memory you don't have any control over.
You may get the old contents of the string. Or something completely different. Or an AV.
Hint: Set a breakpoint on p := Pointer(s);
and go to the Memory panel (Ctrl+Alt+1). Press Ctrl+G there and type s[1]
to take you to the string. At negative offset, you will find the reference count of the heap object. As you step over end
, you will see that it drops to zero.
Bonus material: Dynamic arrays are reference types that are reference counted and managed by the compiler, but that do not use COW semantics. [Actually, on my YouTube channel you can find a not horribly bad video about their internal machinery.]
Upvotes: 3