Some types (Unicodestring, Ansistring, interfaces, dynamic arrays) are treated somewhat specially by the compiler: the data has a reference count which is increased or decreased depending on how many reference to the data exists.
The qualifiers for parameters in function or procedure calls influence what happens to the reference count of the managed types:
nothing (pass by value): the reference count of the parameter is increased on entry and decreased on exit.
out: the reference count of the value that is passed in is decreased by 1, and the variable that’s passed into the procedure is initialized to “empty” (usually Nil, but that is an implementation detail which should not be relied on).
var nothing happens to the reference count. A reference to the original variable is passed in, and changing it or reading it has exactly the same effect as changing/reading the original variable.
const this case is slightly tricky. Nothing happens to the reference count because you can pass non-values here. In particular, you can pass a class implementing an interface rather than the interface itself which can cause the class to be freed unexpectedly.
Remark The function result is internally treated as a var parameter to the function, and the same rules as for var parameters apply.
The following example demonstrates the dangers:
{$mode objfpc} Type ITest = Interface Procedure DoTest(ACount : Integer); end; TTest = Class(TInterfacedObject,ITest) Procedure DoTest(ACount : Integer); Destructor destroy; override; end; Destructor TTest.Destroy; begin Writeln('Destroy called'); end; Procedure TTest.DoTest(ACount : Integer); begin Writeln('Test ',ACount,' : ref count: ',RefCount); end; procedure DoIt1(x: ITest; ACount : Integer); begin // Reference count is increased x.DoTest(ACount); // And decreased end; procedure DoIt2(const x: ITest; ACount : Integer); begin // No change to reference count. x.DoTest(ACount); end; Procedure Test1; var y: ITest; begin y := TTest.Create; // Ref. count is 1 at this point. y.DoTest(1); // Calling DoIT will increase reference count and decrease on exit. DoIt1(y,2); // Reference count is still one. y.DoTest(3); end; Procedure Test2; var Y : TTest; begin Y := TTest.Create; // no count on the object yet // Ref. count is 0 at this point. y.DoTest(3); // Ref count will remain zero. DoIt2(y,4); Y.DoTest(5); Y.Free; end; Procedure Test3; var Y : TTest; begin Y := TTest.Create; // no count on the object yet // Ref. count is 0 at this point. y.DoTest(6); // Ref count will remain zero. DoIt1(y,7); y.DoTest(8); end; begin Test1; Test2; Test3; end.
The output of this example is:
Test 1 : ref count: 1 Test 2 : ref count: 2 Test 3 : ref count: 1 Destroy called Test 3 : ref count: 0 Test 4 : ref count: 0 Test 5 : ref count: 0 Destroy called Test 6 : ref count: 0 Test 7 : ref count: 1 Destroy called Test 8 : ref count: 0
As can be seen, in test3, the reference count is decreased from 1 to 0 at the end of the DoIt call, causing the instance to be freed before the call returns.
The following small program demonstrates the reference counts used in strings:
{$mode objfpc} {$H+} Procedure ByVar(Var S : string); begin Writeln('By var, ref count : ',StringRefCount(S)); end; Procedure ByConst(Const S : string); begin Writeln('Const, ref count : ',StringRefCount(S)); end; Procedure ByVal(S : string); begin Writeln('Value, ref count : ',StringRefCount(S)); end; Function FunctionResult(Var S : String) : String; begin Writeln('Function argument, ref count : ',StringRefCount(S)); Writeln('Function result, ref count : ',StringRefCount(Result)); end; Var S,T : String; begin S:='Some string'; Writeln('Constant : ',StringRefCount(S)); UniqueString(S); Writeln('Unique : ',StringRefCount(S)); T:=S; Writeln('After Assign : ',StringRefCount(S)); ByVar(S); ByConst(S); ByVal(S); UniqueString(S); T:=FunctionResult(S); Writeln('After function : ',StringRefCount(S)); end.