一、错误现象描述当一个运行期Access Violation出现时你的用户得到的错误信息类似于如下情况Access violation at address 十六进制值 in module 应用程序名 Read of address 十六进制值例如本文标题所示的错误Access violation at address 0048B11A in module XXXX.exe. Read of address二、错误原因分析在Delphi程序中一个最普遍导致Access Violation错误的原因是使用了一个没有被创建的对象实例。重点关注第二个地址值如果该值是00000000∗∗或∗∗00000000∗∗或∗∗FFFFFFF十有八九就是你访问了一个没有被建立的对象典型场景调用了一个表单的事件但这个表单不是自动创建的也没有通过代码实例化典型错误代码示例假设有如下JSON数据结构json{processchangeback:[{workordernum:SCRW-2024-05-07-00005-1,productname:E15/4m,processname:工序1,orderNum:2}]}错误代码写法strreportType : Copy(ArrayRoot.GetValue(reportType).ToString, 2, Length(ArrayRoot.GetValue(reportType).ToString) - 2);在上面的代码里ArrayRoot对象可能为nil或者GetValue(reportType)返回了nil此时调用.ToString就会触发 Access Violation。三、软件层面的常见原因3.1 原因总览以下情况均可能导致 Read of address 00000000 错误序号原因说明典型场景1对象未创建声明了对象变量但未调用构造函数.Create直接使用变量调用方法2对象已被释放调用了.Free或.Destroy后继续访问在其他事件中使用了已释放的表单对象3接口指针为 nil接口变量未赋值就调用方法未实例化的接口调用4PChar 指针为 nil对空指针进行字符串操作对 PChar(nil) 执行StrLen5动态数组未初始化访问了未初始化的动态数组元素未 SetLength 就赋值6窗体未创建通过Application.CreateForm之外的方式访问窗体在未实例化时使用直接调用非自动创建窗体的变量7TJSONValue 节点为空JSON 解析时目标节点不存在调用GetValue后未检查直接使用8TStringList 对象未创建声明了 TStringList 变量但未 Create直接调用.Add或.Text9TDataSet 状态异常数据集未打开或 RecordCount 为 0 时访问字段对空数据集调用FieldByName10回调函数/事件指针为空事件属性未赋值就被触发调用未赋值的OnClick事件3.2 详细示例基于 JSON 解析场景扩展以下所有示例均基于你提供的 JSON 数据结构json{ processchangeback: [ { workordernum: SCRW-2024-05-07-00005-1, productname: E15/4m, processname: 工序1, orderNum: 2 } ] }原因 1对象未创建错误代码pascalvar jsonObj: TJSONObject; jsonValue: TJSONValue; begin // 忘记创建 jsonObj直接使用 jsonValue : jsonObj.GetValue(processchangeback); // ↑ Access ViolationjsonObj 为 nil end;修正方案pascalvar jsonObj: TJSONObject; jsonValue: TJSONValue; begin jsonObj : TJSONObject.Create; // 必须创建 try jsonObj : TJSONObject.ParseJSONValue({processchangeback:[...]}) as TJSONObject; if Assigned(jsonObj) then jsonValue : jsonObj.GetValue(processchangeback); finally jsonObj.Free; end; end;原因 2对象已被释放错误代码pascalvar jsonObj: TJSONObject; jsonValue: TJSONValue; begin jsonObj : TJSONObject.ParseJSONValue({processchangeback:[...]}) as TJSONObject; jsonObj.Free; // 提前释放了 // 后续代码还在使用已释放的对象 jsonValue : jsonObj.GetValue(processchangeback); // ↑ Access Violation对象已被释放但指针未置 nil end;修正方案pascalvar jsonObj: TJSONObject; jsonValue: TJSONValue; begin jsonObj : TJSONObject.ParseJSONValue({processchangeback:[...]}) as TJSONObject; try jsonValue : jsonObj.GetValue(processchangeback); // 使用 jsonValue... finally FreeAndNil(jsonObj); // 释放后将指针置为 nil end; // 后续如需使用检查 Assigned(jsonObj) end;原因 3-4接口指针 / PChar 指针为 nil错误代码pascalprocedure ProcessPCharData(p: PChar); begin // 未检查 p 是否为空就直接取长度 ShowMessage(长度 IntToStr(StrLen(p))); // ↑ 如果 p nilAccess Violation end; // 调用时 ProcessPCharData(nil);修正方案pascalprocedure ProcessPCharData(p: PChar); begin if p nil then ShowMessage(长度 IntToStr(StrLen(p))) else ShowMessage(数据为空); end;原因 5动态数组未初始化错误代码pascalvar arr: array of string; begin // 忘记 SetLength直接赋值 arr[0] : 工序1; // ↑ Access Violation数组未分配内存 end;修正方案pascalvar arr: array of string; begin SetLength(arr, 1); // 必须先分配长度 arr[0] : 工序1; // 安全使用 end;原因 6窗体未创建场景说明当XXXX.exe的 Project Options 中某个窗体没有勾选 Auto-Create且代码中直接访问该窗体变量。错误代码pascal// 假设 TFormReport 未在 Auto-Create 列表中 procedure TMainForm.btnShowReportClick(Sender: TObject); begin // 直接调用未创建的窗体 FormReport.ShowModal; // ↑ Access ViolationFormReport 为 nil end;修正方案pascalprocedure TMainForm.btnShowReportClick(Sender: TObject); begin if not Assigned(FormReport) then FormReport : TFormReport.Create(Application); FormReport.ShowModal; end;原因 7TJSONValue 节点为空针对你的案例重点扩展原始错误代码你提供的案例pascalstrreportType : Copy(ArrayRoot.GetValue(reportType).ToString, 2, Length(ArrayRoot.GetValue(reportType).ToString) - 2);问题分析ArrayRoot可能为nilGetValue(reportType)在 JSON 中找不到该字段时返回nil对nil调用.ToString→ Access Violation修正方案 1 - 基础检查pascalvar jv: TJSONValue; strreportType: string; begin jv : ArrayRoot.GetValue(reportType); if Assigned(jv) then begin strreportType : jv.ToString; // 去除首尾引号 if (Length(strreportType) 2) and (strreportType[1] ) and (strreportType[Length(strreportType)] ) then strreportType : Copy(strreportType, 2, Length(strreportType) - 2); end else strreportType : ; // 默认值 end;修正方案 2 - 使用 TryGetValue推荐pascalvar jv: TJSONValue; strreportType: string; begin if ArrayRoot.TryGetValue(reportType, jv) then strreportType : jv.Value // .Value 自动去除引号 else strreportType : ; end;修正方案 3 - 封装为安全函数pascalfunction SafeGetJSONString(Obj: TJSONObject; const Key: string): string; var jv: TJSONValue; begin Result : ; if not Assigned(Obj) then Exit; if Obj.TryGetValue(Key, jv) then Result : jv.Value else Result : ; end; // 调用方式 strreportType : SafeGetJSONString(ArrayRoot, reportType);原因 8TStringList 对象未创建错误代码pascalvar sl: TStringList; begin // 忘记 Create sl.Add(订单号SCRW-2024-05-07-00005-1); // ↑ Access Violation end;修正方案pascalvar sl: TStringList; begin sl : TStringList.Create; try sl.Add(订单号SCRW-2024-05-07-00005-1); // 使用 sl... finally sl.Free; end; end;原因 9TDataSet 状态异常错误代码pascalprocedure ShowOrderInfo(DataSet: TDataSet); var sWorkOrder: string; begin // 未检查数据集是否打开或是否有数据 sWorkOrder : DataSet.FieldByName(workordernum).AsString; // ↑ 如果 DataSet 为 nil 或未打开Access Violation end;修正方案pascalprocedure ShowOrderInfo(DataSet: TDataSet); var sWorkOrder: string; begin if Assigned(DataSet) and DataSet.Active and not DataSet.IsEmpty then sWorkOrder : DataSet.FieldByName(workordernum).AsString else sWorkOrder : ; end;原因 10回调函数/事件指针为空错误代码pascaltype TNotifyEvent procedure(Sender: TObject) of object; var FOnDataReceived: TNotifyEvent; begin // 事件未赋值直接调用 FOnDataReceived(Self); // ↑ Access Violation事件指针为 nil end;修正方案pascalvar FOnDataReceived: TNotifyEvent; begin if Assigned(FOnDataReceived) then FOnDataReceived(Self) else // 未赋值时默认处理 LogMessage(事件未绑定); end;3.3 针对你案例的完整安全解析代码综合以上原则针对你提供的 JSON 数据完整的安全解析代码如下pascalprocedure ParseOrderInfo(const JSONStr: string); var RootObj: TJSONObject; ArrayValue: TJSONValue; ItemObj: TJSONObject; i: Integer; sWorkOrder, sProductName, sProcessName, sOrderNum: string; begin // Step 1: 解析 JSON 字符串 RootObj : TJSONObject.ParseJSONValue(JSONStr) as TJSONObject; if not Assigned(RootObj) then begin ShowMessage(JSON 格式错误); Exit; end; try // Step 2: 获取数组字段 if not RootObj.TryGetValue(processchangeback, ArrayValue) then begin ShowMessage(找不到 processchangeback 字段); Exit; end; // Step 3: 验证是否为数组 if not (ArrayValue is TJSONArray) then begin ShowMessage(processchangeback 不是数组类型); Exit; end; // Step 4: 遍历数组 for i : 0 to (ArrayValue as TJSONArray).Count - 1 do begin ItemObj : (ArrayValue as TJSONArray).Items[i] as TJSONObject; if not Assigned(ItemObj) then Continue; // Step 5: 安全获取每个字段值 sWorkOrder : SafeGetJSONString(ItemObj, workordernum); sProductName : SafeGetJSONString(ItemObj, productname); sProcessName : SafeGetJSONString(ItemObj, processname); sOrderNum : SafeGetJSONString(ItemObj, orderNum); // 处理数据... ShowMessage(Format(工单%s产品%s工序%s序号%s, [sWorkOrder, sProductName, sProcessName, sOrderNum])); end; finally RootObj.Free; end; end; function SafeGetJSONString(Obj: TJSONObject; const Key: string): string; var jv: TJSONValue; begin Result : ; if not Assigned(Obj) then Exit; if Obj.TryGetValue(Key, jv) then Result : jv.Value else Result : ; end;3.4 常见原因速查表针对你的业务场景错误场景可能原因快速检查解析 JSON 时报错ArrayRoot为 nil检查是否调用ParseJSONValueGetValue后报错JSON 中无该字段使用TryGetValue解析数组时报错字段类型不匹配使用is运算符验证类型表单弹窗报错窗体未 Auto-Create在代码中显式Create回调函数报错事件未绑定使用Assigned检查四、调试定位方法方法一使用 Delphi IDE 调试编译选项设置确保勾选Project - Options - Compiler - Debugging - Debug information勾选Use debug .dcus如需要深入VCL源码运行程序在 IDE 中按 F9 运行复现错误IDE 会自动定位到出错源代码行查看调用栈按CtrlAltS打开 Call Stack 窗口向上追溯调用链找到业务代码的入口方法二使用 EurekaLog / madExcept这些第三方异常捕获工具可以生成详细的错误报告包含完整的调用栈变量值快照模块版本信息方法三使用 Windows 事件查看器如果程序已在客户环境运行且未安装调试工具可查看text控制面板 - 管理工具 - 事件查看器 - Windows 日志 - 应用程序五、快速定位的代码模式当错误地址为00000000时重点关注以下代码模式1. 对象创建检查pascal// ❌ 错误写法 var frm: TMyForm; begin frm.ShowModal; // 未创建就使用 end; // ✅ 正确写法 var frm: TMyForm; begin frm : TMyForm.Create(Application); try frm.ShowModal; finally frm.Free; end; end;2. 函数返回对象检查pascal// ❌ 错误写法 function GetData: TDataObject; begin // 某些分支没有赋值 Result end; procedure UseData; var data: TDataObject; begin data : GetData; data.DoSomething; // 可能为 nil end; // ✅ 正确写法 procedure UseData; var data: TDataObject; begin data : GetData; if Assigned(data) then data.DoSomething else // 处理空值情况 ShowMessage(数据对象为空); end;3. JSON 解析检查针对本文案例pascal// ❌ 错误写法 strreportType : Copy(ArrayRoot.GetValue(reportType).ToString, 2, Length(ArrayRoot.GetValue(reportType).ToString) - 2); // ✅ 正确写法 var jv: TJSONValue; begin jv : ArrayRoot.GetValue(reportType); if Assigned(jv) then strreportType : jv.ToString // 自行处理首尾引号 else strreportType : ;或者更安全的写法pascalvar s: string; begin s : ArrayRoot.GetValue(reportType).Value; // 使用 Value 属性而非 ToString // GetValue(reportType) 可能返回 nil仍需检查 end;六、完整解决方案步骤Access violation at address 0048B11A in module XXXX.exe. Read of address 00000000地址0048B11A出错指令的代码地址地址00000000试图读取的内存地址空指针Step 2启用调试信息重新编译确保生成带有.map文件或调试符号的版本Step 3使用 TProc 或 TMethod 检查事件赋值检查所有OnClick、OnCreate等事件赋值是否有效Step 4检查 Project 的 Auto-Create Forms 列表Project - Options - Forms确认被调用的窗体在 Auto-Create 列表中或者已在代码中手动创建Step 5检查全局变量全局对象变量可能在不同单元中被提前释放七、预防措施1. 使用 Assigned 判断pascalif Assigned(MyObject) then MyObject.DoSomething;2. 使用 FreeAndNilpascalFreeAndNil(MyObject); // 释放后将指针置为 nil3. 使用 try-except 保护关键代码pascaltry // 可能出错的代码 except on E: Exception do LogError(E.Message); end;4. 初始化对象变量pascalvar MyObject: TMyClass : nil; // 明确初始化为 nil5. JSON 操作规范每次调用GetValue后检查返回值是否Assigned使用TJSONObject的TryGetValue方法6. 统一异常处理pascalApplication.OnException : MyExceptionHandler; procedure MyExceptionHandler(Sender: TObject; E: Exception); begin if E is EAccessViolation then LogError(访问违例 E.Message) else LogError(其他异常 E.Message); end;八、总结检查项操作错误地址00000000→ 空指针访问首要嫌疑对象未创建或已释放调试手段IDE 调试信息 / EurekaLog代码规范使用 Assigned、try-except、FreeAndNil重点区域事件调用、JSON解析、动态数组、窗体创建记住一句话读地址 00000000一定是对象为 nil排查方向就是找到哪里忘记创建或者哪里提前释放了。附录快速排查清单检查出错行调用的对象是否已.Create检查对象是否已被.Free或.Destroy检查 JSON 节点是否存在检查动态数组是否已SetLength检查窗体是否在 Auto-Create 列表中或在代码中创建检查接口指针是否通过工厂方法正确赋值检查 TStringList 等容器是否已创建检查 DataSet 是否已打开且有数据检查事件回调是否已赋值本文档可作为团队内部错误排查手册遇到类似 Read of address 00000000 错误时按图索骥即可快速定位并解决问题。