【问题标题】:Error Delphi XE2 - Exception class $C00000005错误 Delphi XE2 - 异常类 $C00000005
【发布时间】:2014-01-24 20:45:30
【问题描述】:

我在调试一个项目时遇到此错误,该项目曾经是在 Delphi 7 中,我一直在升级到 Delphi XE2,同样的错误发生在几个方法中。

First chance exception at $006DC660. Exception class $C0000005 with message 'access violation at 0x006dc660 read of address 0xffffffff' 

这是其中一种方法:

PFI = ^TFI;           
TFI = record
Id         : TToken;
Name       : TName;
Parameters : string;
end;

function TListFI.IsIn(S: PChar): PFI;

  function SearchName2(Item: PFI):Boolean;
  var N1, N2: PChar;
  begin
    N1:= StrNew(Item^.Name);
    N2:= StrNew(S); //Here is the issue
    SearchName2:= (StrComp(StrUpper(N1), StrUpper(N2)) = 0);
    StrDispose(N1);
    StrDispose(N2);
  end;

begin
  IsIn:= PFI(FirstThat(@SearchName2));
end;

我用谷歌搜索过,我发现有人描述了类似的问题,他确认当增量链接器被禁用时它可以工作,有人可以告诉我它是什么和在哪里,或者提供一些解决这种情况的建议。

[编辑]

现在删除 @ 会给我在 IsIn:= PFI(FirstThat(SearchName2)); 中出现以下错误

E2010 Incompatible types: 'TObject' and 'PFI'

我正在添加 FirstThat 程序,看看它是否有帮助。

TFuncionColeccion = function (Elemento: TObject): Boolean;

function TColeccion.FirstThat (Rutina: TFuncionColeccion): TObject;
var
  i: Integer;
begin
  For i:=0 to Count-1 do
    if Rutina(Items[i]) then
    begin
      FirstThat:=Items[i];
      exit;
    end;
  FirstThat:=nil;
end;

【问题讨论】:

  • 是什么促使您使用StrNew?我将采取的第一步是将其从我的代码库中删除。
  • 我收到了这个项目,它已经在几个学生的手中(我也是一个)。什么可以使用 insted 以及为什么要删除它?我会添加地址。
  • 空终止字符串的堆分配需要仔细的内存管理。您的代码错过了必要的 try/finally 循环以防止泄漏。在今天的 Delphi 中,您将使用 string。你可以通过简单的赋值将PAnsiCharPWideChar直接转换为string
  • @sandiego - 对我来说,指针和托管类型在同一个记录声明中的混合表明作者并不真正知道他们在做什么。如果我是你,我会考虑重构代码并完全删除显式指针使用 - StrNew 和朋友是 1996 年在 Delphi 2 中引入的堆管理 string 类型的相当糟糕的前身。哪里有仍然有理由在适当的时候使用 PChar 类型,其中不会是当您只想对两个字符串进行不区分大小写的比较时。
  • 我还在为这个问题苦苦挣扎,问题已编辑,但如果不是问题,谁能解释我为什么原始功能在 Delphi 7 上工作,但在 Delphi XE2 中我遇到了这个问题?跨度>

标签: delphi exception delphi-xe2 access-violation


【解决方案1】:

通过指针调用本地(嵌套)过程是(并且一直是)错误,这显然是您的FirstThat 函数所做的。编译器必须对堆栈做一些特殊的事情来调用局部函数并让它们访问父作用域的变量(代码中的S),但是编译器只有在直接调用局部函数时才能知道做那些特殊的事情.编译器无法知道FirstThat 的参数将是一个本地函数,因此当FirstThat 调用指向函数时它不包含特殊代码。

最重要的是,函数内部的堆栈没有按照应有的方式设置,这意味着可能会出现任何数量的奇怪症状。您将不得不使用其他方式。也许让SearchName2成为一个有两个参数的函数,然后写FirstThat接受S作为一个参数,它可以转发给函数参数。

在构造函数指针时,您不需要使用@ 运算符。当您这样做时,编译器往往会跳过类型检查,这首先允许您将本地函数指针传递给 FirstThat。当您传递的函数真正匹配所需的原型时,编译器将允许您在没有@ 运算符的情况下传递它。

【讨论】:

    【解决方案2】:

    您正在报告访问冲突

    StrNew(S)
    

    其中S 的类型为PChar。对此的解释(概率非常接近 1)是 S 实际上不是指向以空结尾的 WideChar 数组的指针。

    在 Delphi 7 中,PCharPAnsiChar 的别名。那是指向以空结尾的AnsiChar 数组的指针,即 8 位字符。在 Delphi XE2 中,PCharPWideChar 的别名,WideChar 的空终止数组的指针,即 16 位字符。

    这有助于理解StrNew 的作用。它遍历数组,直到找到一个空字符。对于单个零字节的 8 位文本。对于 16 位文本,null 是 16 位零字。然后它分配一个与输入字符串长度相同的新内存块,并将副本复制到该新内存中。源代码为:

    function StrNew(const Str: PWideChar): PWideChar;
    var
      Size: Cardinal;
    begin
      if Str = nil then Result := nil else
      begin
        Size := StrLen(Str) + 1;
        Result := StrMove(WideStrAlloc(Size), Str, Size);
      end;
    end;
    

    唯一可能的故障模式是当StrLen 遍历数组时,它会尝试读取无效的内存。只有当您的输入参数无效时才会发生这种情况。换句话说,这一定是您的编程错误。

    一种可能的解释是,尽管您承诺传递 16 位文本,但实际上您正在向该函数传递 8 位文本。一个容易犯的错误,特别是如果您还不完全熟悉 Unicode 更改。 8 位文本有一个零终止符,但后面的字节恰好不是零。或者零字节落在从开始的奇数偏移处。然后StrNew 继续遍历缓冲区,但现在它已经结束了,碰巧它在溢出到尚未分配的地址之前找不到零字。这是访问冲突。

    如果是这样,那么解决方案将是:

    • 将函数的参数更改为PAnsiChar 类型,并修复调用站点的可疑转换。
    • 根据需要传递函数 16 位文本。

    在您的更新中包含无法读取的地址0xffffffff。这是十六进制的-1。这似乎是最平淡无奇的错误。你的指针完全是假的!您可以使用以下代码复制您的确切错误消息:StrNew(PChar(-1))

    我没有足够的信息来告诉你为什么你的指针是假的。希望您已经学习了一些调试和诊断技术,可以帮助您解决问题。至少您现在知道错误在您的代码中。

    假设 BuscaName2 和 SearchName2 是同一个东西,那么你就不需要再看了。本地过程只能从包含函数中调用。正如@Rob 所说,在过程中使用@ 几乎总是不正确的,并且是代码存在严重问题的警告信号。

    【讨论】:

    • 嗨。 “异常类 $C0000005”到底是什么?这个具体数字是多少?
    • @server 这是访问冲突的 NT 状态码
    猜你喜欢
    • 1970-01-01
    • 2013-06-30
    • 2012-09-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-09-18
    • 2011-11-20
    • 1970-01-01
    相关资源
    最近更新 更多