You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 16:40:38