当设置数组(记录数组)长度时,Delphi出现堆栈溢出和访问冲突错误。

3
我正在开发一个应用程序,从两个“记录”文件中读取数据。我遇到了一个奇怪的错误,这取决于我打开文件的顺序(请参见下面的代码)。
如果我先单击button1,然后再单击button2,即先调用“天气数据记录”文件,然后是“参数记录”文件,一切正常。如果我反过来做,就会出现“堆栈溢出”,然后是“访问地址0x7c90e898:写入”的错误。当我在Button1Click中调用SetLength数组时,就会发生这种情况。
天气数据文件大约有550条记录,参数文件大约有45条记录。
有人能看出我的代码有什么明显的问题吗?如果有人想测试这些文件,我不确定如何附加或提供它们...
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, Buttons, ExtCtrls, Grids,FileCtrl,Contnrs;

type  
    TWeatherData = record  
    MyDate : TDate;  
    Rainfall : Double;  
    Temperature : Double;  

  end;

  TParameters = record
    Species : string[50];
    ParameterName: string[50];
    ParameterValue : double;
  end;

  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);

  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
Var
  WeatherDataFile : file of TWeatherData;
  j : integer;
  WeatherDataArray : array of TWeatherData;
  MyFileSize : Integer;

begin


  AssignFile(WeatherDataFile,'C:\Test5.cmbwthr') ;
  Reset(WeatherDataFile);
  MyFileSize := FileSize(WeatherDataFile);

  SetLength(WeatherDataArray,MyFileSize);

  j := 0;

  try
   while not Eof(WeatherDataFile) do begin
    j := j + 1;
    Read (WeatherDataFile, WeatherDataArray[j]) ;
   end;
  finally
   CloseFile(WeatherDataFile) ;
  end;
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  ParametersFile : file of TParameters;
  j : integer;
  CurrentParameters : array of TParameters;
  MyFileSize : Integer;

begin
  AssignFile(ParametersFile,'C:\Test5.cmbpara') ;
  Reset(ParametersFile);

  Reset(ParametersFile);
  MyFileSize := FileSize(ParametersFile);

  SetLength(CurrentParameters,MyFileSize);

  j := 0;

  try
   while not Eof(ParametersFile) do begin
    j := j + 1;
    Read (ParametersFile, CurrentParameters[j]) ;
   end;
  finally
   CloseFile(ParametersFile) ;
  end;
end;

end. 

1
没有走到最后,但如果您不将记录大小传递给Reset函数,则无法通过FileSize获取记录数(除非WeatherData为128字节)。请参阅这两个函数的文档。 - Sertac Akyuz
@David,你能发一个示例文件的链接吗?Sertac的回答很有道理,如果是正确的话,你可能会在某个地方覆盖内存。另外,编译器选项中是否打开了范围检查? - Ken White
@Ken,我已经将文件发布到ftp.csiro.au的“Drew”文件夹中。更新可能需要几分钟时间。 - David Drew
@Sertac,我相信你是对的,但当我将代码修改为Reset(WeatherDataFile,SizeOf(TWeatherData))时,程序无法编译。它说“实际参数太多”。 - David Drew
1
@David - 我错了!根据Rob的评论:“RecSize参数仅适用于无类型文件。如果文件像问题中那样被分类,则记录大小是隐式的,因为编译器已经知道记录类型。” - Sertac Akyuz
显示剩余3条评论
3个回答

8

你在写入数组之前递增索引,而不是之后,导致超出数组范围的写入。由于你正在写入不属于该数组的内存,可能会发生各种问题。

AssignFile(ParametersFile, 'C:\Test5.cmbpara');
Reset(ParametersFile);
try // Enter "try" block as soon as the file is opened.
  MyFileSize := FileSize(ParametersFile);
  SetLength(CurrentParameters, MyFileSize);

  j := 0;
  while not Eof(ParametersFile) do begin
    Read(ParametersFile, CurrentParameters[j]);
    Inc(j);
  end;
finally
  CloseFile(ParametersFile);
end;

if j <> MyFileSize then
  raise Exception.CreateFmt('Parameter count mismatch: expected %d but got %d instead.',
    [MyFileSize, j]);

谢谢Rob!那就是问题所在。一个愚蠢的错误竟然会产生如此戏剧性的影响,真是令人惊讶! - David Drew

0

您需要使用打包记录来保存到文件。

type  
  TWeatherData = packed record  
    MyDate : TDate;  
    Rainfall : Double;  
    Temperature : Double;  
  end;

  TParameters = packed record
    Species : string[50];
    ParameterName: string[50];
    ParameterValue : double;
  end;

1
“Need” 是一个很强的词。实际上太强了。 - Rob Kennedy
使用紧凑记录而不是“普通”记录有什么优势吗? - David Drew
2
@David - 它们对编译器的“记录字段对齐”设置是“免疫”的。如果未打包,则编译器将使用{$An}中指定的任何值或在编译器选项中设置来布局记录。如果打包,则它们将有效地使用{$A1}进行编译。其影响是:1. 最小磁盘使用(不是一个大问题)2. 更改编译器设置或使用不同版本的Delphi不会更改程序所依赖的内部布局。 - Gerry Coll

0
请查看我们在 SynCommons.pas 单元中提供的 TDynArray 包装器。其中包含序列化功能。
您可以将常规字符串放入记录中,而不是使用短字符串:这将占用更少的磁盘空间,并且自 Delphi 2009 以来支持 Unicode。
type  
  TWeatherData = record  
    MyDate : TDate;  
    Rainfall : Double;  
    Temperature : Double;  
  end;
  TWeatherDatas = array of TWeatherData;

  TParameter = record
    Species : string;
    ParameterName: string;
    ParameterValue : double;
  end;
  TParameters = array of TParameter;

var
  Stream: TMemoryStream;
  Params: TParameters;
  Weather: TWeatherDatas;
begin
  Stream := TMemoryStream.Create;
  try
    Stream.LoadFromFile('C:\Test5.cmbpara');
    DynArray(TypeInfo(TParameters),Params).LoadFromStream(Stream));
    Stream.LoadFromFile('C:\Test5.cmbwthr');
    DynArray(TypeInfo(TWeatherDatas),Weather).LoadFromStream(Stream));
  finally
    Stream.Free;
  end;
end;

使用 TDynArray,您可以使用类似于 TList 的属性和方法访问任何动态数组,例如 Count、Add、Insert、Delete、Clear、IndexOf、Find、Sort 等,并且还有一些新的方法,如 LoadFromStream、SaveToStream、LoadFromSaveTo,它们允许快速二进制序列化任何动态数组,甚至包含字符串或记录 - 还提供了一个 CreateOrderedIndex 方法,根据动态数组内容创建单独的索引。如果需要,您还可以将数组内容序列化为 JSON。
适用于 Delphi 6 至 XE

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接