小弟本意是想通过解析单元文件生成工程的引用结构图
在使用Move方法复制数据时经常出现不稳定的错误,时有时无!
Access violation at address 00402CEB in module 'Project1.exe'. Read of address 01830000
多运行几次测试代码就会报上述错。代码如下:
function TCodeFileModel.Append(AFileName: PChar): PCodeFile;
var
  pItem: PCodeFile;
begin
    New(pItem);
    FillChar(pItem^, Sizeof(TCodeFile), #0);
//    Move(AFileName^, pItem^, MaxFileLength);
    Move(AFileName^, pItem.Name, MaxFileLength);
    // .... 其他代码
end;
使用下面的TCodeFile 和 TCodeFileLink 记录生成单元引用关系
  PCodeFileLink = ^TCodeFileLink;
  PCodeFile = ^TCodeFile;
  // 单元文件信息记录
  TCodeFile = record
    Name: array[0..MaxFileLength] of char;  // 单元名称
    Kind: TCodeFileKind;   //类型
    Contain: PCodeFileLink;  //所引用到的单元链
    Next: PCodeFile;  
    Prev: PCodeFile;  
  end;  //单元链记录
  TCodeFileLink = record
    CodeFile: PCodeFile;
    Next: PCodeFileLink; 
  end;下面是完整单元代码和测试代码
--------------------------------------------------------------------------------------// 测试代码
procedure TForm2.btn3Click(Sender: TObject);
var
  i: Integer;
  j: Integer;
  pItem: PCodeFile;
begin
  if FileMap <> nil then
    FileMap.Free;
  FileMap := TCodeFileModel.Create;  // 多运行几次这里的测试代码就会出现错误
  AddMsg('开始增加1000个');
  btn3.Enabled := False;
  try
    for i := 1 to 1000 do
    begin
      try
        pItem := FileMap.Append(Pchar('Test' + IntToStr(i)));
        for j := 1 to 100 do
          FileMap.AddLink(pItem, Pchar(IntToStr(i) + ' - Child - ' + IntToStr(j)));
      except
        on E: Exception do
          AddMsg(e.Message);
      end;
    end;
  finally
    btn3.Enabled := True;
    AddMsg('增加1000个结束');
  end;
end;
procedure TForm2.AddMsg(const AValue: string);
begin
  mmo1.Lines.Add(AValue);
end;unit uUnitMap;interfaceconst
  MaxFileLength = 60;
type
  // 搜索范围内, 搜索范围外, 工程
  TCodeFileKind = (cfExternal, cfInternal, cfProject);  PCodeFileLink = ^TCodeFileLink;
  PCodeFile = ^TCodeFile;  TCodeFile = record
    Name: array[0..MaxFileLength] of char;
    Kind: TCodeFileKind;
    Contain: PCodeFileLink;
    Next: PCodeFile;
    Prev: PCodeFile;
  end;
  TCodeFileLink = record
    CodeFile: PCodeFile;
    Next: PCodeFileLink; // 使用单链就可以,
    //Prev: PCodeFileLink;
  end;  TCodeFileModel = class
  public
    FirstItem: PCodeFile;
    LastItem: PCodeFile;
    CurrentItem: PCodeFile;
    Count: integer;    function Append(AFileName: PChar): PCodeFile;
    function Find(AFileName: PChar): PCodeFile;
    function Locate(AFileName: PChar): Boolean;
    function FindLink(AParent, AChild: PCodeFile): Boolean;
    procedure Link(AParent, AChild: PCodeFile);
    procedure AddLink(AParent: PCodeFile; AFileName: PChar);  public
    constructor Create;
    destructor Destroy; override;
    procedure Clear;
  end;
implementationuses SysUtils;constructor TCodeFileModel.Create;
begin
  inherited;
  clear;
end;destructor TCodeFileModel.Destroy;
begin
  Clear;
  inherited;
end;function TCodeFileModel.Locate(AFileName: PChar): Boolean;
var
  pItem: PCodeFile;
begin
  pItem := Find(AFileName);
  Result := pItem <> nil;
  if Result then
    CurrentItem := pItem;
end;function TCodeFileModel.FindLink(AParent, AChild: PCodeFile): Boolean;
var
  pIncItem: PCodeFileLink;
begin
  Result := False;
  pIncItem := AParent.Contain;
  while pIncItem <> nil do
  begin
    Result := pIncItem.CodeFile = AChild;
    if Result then
      Break;
    pIncItem := pIncItem.Next;
  end;
end;function TCodeFileModel.Append(AFileName: PChar): PCodeFile;
var
  pItem: PCodeFile;
begin
  try
    New(pItem);
    FillChar(pItem^, Sizeof(TCodeFile), #0);
    // 下面Move会出现错误,不稳定(错误时有时无)
//    Move(AFileName^, pItem^, MaxFileLength);
    Move(AFileName^, pItem.Name, MaxFileLength);    pItem.Prev := LastItem;
    if LastItem <> nil then
      LastItem.Next := pItem
    else
    begin
      FirstItem := pItem;
      LastItem := pItem;
    end;
    LastItem := pItem;    inc(Count);
    Result := pItem;
  except
//    raise Exception.Create('Append(AFileName: PChar): PCodeFile');
    on E: Exception do
      raise Exception.Create('Err: Add ' + AFileName + '  Count:' + IntToStr(Count) +
        #13#10'Error Code'#13#10 + E.Message + #13#10 +
        'Address:' + IntToStr(integer(@pItem)));
  end;end;procedure TCodeFileModel.Clear;
var
  pLink: PCodeFileLink;
begin
  while FirstItem <> nil do
  begin
    CurrentItem := FirstItem;
    FirstItem := FirstItem.Next;    while CurrentItem.Contain <> nil do
    begin
      pLink := CurrentItem.Contain;
      CurrentItem.Contain := pLink.Next;
      Dispose(pLink);
    end;
    Dispose(CurrentItem);
  end;  FirstItem := nil;
  LastItem := nil;
  CurrentItem := nil;
  Count := 0;
end;procedure TCodeFileModel.Link(AParent, AChild: PCodeFile);
var
  pLink: PCodeFileLink;
begin
  try
    New(pLink);
    FillChar(pLink^, SizeOf(PCodeFileLink), #0);
    pLink^.CodeFile := AChild;    pLink.Next := AParent.Contain;
    if AParent.Contain <> nil then
      AParent.Contain := pLink;
  except
    raise Exception.Create('Link(AParent, AChild: PCodeFile);');
  end;
end;procedure TCodeFileModel.AddLink(AParent: PCodeFile; AFileName: PChar);
var
  pLinkItem: PCodeFile;
begin
  // 此处应该考虑这个单元文件是否已经被关联过的检查
  //
  Assert(AParent <> nil);
  pLinkItem := Append(AFileName);
  Link(AParent, pLinkItem);
end;function TCodeFileModel.Find(AFileName: PChar): PCodeFile;
var
  pItem: PCodeFile;
begin
  Result := nil;  pItem := FirstItem;
  while pItem <> nil do
  begin
    if AnsiStrComp(AFileName, pItem.Name) = 0 then
    begin
      Result := pItem;
      Break;
    end;
    pItem := pItem.Next;
  end;
end;end.

解决方案 »

  1.   

    改成
    Move(AFileName^, pItem.Name, min(MaxFileLength,strlen(AFileName)));
    试试。
    另外根据调用者传给AFileName参数值的可能性,适当增加对空串的处理,即AFileName = nil时的处理。
      

  2.   

    是Move(AFileName^, pItem.Name, min(MaxFileLength,strlen(AFileName) + 1));
      

  3.   

    页面访问越界了,访问了还没被分配的地址出现了访问权限错误。注意一下那个地址,正好是64k的整数倍,也就是页面分界的地方
    原因就是那次 Move 的时候 AFileName+MaxFileLength 刚好越过它所在活动页面的边界,到了一个未被分配的页面,于是访问就出错了。运行多了才出错是因为,像不到64字节的小内存,并不是很容易处于页面边缘,并且差那么几个到几十个字节就越过页面了
      

  4.   

    TCodeFileModel在消毁的时候有没有释放前面New出来的PCodeFile?
      

  5.   

    谢谢:Allan_xd 和Seamour 
    同样感谢unsigned查到了一处不显眼的内存泄露^_^