直接上例子了,基础知识自己去了解,首先定义一个类:
TPerson = class
public
name: string;
age: Integer;
constructor Create(name: string; age: Integer);
end;
constructor TPerson.Create(name: string; age: Integer);
begin
Self.name := name;
Self.age := age;
end;
例子1,首先了解Free,仅仅是销毁堆中数据,栈中变量的值依然是堆中的地址:
procedure TForm3.btn1Click(Sender: TObject);
var
ps: TPerson;
begin
ps := TPerson.Create('小李', 10);
ps.Free; //这里调用Free,堆中的数据已经被释放,但是ps这个指针的值还是堆中对象的地址
if Assigned(ps) then //Assigned方法,当ps指针的值为nil的时候,才返回False,由于ps的值不为nil,这里显然会返回True
begin
mmo1.Lines.Add('yes');
mmo1.Lines.Add(ps.name); //这里产生了Av错误,原因是ps堆中的对象已经被释放
end else begin
mmo1.Lines.Add('no');
end;
end;
例子2,知道即使你不创建实例,Assigned依然是true;
procedure TForm3.btn3Click(Sender: TObject);
var
p: TPerson;
begin
//p不初始化,会自由随机分配一个地址和值,值也是原来的自由值,不一定是nil,通常都不是nil
if Assigned(p) then
begin
//只要p指针的值不是nil,这里就会返回true,所以是yes
mmo1.Lines.Add('yes');
end else begin
mmo1.Lines.Add('no');
end;
end;
例子3:对例子1进行改造,使用FreeAndNil,将堆中数据释放,同时将栈中指针的值变为nil;
procedure TForm3.btn3Click(Sender: TObject);
var
ps: TPerson;
begin
ps := TPerson.Create('小李', 10);
FreeAndNil(ps); //指针的值也变成nil了
if Assigned(ps) then
begin
mmo1.Lines.Add('yes');
end else begin
mmo1.Lines.Add('no'); //此时就自然到这里了
end;
end;
例子4,最后一个例子,得出官方的Assigned 不是我们需要的:
procedure TForm3.btn4Click(Sender: TObject);
var
ps, ps1: TPerson;
begin
ps := TPerson.Create('小李', 10);
ps1 := ps;
FreeAndNil(ps);
if Assigned(ps) then
begin
mmo1.Lines.Add('yes');
end else begin
mmo1.Lines.Add('no'); //ps指针的值是nil了,所以no
end;
if Assigned(ps1) then
begin
mmo1.Lines.Add('yes'); //ps1指针的值不是nil
//mmo1.Lines.Add(ps1.name); //这里就会报错,因为ps堆中的数据已经释放
end else begin
mmo1.Lines.Add('no');
end;
//显然上面不是我们要的结果,使用我们自己的函数
if AssignedEx(ps1, TPerson) then
begin
mmo1.Lines.Add('yes');
end else begin
mmo1.Lines.Add('no'); //由于ps1指向的堆中数据已经不存在,这里会no
end;
end;
好了综上得出官方的Assigned 有点简陋,不是我们需要的,引出我们的修改版:
/// <summary>
/// 判断实例释放释放
/// </summary>
/// <param name="AVar">实例变量</param>
/// <param name="className">实例类型</param>
function AssignedEx(AVar: TObject; AClass: TClass): Boolean;
begin
//首先判断变量指针的值是否为nil
if not Assigned(AVar) then
begin
Exit(False);
end;
//判断堆中的数据是否是当前类
try
//若AVar指向的堆中数据已释放,则这里调用.ClassName会AV异常
if AVar.ClassName = AClass.ClassName then
begin
Exit(True);
end else begin
Exit(False);
end;
except
//有异常说明堆中已经被释放掉了
Exit(False);
end;
end;
==========================================================
最后贴上全部测试代码:
全部测试代码
unit Unit3;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TForm3 = class(TForm)
mmo1: TMemo;
btn1: TButton;
btn2: TButton;
btn3: TButton;
btn4: TButton;
btn5: TButton;
procedure btn1Click(Sender: TObject);
procedure btn2Click(Sender: TObject);
procedure btn3Click(Sender: TObject);
procedure btn4Click(Sender: TObject);
procedure btn5Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
TPerson = class
public
name: string;
age: Integer;
constructor Create(name: string; age: Integer);
end;
var
Form3: TForm3;
implementation
{$R *.dfm}
constructor TPerson.Create(name: string; age: Integer);
begin
Self.name := name;
Self.age := age;
end;
/// <summary>
/// 判断实例释放释放
/// </summary>
/// <param name="AVar">实例变量</param>
/// <param name="className">实例类型</param>
function AssignedEx(AVar: TObject; AClass: TClass): Boolean;
begin
//首先判断变量指针的值是否为nil
if not Assigned(AVar) then
begin
Exit(False);
end;
//判断堆中的数据是否是当前类
try
//若AVar指向的堆中数据已释放,则这里调用.ClassName会AV异常
if AVar.ClassName = AClass.ClassName then
begin
Exit(True);
end else begin
Exit(False);
end;
except
//有异常说明堆中已经被释放掉了
Exit(False);
end;
end;
procedure TForm3.btn1Click(Sender: TObject);
var
ps: TPerson;
begin
ps := TPerson.Create('小李', 10);
ps.Free; //这里调用Free,堆中的数据已经被释放,但是ps这个指针的值还是堆中对象的地址
if Assigned(ps) then //Assigned方法,当ps指针的值为nil的时候,才返回False,由于ps的值不为nil,这里显然会返回True
begin
mmo1.Lines.Add('yes');
mmo1.Lines.Add(ps.name); //这里产生了Av错误,原因是ps堆中的对象已经被释放
end else begin
mmo1.Lines.Add('no');
end;
end;
procedure TForm3.btn2Click(Sender: TObject);
var
p: TPerson;
begin
//p不初始化,会自由随机分配一个指针,指针的值也是原来的自由值,不一定是nil
if Assigned(p) then
begin
//只要p指针的值不是nil,这里就会返回true,所以是yes
mmo1.Lines.Add('yes');
end else begin
mmo1.Lines.Add('no');
end;
end;
procedure TForm3.btn3Click(Sender: TObject);
var
ps: TPerson;
begin
ps := TPerson.Create('小李', 10);
FreeAndNil(ps); //指针的值也变成nil了
if Assigned(ps) then
begin
mmo1.Lines.Add('yes');
end else begin
mmo1.Lines.Add('no'); //此时就自然到这里了
end;
end;
procedure TForm3.btn4Click(Sender: TObject);
var
ps, ps1: TPerson;
begin
ps := TPerson.Create('小李', 10);
ps1 := ps;
FreeAndNil(ps);
if Assigned(ps) then
begin
mmo1.Lines.Add('yes');
end else begin
mmo1.Lines.Add('no'); //ps指针的值是nil了,所以no
end;
if Assigned(ps1) then
begin
mmo1.Lines.Add('yes'); //ps1指针的值不是nil
//mmo1.Lines.Add(ps1.name); //这里就会报错,因为ps堆中的数据已经释放
end else begin
mmo1.Lines.Add('no');
end;
//显然上面不是我们要的结果,使用我们自己的函数
if AssignedEx(ps1, TPerson) then
begin
mmo1.Lines.Add('yes');
end else begin
mmo1.Lines.Add('no'); //由于ps1指向的堆中数据已经不存在,这里会no
end;
end;
procedure TForm3.btn5Click(Sender: TObject);
var
ps, ps1: TPerson;
begin
ps := TPerson.Create('小李', 10);
ps1 := ps;
FreeAndNil(ps);
if Assigned(ps) then
begin
mmo1.Lines.Add('yes');
end else begin
mmo1.Lines.Add('no'); //ps指针的值是nil了,所以no
end;
if Assigned(ps1) then
begin
mmo1.Lines.Add('yes'); //ps1指针的值不是nil
//mmo1.Lines.Add(ps1.name); //这里就会报错,因为ps堆中的数据已经释放
end else begin
mmo1.Lines.Add('no');
end;
//显然上面不是我们要的结果,使用我们自己的函数
if AssignedEx(ps1, TPerson) then
begin
mmo1.Lines.Add('yes');
end else begin
mmo1.Lines.Add('no'); //由于ps1指向的堆中数据已经不存在,这里会no
end;
end;
end.
======================== 2024.2.20 更新,废弃 assignedEx 函数,以后不要使用了 ====================
首先看下 官方 对 assigned的解释,官方已经说明了 本身 assigned就无法应对 悬浮指针:
官方说明了2点:
1. assigned的主要用途是:
主要是 对象事件 和 过程时,不能与 nil 进行比较,应该使用 assigned,其它情况 要使用 nil进行对比是 更好的选择;
2. 对悬浮指针无效,悬浮指针是 开发人员设计的问题,开发设计时,要避开 悬浮指针的问题,是更好的习惯,所以废弃 assignedEx,之前的博客不删除;
使用try...except
块进行访问测试:
一种更激进的方法是尝试访问指针指向的内存,并捕获可能发生的访问违规异常。这种方法不是推荐的做法,因为它可能会导致程序崩溃或其他不可预测的行为。
var
MyPointer: Pointer;
TestValue: Integer;
begin
// 假设MyPointer在某处被赋值
if MyPointer <> nil then
begin
try
// 尝试读取指针指向的内存
TestValue := PInteger(MyPointer)^;
// 如果没有异常,说明读取操作成功了,但这并不保证指针完全有效
except
on E: Exception do
// 捕获异常,说明指针无效或访问违规
end;
end;
end;
请注意,上面的try...except
块方法是非常危险的,因为它实际上尝试访问可能是无效的内存地址。在大多数情况下,你应该避免这样做,除非你完全了解你在做什么,以及可能的后果。
通常,最好的做法是始终确保你的代码在逻辑上不会导致无效的指针引用。这包括正确地初始化指针,确保在释放对象后将指针设置为nil
,以及避免悬挂指针(dangling pointers,即指向已经释放的内存的指针)。在Delphi中,使用智能指针(如TInterfacedObject
和引用计数)也可以帮助管理对象的生命周期,并减少无效指针引用的风险。
===========
delphi 如何判断一个指针是 dangling pointers?????
在 Delphi 中,没有直接的方式来判断一个指针是否是 dangling pointer(悬挂指针)。悬挂指针是指向已经被释放的内存区域的指针。由于 Delphi(或任何编程语言)无法跟踪内存释放后的使用情况,因此无法直接检测一个指针是否悬挂。
然而,你可以采取一些编程实践来避免产生悬挂指针,并在可能的情况下减少它们的影响:
-
初始化指针:始终确保在使用指针之前将其初始化为
nil
。这样,如果指针未被正确赋值,你可以通过检查它是否为nil
来避免访问未初始化的内存。 -
及时置空:在释放指针所指向的内存后,立即将指针设置为
nil
。这是一种良好的编程习惯,可以帮助你避免在释放后意外地使用指针。 -
使用智能指针:Delphi 支持智能指针的概念,尽管它不像 C++ 那样内置了智能指针类型。你可以通过实现引用计数或使用接口来达到类似的效果。智能指针可以自动管理对象的生命周期,从而减少悬挂指针的风险。
-
避免野指针:野指针是指那些指向未知或无效内存的指针。避免使用野指针可以减少悬挂指针的产生。确保你的代码逻辑正确,不要保留指向已释放对象的指针。
-
内存管理工具:使用像 FastMM、madExcept、AQTime 等内存管理工具来检测内存泄漏和访问违规。这些工具可以帮助你发现代码中的问题,包括悬挂指针的使用。
-
代码审查:定期进行代码审查,检查是否有可能导致悬挂指针的代码模式。这包括检查对象的创建、使用和释放过程,确保它们都是正确的。
-
异常处理:虽然异常处理不能直接检测悬挂指针,但它可以帮助你在访问违规时捕获异常,从而避免程序崩溃。然而,依赖异常处理来管理悬挂指针是不良的编程习惯;它应该被视为最后的防线,而不是解决问题的主要手段。
请记住,悬挂指针问题本质上是关于内存管理的,因此你需要确保你的代码遵循良好的内存管理实践。在 Delphi 中,这通常意味着正确地创建、使用和释放对象,以及谨慎地处理指针。
本文来自博客园,作者:del88,转载请注明原文链接:https://www.cnblogs.com/del88/p/17437729.html