带有自定义记录的 Lazarus Pascal 中的二进制文件错误 - 错误 SIGSEGV

Binary file error in Lazarus Pascal with custom records - error SIGSEGV

我不经常使用 Pascal,所以如果这个问题很基础,我深表歉意。我正在开发一个二进制文件程序,该程序将自定义记录数组写入二进制文件。

最终我希望它能够将多个不同自定义记录类型的数组写入一个二进制文件。

出于这个原因,我想我会先写一个整数作为下一个数组的总字节数。然后我写数组本身。然后我可以读取第一个整数类型块 - 告诉我下一个块的大小直接读入数组。

例如 - 在编写二进制文件时我会做这样的事情:

    assignfile(f,MasterFileName);
      {$I-}
      reset(f,1);
      {$I+}
      n := IOResult;
      if n<> 0 then
      begin
          {$I-}
          rewrite(f);
          {$I+}
      end;
      n:= IOResult;
      If n <> 0 then
      begin
         writeln('Error creating file: ', n);
      end
      else
      begin
      SetLength(MyArray, 2);

      MyArray[0].ID := 101;
      MyArray[0].Att1 := 'Hi';
      MyArray[0].Att2 := 'MyArray 0 - Att2';
      MyArray[0].Value := 1;

      MyArray[1].ID := 102;
      MyArray[1].Att1:= 'Hi again';
      MyArray[1].Att2:= MyArray 1 - Att2';
      MyArray[1].Value:= 5;

      SizeOfArray := sizeOf(MyArray);

      writeln('Size of character array: ', SizeOfArray);
      writeln('Size of integer var: ', sizeof(SizeOfArray));

      blockwrite(f,sizeOfArray,sizeof(SizeOfArray),actual);

      blockwrite(f,MyArray,SizeOfArray,actual);

      Close(f);

然后你可以像这样重新读取文件:

Assign(f, MasterFileName);
Reset(f,1);
blockread(f,SizeOfArray,sizeof(SizeOfArray),actual);
blockread(f,MyArray,SizeOfArray,actual);

Close(f); 

这个想法是在读取这些块之后,您可以记录一个新的整数并保存一个新的数组等。

它读取记录的整数部分,但不读取字符串。记录将是这样的:

TMyType = record
ID : Integer;
att1 : string;
att2 : String;
Value : Integer;
end;

感谢收到任何帮助!!

TMyType = record
ID : Integer;
att1 : string;   // <- your problem

以这种方式声明为字符串的字段 att1 意味着该记录包含指向实际字符串数据的指针(att1 实际上是一个指针)。编译器管理这个指针和相关数据的内存,字符串可以是任何(合理的)长度。

一个快速修复方法是声明 att1 类似 string[64] 的内容,例如:一个最多 64 个字符长的字符串。这将消除指针并使用记录的内存(att1 字段本身,现在是一个特殊的静态数组)作为字符串字符的缓冲区。当然,声明字符串的最大长度可能有点危险:如果您尝试为字符串分配一个太长的字符串,它将被截断。

要真正完整:这取决于编译器;有些有一个开关使你的声明 "string" 可用,使它成为 "string[255]" 的别名。这不是默认的。还要考虑使用 string[...] 更快并且会浪费内存。

你有一些错误。

MyArray是动态数组,引用类型(指针),所以SizeOf(MyArray)是指针的大小,不是数组的大小。要获取数组的长度,请使用 Length(MyArray).

但更大的问题是保存长字符串(AnsiStrings -- string 映射到的通常类型 --, WideStrings, UnicodeStrings) .这些也是引用类型,因此您不能将它们与记录一起保存。你将不得不一个一个地保存记录的部分,对于字符串,你将不得不使用像这样的函数:

procedure SaveStr(var F: File; const S: AnsiString);
var
  Actual: Integer;
  Len: Integer;
begin
  Len := Length(S);
  BlockWrite(F, Len, SizeOf(Len), Actual);
  if Len > 0 then
  begin
    BlockWrite(F, S[1], Len * SizeOf(AnsiChar), Actual);
  end;
end;

当然,您通常应该检查 Actual 并进行适当的错误处理,但为简单起见,我将其省略。

读回类似:首先读取长度,然后使用 SetLength 将字符串设置为该长度,然后读取其余部分。

所以现在你做这样的事情:

Len := Length(MyArray);
BlockWrite(F, Len, SizeOf(Len), Actual);
for I := Low(MyArray) to High(MyArray) do
begin
  BlockWrite(F, MyArray[I].ID, SizeOf(Integer), Actual);
  SaveStr(F, MyArray[I].att1);
  SaveStr(F, MyArray[I].att2);
  BlockWrite(F, MyArray[I].Value, SizeOf(Integer), Actual);
end;
// etc...  

请注意,我目前无法测试代码,因此可能存在一些小错误。如果有必要,我稍后会尝试这个,当我可以访问编译器时。


更新

正如 Marco van de Voort 评论的那样,您可能必须这样做:

rewrite(f, 1);

而不是简单的

rewrite(f);

但正如我回答他的那样,如果可以的话,请使用流。它们更易于使用 (IMO) 并提供更一致的界面,无论您究竟想写什么或读什么。有许多不同类型的流 I/O,并且都派生自(因此兼容)相同的基本抽象 TStream class.