Search code examples
delphimemory-leaksaccess-violation

Why does not MemoryLeak or AccessViolation occur?


I found two cases that made me curious.

The first is why no memory leak occurs when using the JSON library.

If GetValue returns a TJSONValue that does not implement an interface. Why does my "if" not raise a memory leak?

procedure TFrmApp.btnJSONClick(Sender: TObject);
var
    JSONObject: TJSONObject;
    JSONStr: string;
begin
    JSONStr := '{"colors":[{"name":"red", "hex":"#f00"}]}';

    JSONObject := TJSONObject.ParseJSONValue(JSONStr) as TJSONObject;
    try

    // If GetValue returns a TJSONValue that does not implement an interface.
    // Why does my "if" not raise a memory leak?
    if JSONObject.GetValue('colors') <> nil then
        memo.Lines.Add('Colors exist.')
    else
        memo.Lines.Add('Colors not found.');

    finally
    JSONObject.Free;
    end;
end;

The second is why access violation does not occur when accessing the fields of the Cds.

If the Cds that provided the Data has been released from memory. Why does not access violation occur?

procedure TFrmApp.btnDataSetClick(Sender: TObject);
var
    Cds: TClientDataSet;
begin
    Cds := TClientDataSet.Create(Self);
    try
    Cds.Data := Self.GetData;

    // If the Cds that provided the Data has been released from memory.
    // Why does not access violation occur?
    memo.Lines.Add('Name: ' + Cds.FieldByName('VendorName').AsString);
    memo.Lines.Add('City: ' + Cds.FieldByName('City').AsString);

    finally
    Cds.Free;
    end;
end;

function TFrmApp.GetData: OleVariant;
var
    Cds: TClientDataSet;
begin
    Cds := TClientDataSet.Create(Self);
    try
    Cds.LoadFromFile(TDirectory.GetCurrentDirectory + '\data.xml');

    Result := Cds.Data;
    finally
    Cds.Free;
    end;
end;

Sample Project:

.DPR

program TestMemoryLeak;

uses
    Vcl.Forms,
    uApp in 'uApp.pas' {FrmApp};

{$R *.res}

begin
    ReportMemoryLeaksOnShutdown := true;
    Application.Initialize;
    Application.MainFormOnTaskbar := True;
    Application.CreateForm(TFrmApp, FrmApp);
    Application.Run;
end.

uAPP.pas

unit uApp;

interface

uses
    Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
    Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, System.JSON, REST.Json, Datasnap.DBClient, Data.DB,
    System.IOUtils;

type
    TFrmApp = class(TForm)
    btnJSON: TButton;
    btnDataSet: TButton;
    memo: TMemo;
    procedure btnJSONClick(Sender: TObject);
    procedure btnDataSetClick(Sender: TObject);
    private
    { Private declarations }
    function GetData: OleVariant;
    public
    { Public declarations }
    end;

var
    FrmApp: TFrmApp;

implementation

{$R *.dfm}


procedure TFrmApp.btnJSONClick(Sender: TObject);
var
    JSONObject: TJSONObject;
    JSONStr: string;
begin
    JSONStr := '{"colors":[{"name":"red", "hex":"#f00"}]}';

    JSONObject := TJSONObject.ParseJSONValue(JSONStr) as TJSONObject;
    try

    // If GetValue returns a TJSONValue that does not implement an interface.
    // Why does my "if" not raise a memory leak?
    if JSONObject.GetValue('colors') <> nil then
        memo.Lines.Add('Colors exist.')
    else
        memo.Lines.Add('Colors not found.');

    finally
    JSONObject.Free;
    end;
end;

procedure TFrmApp.btnDataSetClick(Sender: TObject);
var
    Cds: TClientDataSet;
begin
    Cds := TClientDataSet.Create(Self);
    try
    Cds.Data := Self.GetData;

    // If the Cds that provided the Data has been released from memory.
    // Why does not access violation occur?
    memo.Lines.Add('Name: ' + Cds.FieldByName('VendorName').AsString);
    memo.Lines.Add('City: ' + Cds.FieldByName('City').AsString);

    finally
    Cds.Free;
    end;
end;

function TFrmApp.GetData: OleVariant;
var
    Cds: TClientDataSet;
begin
    Cds := TClientDataSet.Create(Self);
    try
    Cds.LoadFromFile(TDirectory.GetCurrentDirectory + '\data.xml');

    Result := Cds.Data;
    finally
    Cds.Free;
    end;
end;

end.

uApp.dfm

object FrmApp: TFrmApp
    Left = 0
    Top = 0
    Caption = 'FrmApp'
    ClientHeight = 245
    ClientWidth = 516
    Color = clBtnFace
    Font.Charset = DEFAULT_CHARSET
    Font.Color = clWindowText
    Font.Height = -11
    Font.Name = 'Tahoma'
    Font.Style = []
    OldCreateOrder = False
    Position = poScreenCenter
    PixelsPerInch = 96
    TextHeight = 13
    object btnJSON: TButton
    Left = 24
    Top = 30
    Width = 75
    Height = 25
    Caption = 'JSON'
    TabOrder = 0
    OnClick = btnJSONClick
    end
    object btnDataSet: TButton
    Left = 24
    Top = 96
    Width = 75
    Height = 25
    Caption = 'DataSet'
    TabOrder = 1
    OnClick = btnDataSetClick
    end
    object memo: TMemo
    Left = 176
    Top = 8
    Width = 321
    Height = 229
    TabOrder = 2
    end
end

data.xml

<?xml version="1.0" standalone="yes"?>
<DATAPACKET Version="2.0">
    <METADATA>
        <FIELDS>
            <FIELD attrname="VendorNo" fieldtype="r8"/>
            <FIELD attrname="VendorName" fieldtype="string" WIDTH="30"/>
            <FIELD attrname="City" fieldtype="string" WIDTH="20"/>
            <FIELD attrname="State" fieldtype="string" WIDTH="20"/>
        </FIELDS>
        <PARAMS DEFAULT_ORDER="1" PRIMARY_KEY="1" LCID="1033"/>
    </METADATA>
    <ROWDATA>
        <ROW VendorNo="2014" VendorName="Cacor Corporation" City="Southfield" State="OH"/>
        <ROW VendorNo="2641" VendorName="Underwater" City="Indianapolis" State="IN" />
        <ROW VendorNo="2674" VendorName="J.W.  Luscher Mfg." City="Berkely" State="MA"/>
        <ROW VendorNo="3511" VendorName="Scuba Professionals" City="Rancho Dominguez"/>
        <ROW VendorNo="3819" VendorName="Divers&apos;  Supply Shop" City="Macon" State="GA"/>
    </ROWDATA>
</DATAPACKET>

Solution

  • why no memory leak occurs when using the JSON library.

    If GetValue returns a TJSONValue that does not implement an interface. Why does my "if" not raise a memory leak?

    TJSONObject.GetValue() returns a pointer to a TJSONValue object that is owned internally by the TJSONObject. The TJSONValue will be freed when the TJSONObject is freed, which you do at the end of your procedure. GetValue() does not allocate any new memory for output, so there is nothing for you to free, hence no leak.

    why access violation does not occur when accessing the fields of the Cds.

    If the Cds that provided the Data has been released from memory. Why does not access violation occur?

    You are assigning the source ClientDataSet's Data property to an OleVariant, and then assigning that to the other ClientDataSet's Data property. OleVariant makes a copy of whatever data is assigned to it (and in the case of [arrays of] interfaced objects, it increments their reference counts), so whether the original ClientDataSet is freed or not makes no difference because the OleVariant is independent and manages the data it is holding.