Переполнение стека Delphi и ошибка нарушения доступа при установке длины массива (записей)

Я занят созданием приложения, в котором я читаю данные еще из двух файлов «записей». У меня очень странная ошибка, которая выскакивает в зависимости от последовательности, в которой я открываю файлы (см. код ниже).

Если я нажимаю кнопку 1, а затем кнопку 2, вызывая таким образом файл «записей данных о погоде», а затем файл «записей параметров», все в порядке. Если я сделаю это наоборот, я получу «переполнение стека», за которым следует ошибка «нарушение доступа по адресу 0x7c90e898: запись адреса». Это происходит, когда я вызываю SetLength для массива в Button1Click.

Файл данных о погоде содержит около 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. 

person David Drew    schedule 24.03.2011    source источник
comment
До конца не дошел, но если не передать размер записи в Reset, то нельзя получить количество записей по FileSize (если только WeatherData не 128 байт). См. документацию по этим двум функциям.   -  person Sertac Akyuz    schedule 24.03.2011
comment
@Sertac Akyuz: Опубликуйте это как ответ, используя Reset(File, SizeOf(Record)) и я проголосую за него.   -  person Ken White    schedule 24.03.2011
comment
Получение размера файла не кажется проблемой. Размер файла, возвращаемый функцией FileSize, является точным в обоих случаях. И ошибка в SetLength возвращается независимо от того, какой размер я установил для длины, в любом случае...   -  person David Drew    schedule 24.03.2011
comment
@David, можешь опубликовать ссылку на примеры файлов? Ответ Сертака имеет смысл, и если он правильный, вы можете где-то перезаписать память. Кроме того, включена ли проверка диапазона в параметрах компилятора?   -  person Ken White    schedule 24.03.2011
comment
@David. В документации FileSize четко указано, что, когда вы не передаете размер записи в Reset, FileSize вычисляет количество записей по «размеру файла в байтах / 128». Вы уверены, что документация неверна?   -  person Sertac Akyuz    schedule 24.03.2011
comment
@Ken, я разместил файлы на ftp.csiro.au в папке Drew. Обновление может занять несколько минут.   -  person David Drew    schedule 24.03.2011
comment
@Sertac, я уверен, что вы правы, но программа не будет компилироваться, если я изменю код следующим образом: Reset(WeatherDataFile,SizeOf(TWeatherData)). Пишет Слишком много фактических параметров   -  person David Drew    schedule 24.03.2011
comment
@David - Плохо! Согласно комментарию Роба, параметр RecSize разрешен только для нетипизированного файла. Если файл имеет тип, указанный в вопросе, размер записи неявный, поскольку компилятор уже знает тип записи.   -  person Sertac Akyuz    schedule 24.03.2011


Ответы (3)


Вы пишете за концы массивов, увеличивая индекс перед записью в массив, а не после. Поскольку вы записываете в память, которая не принадлежит массиву, может возникнуть множество проблем.

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]);
person Rob Kennedy    schedule 24.03.2011
comment
Спасибо Роб! В этом была проблема. Удивительно, какой драматический эффект произвела эта глупая ошибка! - person David Drew; 24.03.2011

Вам нужны упакованные записи для сохранения в файл.

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

  TParameters = packed record
    Species : string[50];
    ParameterName: string[50];
    ParameterValue : double;
  end;
person Clóvis Valadares Junior    schedule 24.03.2011
comment
Потребность — это сильно сказано. На самом деле слишком сильно. - person Rob Kennedy; 24.03.2011
comment
Есть ли преимущество в использовании упакованных записей, а не простых записей? - person David Drew; 24.03.2011
comment
@David - они невосприимчивы к настройкам выравнивания поля записи компилятора. В случае распаковки компилятор разметит запись, используя любое значение, указанное {$An} или заданное в параметрах компилятора. Если они упакованы, они будут эффективно скомпилированы с {$A1}. Эффекты: 1. Минимальное использование диска (не большая проблема) 2. Изменение настроек компилятора или использование другой версии Delphi не изменит внутреннюю структуру, от которой зависит ваша программа. - person Gerry Coll; 24.03.2011

Взгляните на нашу оболочку TDynArray, доступную в нашем блоке SynCommons.pas. Включена функция сериализации.

И вы можете поместить в записи обычную строку вместо короткой: она будет занимать меньше места на диске и будет готова к использованию Unicode, начиная с Delphi 2009.

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, LoadFrom и SaveTo, которые обеспечивают быструю двоичную сериализацию любого динамического массива, даже содержащего строки или записи. Также доступен метод CreateOrderedIndex для создания отдельного индекса в соответствии с содержимым динамического массива. Вы также можете сериализовать содержимое массива в JSON, если хотите.

Для Delphi 6 до XE.

person Arnaud Bouchez    schedule 24.03.2011