递归处理文本文件关键字时返回阶段触发访问违例求助
问题
我正在构建一个从文本文件提取关键字的数据库,文本文件可使用关键字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关键字之后的内容外已完整构建,且数组内容均显示正常。请求解释问题原因及解决方法。
问题原因分析
Pages数组扩容导致引用失效
处理include文件时,执行SetLength(Pages, Length(Pages)+1)新增FileTree条目,但顶层函数一开始用了with Pages[PageIdx] do。动态扩容数组会导致内存重新分配,原来的Pages[PageIdx]引用变为野指针,后续访问L0CodeData、L1CodeData时触发越界。ChildCount赋值逻辑冲突
处理caIncludes时,先通过递归得到子节点数量赋值给ChildCount,但随后又执行ChildCount := 0覆盖了正确值,不仅逻辑错误,还会导致后续索引计算混乱。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

