Delphi中引用计数变量是什么?本地引用/非引用计数变量解析
Delphi本地引用计数变量与非引用计数变量解析
一、基本定义
1. 本地非引用计数变量
指局部作用域内的普通变量,分为两类:
- 基本值类型(如
Integer、Boolean):内存分配在栈上,生命周期严格绑定作用域,出作用域后自动回收。 - 传统
TObject派生类实例:内存分配在堆上,栈中仅存储指向堆内存的指针,必须手动调用Free释放堆内存,否则会造成泄漏。
2. 本地引用计数变量
指局部作用域内的引用计数类型实例,其内存由编译器通过维护引用计数自动管理。当变量的引用计数降为0时,系统自动释放其占用的内存。常见类型包括:string、动态数组、interface、TInterfacedObject派生类、Variant,以及Delphi 10.2+中带有[ReferenceCounted]属性的记录类型。
二、核心区别
- 内存管理方式:非引用计数变量要么栈上自动回收(值类型),要么手动释放堆内存(传统类);引用计数变量由编译器自动维护计数,计数为0时自动释放。
- 生命周期:非引用计数变量的生命周期由作用域(栈变量)或开发者手动控制(堆对象);引用计数变量的生命周期取决于引用数量,即使超出作用域,只要存在其他引用就不会销毁。
- 类型范围:非引用计数变量涵盖基本值类型、传统非接口类;引用计数变量涵盖字符串、动态数组、接口、接口实现类等。
三、工作原理
1. 本地非引用计数变量
- 值类型:函数执行时,变量被分配到当前函数的栈帧中,函数执行完毕栈帧弹出,变量内存直接被回收,无需额外操作。
- 传统堆对象:调用
Create方法时在堆上分配内存,栈中仅存指针。函数结束后栈指针被销毁,但堆内存仍存在,必须手动调用Free释放,否则内存泄漏。
2. 本地引用计数变量
编译器会自动插入引用计数操作的代码:
- 赋值操作:原引用的计数减1,若计数为0则释放内存;新引用的计数加1。
- 作用域结束:自动将变量的引用计数减1,若计数为0则销毁对象/释放内存。
- 优化机制:对于
string、动态数组这类类型,编译器实现写时复制(Copy-On-Write),仅当修改内容时才复制底层内存,未修改时共享缓冲区,提升性能。
四、代码示例与说明
1. 本地非引用计数变量示例
procedure NonRefCountDemo; var IntVal: Integer; // 栈上值类型,非引用计数 PlainObj: TObject; // 传统堆对象,非引用计数 begin IntVal := 2024; Writeln('IntVal的值: ', IntVal); // IntVal出作用域后自动回收,无需处理 PlainObj := TObject.Create; try Writeln('对象类名: ', PlainObj.ClassName); finally PlainObj.Free; // 必须手动释放,否则内存泄漏 end; end;
说明:Integer是栈分配的值类型,函数结束后栈帧销毁,内存自动回收;TObject实例在堆上,必须用try-finally确保即使出错也能释放内存,避免泄漏。
2. 本地引用计数变量示例
示例1:字符串类型
procedure RefCountStringDemo; var Str1, Str2: string; // String是引用计数类型 begin Str1 := 'Delphi Reference Count'; Writeln('Str1初始引用计数: ', StringRefCount(Str1)); // 输出1 Str2 := Str1; Writeln('Str1赋值后的引用计数: ', StringRefCount(Str1)); // 输出2,共享底层缓冲区 Str2 := 'Modified String'; Writeln('Str1修改后的引用计数: ', StringRefCount(Str1)); // 输出1,触发写时复制 // 函数结束时,Str1、Str2计数分别减1,均变为0,自动释放内存 end;
说明:字符串的引用计数由编译器自动维护,赋值时共享内存,修改时才复制,作用域结束时自动清理。
示例2:接口类型
type IGreeting = interface ['{F5E0C5D1-1A9B-4D3C-8F7E-6B5A4C3D2E1F}'] procedure Greet; end; TGreetImpl = class(TInterfacedObject, IGreeting) public constructor Create; destructor Destroy; override; procedure Greet; end; constructor TGreetImpl.Create; begin inherited; Writeln('TGreetImpl实例已创建'); end; destructor TGreetImpl.Destroy; begin Writeln('TGreetImpl实例已销毁'); inherited; end; procedure TGreetImpl.Greet; begin Writeln('Hello from Interface!'); end; procedure RefCountInterfaceDemo; var Greet1, Greet2: IGreeting; begin Greet1 := TGreetImpl.Create; // 引用计数变为1 Greet1.Greet; Greet2 := Greet1; // 引用计数变为2 Writeln('当前引用计数: ', Greet1._AddRef - 1); // _AddRef先加1再返回,减1得实际计数 Greet1 := nil; // 引用计数变为1 Greet2 := nil; // 引用计数变为0,触发销毁 end;
说明:TInterfacedObject实现了IInterface的_AddRef和_Release方法,编译器自动调用这些方法维护计数。当最后一个接口引用被置空时,计数降为0,对象自动销毁,无需手动释放。
示例3:动态数组
procedure RefCountDynamicArrayDemo; var Arr1, Arr2: array of Integer; begin SetLength(Arr1, 3); Arr1[0] := 10; Arr1[1] := 20; Arr1[2] := 30; Writeln('Arr1初始引用计数: ', DynArrayRefCount(Arr1)); // 输出1 Arr2 := Arr1; Writeln('Arr1赋值后的引用计数: ', DynArrayRefCount(Arr1)); // 输出2 Arr2[0] := 100; // 修改时触发写时复制 Writeln('Arr1修改后的引用计数: ', DynArrayRefCount(Arr1)); // 输出1 // 作用域结束,两个数组计数均减为0,自动释放内存 end;
说明:动态数组也是引用计数类型,支持写时复制,内存由编译器自动管理,无需手动释放。
内容的提问来源于stack exchange,提问作者Martin Kakhuis
相关产品推荐
相关产品推荐

