TObject vs String 来回转换
TObject vs String casting there and back
请有人解释一下它是如何工作的? TObject 和 String 不兼容,应该不行,我想。
var
xObj: TObject;
begin
// xObj := TObject('yyy'); // invalid typecast
xObj := TObject('yyy ' + ClassName); // no error
ShowMessage(String(xObj)); // no error too
end;
另外我很好奇 xObj 的内存何时会被释放?
这是一个很难回答的问题,因为
如果您知道 Delphi 内部数据格式和内存管理是如何工作的,那么这个问题就很简单了。
如果您不知道 Delphi 内部数据格式和内存管理是如何工作的,那么单个 SO 答案无法告诉您。
但是,由于非常天真,我会给你一个非常简短的介绍。此介绍包含一些指向文档的链接。我希望你会阅读那些文档页面。我还使用了很多技术词汇。我希望您努力真正理解这些概念中的每一个。
在Delphi中,object variable只不过是指向对象的指针。因此,它是一个 native-sized 整数。例如,在 Win64 上,它是一个 64 位整数——计算机内存中对象的地址。
如果A, B: TBitmap
而你A := B
,那么只会复制这样一个整数,这样A
和B
之后就指向同一个位图对象堆(或者都是 nil
指针)。换句话说,NativeInt(B) = NativeInt(A)
.
对象(如果我们暂时忘记接口对象)不是由编译器管理的。您必须手动创建它们,并且必须手动释放它们:
var Frog := TFrog.Create;
try
// Use Frog
finally
Frog.Free;
end;
显然,您必须非常小心,不要造成内存泄漏或使用悬挂指针。始终使用 try..finally
成语(及其朋友),就像你的生活取决于它一样。
类似地,string
类型的变量是指向 string heap object 的指针。因此,字符串变量只不过是一个 native-sized 整数。例如,在 Win64 上,它是一个 64 位整数——计算机内存中字符串堆对象的地址。
与对象一样,字符串也是引用类型。但是,与对象不同的是,它们由编译器进行引用计数和管理,因此您永远不需要自己创建或释放它们。
尽管字符串是引用类型,但它们“看起来”像值类型,因为它们使用 copy-on-write (COW) 语义。
因此,在 string-variable 作业中,如
b := a;
赋值将 (1) 使 b
指向与 a
相同的字符串堆对象(以便之后 NativeInt(b) = NativeInt(a)
),以及 (2) 更改 a
字符串堆对象的引用计数增加 1,因为现在 b
也指向它。此外,b
曾经指向的堆对象(如果有的话)的引用计数将减一,因为 b
不再指向它。如果它达到零,则释放堆对象。
现在,如果 A
是一个对象而 s
是一个字符串,那么 A
和 s
都是 native-sized 整数,所以当然你可以告诉编译器执行赋值。
然而,这(几乎总是)是一个非常糟糕的主意。如果使用 string(X)
,其中 X
是一个非 nil
指针,不指向字符串堆对象,编译器仍会将这部分内存视为字符串。然后会发生可怕的事情,比如内存损坏(如果你不走运)和 AV(如果你很幸运)。
类似地,如果 s
是一个字符串并且您开始使用 TBitmap(s)
,编译器会将 s
指向的内存(字符串堆对象)视为TBitmap
实例。再一次,可怕的事情会发生。
事实上,如果您混合使用托管类型和 non-managed 类型,即使您只是(看似)从错误类型的指针“读取”,也可能会发生可怕的事情。例如,在 end
,编译器可能会添加代码以减少不指向字符串堆对象的“字符串”变量的引用计数。
或者,与OP的实际情况相关:可能会在你背后释放一个字符串。
考虑这个程序:
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;
当点击Button1
时,会设置一个局部字符串变量s
指向一个引用计数为1的随机数字符串堆对象,然后我们将地址保存到这个字符串堆对象中在 non-managed 指针变量中。
当我们命中Button1Click
的end
时,这个堆对象的引用计数降为0,所以它被释放了。因此,p
现在是一个悬挂指针。
因此,如果您按 Button2
,您将访问一个悬空指针:p
不指向字符串堆对象,而是指向您无法控制的某些内存。
您可能会得到字符串的旧内容。或者完全不同的东西。或者AV。
提示:在 p := Pointer(s);
上设置断点并转到“内存”面板 (Ctrl+Alt+1)。在此处按 Ctrl+G 并键入 s[1]
以转到字符串。在负偏移处,您将找到堆对象的引用计数。当您跨过 end
时,您会看到它下降到零。
奖励 material: Dynamic arrays 是引用类型,由编译器进行引用计数和管理,但不 使用 COW 语义。 [实际上,在我的 YouTube 频道上,您可以找到一段关于他们内部机制的非常糟糕的视频。]
请有人解释一下它是如何工作的? TObject 和 String 不兼容,应该不行,我想。
var
xObj: TObject;
begin
// xObj := TObject('yyy'); // invalid typecast
xObj := TObject('yyy ' + ClassName); // no error
ShowMessage(String(xObj)); // no error too
end;
另外我很好奇 xObj 的内存何时会被释放?
这是一个很难回答的问题,因为
如果您知道 Delphi 内部数据格式和内存管理是如何工作的,那么这个问题就很简单了。
如果您不知道 Delphi 内部数据格式和内存管理是如何工作的,那么单个 SO 答案无法告诉您。
但是,由于非常天真,我会给你一个非常简短的介绍。此介绍包含一些指向文档的链接。我希望你会阅读那些文档页面。我还使用了很多技术词汇。我希望您努力真正理解这些概念中的每一个。
在Delphi中,object variable只不过是指向对象的指针。因此,它是一个 native-sized 整数。例如,在 Win64 上,它是一个 64 位整数——计算机内存中对象的地址。
如果A, B: TBitmap
而你A := B
,那么只会复制这样一个整数,这样A
和B
之后就指向同一个位图对象堆(或者都是 nil
指针)。换句话说,NativeInt(B) = NativeInt(A)
.
对象(如果我们暂时忘记接口对象)不是由编译器管理的。您必须手动创建它们,并且必须手动释放它们:
var Frog := TFrog.Create;
try
// Use Frog
finally
Frog.Free;
end;
显然,您必须非常小心,不要造成内存泄漏或使用悬挂指针。始终使用 try..finally
成语(及其朋友),就像你的生活取决于它一样。
类似地,string
类型的变量是指向 string heap object 的指针。因此,字符串变量只不过是一个 native-sized 整数。例如,在 Win64 上,它是一个 64 位整数——计算机内存中字符串堆对象的地址。
与对象一样,字符串也是引用类型。但是,与对象不同的是,它们由编译器进行引用计数和管理,因此您永远不需要自己创建或释放它们。
尽管字符串是引用类型,但它们“看起来”像值类型,因为它们使用 copy-on-write (COW) 语义。
因此,在 string-variable 作业中,如
b := a;
赋值将 (1) 使 b
指向与 a
相同的字符串堆对象(以便之后 NativeInt(b) = NativeInt(a)
),以及 (2) 更改 a
字符串堆对象的引用计数增加 1,因为现在 b
也指向它。此外,b
曾经指向的堆对象(如果有的话)的引用计数将减一,因为 b
不再指向它。如果它达到零,则释放堆对象。
现在,如果 A
是一个对象而 s
是一个字符串,那么 A
和 s
都是 native-sized 整数,所以当然你可以告诉编译器执行赋值。
然而,这(几乎总是)是一个非常糟糕的主意。如果使用 string(X)
,其中 X
是一个非 nil
指针,不指向字符串堆对象,编译器仍会将这部分内存视为字符串。然后会发生可怕的事情,比如内存损坏(如果你不走运)和 AV(如果你很幸运)。
类似地,如果 s
是一个字符串并且您开始使用 TBitmap(s)
,编译器会将 s
指向的内存(字符串堆对象)视为TBitmap
实例。再一次,可怕的事情会发生。
事实上,如果您混合使用托管类型和 non-managed 类型,即使您只是(看似)从错误类型的指针“读取”,也可能会发生可怕的事情。例如,在 end
,编译器可能会添加代码以减少不指向字符串堆对象的“字符串”变量的引用计数。
或者,与OP的实际情况相关:可能会在你背后释放一个字符串。
考虑这个程序:
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;
当点击Button1
时,会设置一个局部字符串变量s
指向一个引用计数为1的随机数字符串堆对象,然后我们将地址保存到这个字符串堆对象中在 non-managed 指针变量中。
当我们命中Button1Click
的end
时,这个堆对象的引用计数降为0,所以它被释放了。因此,p
现在是一个悬挂指针。
因此,如果您按 Button2
,您将访问一个悬空指针:p
不指向字符串堆对象,而是指向您无法控制的某些内存。
您可能会得到字符串的旧内容。或者完全不同的东西。或者AV。
提示:在 p := Pointer(s);
上设置断点并转到“内存”面板 (Ctrl+Alt+1)。在此处按 Ctrl+G 并键入 s[1]
以转到字符串。在负偏移处,您将找到堆对象的引用计数。当您跨过 end
时,您会看到它下降到零。
奖励 material: Dynamic arrays 是引用类型,由编译器进行引用计数和管理,但不 使用 COW 语义。 [实际上,在我的 YouTube 频道上,您可以找到一段关于他们内部机制的非常糟糕的视频。]