You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

递归处理文本文件关键字时返回阶段触发访问违例求助

问题

我正在构建一个从文本文件提取关键字的数据库,文本文件可使用关键字include引用其他文本文件。
使用的数据结构:

  • ItemRecord:存储文件中关键字的详情、位置等信息
  • FileTree:存储特定文本文件的所有ItemRecord数组
  • Pages:存储从根文本文件引用的所有文件对应的FileTree数组

核心递归代码如下:

Const
  Items: array [0..1] of string = ('Includes','Variables');

function RecurseKeywords(pFileName: String; PageIdx: Integer; L1Index: Integer = 0): Integer;
Var
  i,z, idx, idy, idz,k: Integer;
  ChildCtr: Integer;
  ActMem: TStringList;
  s,t:string;

  function GetItems(Text: String; Category: TCategory):string;
  Var
    RegEx: TRegex;
    Match: TMatch;
  begin
    case Category of
      caIncludes:
        Match := Regex.Match(Text, '^\s*Include\s+"([0-9a-z_\-\\ :]+[\.inc|\.bas]+)"',[roignorecase]);
      caVariables:
        Match := Regex.Match(Text,'^\s*Dim\s+([a-z_#][0-9a-z_#]+)(?:_0-9a-z)*(\s+as\s+(bit|byte|word|dword|float|sbyte|sword|sdword|Long|slong|double|string|pin))',[roignorecase]);
    end;
    if match.Success then
      result := match.Groups[1].Value
    else
      result := '';
  end;

begin
  Result := 0;
  with Pages[PageIdx] do
    if fileExists(pFileName) then begin
      ActMem := TStringList.Create;
      ActMem.LoadFromFile(pFileName);
      idz := L1Index;    // Set Key to first item in the level 1 list
      for idy := 0 to High(Items) do begin   // scan through all items
        ChildCtr := 0;
        for idx := 0 to ActMem.Count -1 do begin
          t := trimLeft(ActMem[idx]);   // remove spaces and skip empty lines.
          if (t <> '') and (t[1] <> #39) and (t[1] <> #59) then begin
            s := GetItems(t, TCategory(idy));
            if s > '' then with Pages[PageIdx] do begin
              if (High(L0CodeData) = -1)                            // if no L0 data or
              or (L0CodeData[High(L0CodeData)].RecType<>TCategory(idy)) then   // L0 Record type <> to Item type
                SetLength(L0CodeData,Length(L0CodeData)+1);         // Create new L0 entry
              with L0CodeData[High(L0CodeData)] do begin
                RecType := TCategory(idy);
                Caption := Items[idy];
                ImageIndex := 0;
                lineNo := Idx;
                Key := idz;
              end;

              SetLength(L1CodeData, Length(L1CodeData)+ 1);
              with L1CodeData[High(L1CodeData)] do begin   // Populate the L1 Record
                RecType := TCategory(idy);
                Caption := s;
                ImageIndex := idy+3;
                LineNo := idx;
                case RecType of
                  caIncludes:
                    if FileExists(sourcepath+s) then begin
                      ImageIndex := 4;
                      SetLength(Pages, Length(Pages)+1);
                      Key := High(Pages);
                      Caption := s;
                      ChildCount := RecurseKeywords(SourcePath+s,key);
                    end;
                  caVariables:
                end;
                ChildCount := 0;
                Inc(ChildCtr);
              end;
            end;
          end;
        end;
        idz := Idz + ChildCtr;
        if ChildCtr > 0 then
          Pages[PageIdx].L0CodeData[High(L0CodeData)].ChildCount := ChildCtr;
        Form1.SaveToFile;
      end;
      ActMem.Free;
      result := Length(Pages[PageIdx].L0CodeData);
    end;
end;

当递归返回至顶层文件并从include转向其他关键字类型时,触发Access Violation。此时数据库除顶层文件中include关键字之后的内容外已完整构建,且数组内容均显示正常。请求解释问题原因及解决方法。

问题原因分析

  1. Pages数组扩容导致引用失效
    处理include文件时,执行SetLength(Pages, Length(Pages)+1)新增FileTree条目,但顶层函数一开始用了with Pages[PageIdx] do。动态扩容数组会导致内存重新分配,原来的Pages[PageIdx]引用变为野指针,后续访问L0CodeData、L1CodeData时触发越界。

  2. ChildCount赋值逻辑冲突
    处理caIncludes时,先通过递归得到子节点数量赋值给ChildCount,但随后又执行ChildCount := 0覆盖了正确值,不仅逻辑错误,还会导致后续索引计算混乱。

  3. L0CodeData的Key值计算错误
    新增L0CodeData条目时,Key依赖idz的累计值,而递归修改Pages数组后,idz的计算和实际L1CodeData的数量不匹配,可能引发数组访问越界。

解决方法

1. 避免with块中修改Pages数组

移除顶层的with Pages[PageIdx] do,改用临时变量存储当前FileTree的引用,防止数组扩容后引用失效:

var
  CurrentPage: FileTree;
begin
  Result := 0;
  if fileExists(pFileName) then begin
    CurrentPage := Pages[PageIdx];
    ActMem := TStringList.Create;
    // 后续所有操作替换为CurrentPage.XXX,而非Pages[PageIdx].XXX
  end;
end;

2. 修复ChildCount赋值逻辑

移除全局的ChildCount := 0,仅在caVariables分支明确赋值0,保留递归返回的子节点数量:

case RecType of
  caIncludes:
    if FileExists(sourcepath+s) then begin
      ImageIndex := 4;
      SetLength(Pages, Length(Pages)+1);
      Key := High(Pages);
      Caption := s;
      ChildCount := RecurseKeywords(SourcePath+s,key);
    end;
  caVariables:
    ChildCount := 0; // 变量无节点,在此赋值0
end;
// 删除原来的ChildCount := 0;语句
Inc(ChildCtr);

3. 修正L0CodeData的Key值

用当前L1CodeData的长度作为Key值,确保起始索引准确:

with CurrentPage.L0CodeData[High(CurrentPage.L0CodeData)] do begin
  RecType := TCategory(idy);
  Caption := Items[idy];
  ImageIndex := 0;
  lineNo := Idx;
  Key := Length(CurrentPage.L1CodeData); // 用L1数组当前长度作为起始Key
end;

4. 加强数组边界检查

访问L0CodeData[High(L0CodeData)]前,再次确认数组非空,避免极端情况下的越界访问。


内容的提问来源于stack exchange,提问作者John Barrat

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 02:37:12