使用指针数学解析字符串的无限循环

Infinite loop in parsing a string using pointer math

我有一个处理类 C 字符串的例程,结果是通常的 Delphi 字符串:

class function UTIL.ProcessString(const S: string): string;
var
  SB:TStringBuilder;
  P:MarshaledString;
  procedure DoIt(const S:string;const I:Integer=2);
  begin
  SB.Append(S);
  Inc(P,I);
  end;
begin
SB:=TStringBuilder.Create;
P:=PChar(S);
while P<>nil do
  begin
  if P^<>'\' then DoIt(P^,1) else
    case (P+1)^ of
    '\','"':DoIt((P+1)^);
    #0,'n':DoIt(sLineBreak);
    't':DoIt(#9);
    else DoIt('\'+(P+1)^,2);
    end;
  end;
Result:=SB.ToString;
SB.Free;
end;

问题是循环永远不会退出。调试显示行 while P<>nil do 未评估为 False,因为 P 在处理结束时为 '',因此代码尝试对其执行超出范围的操作。因为我没有在 Delphi 中找到任何关于指针数学的简明文档,所以很可能我在这里有问题。

编辑:我重写了这个函数,所有内容都像这样:

class function UTIL.ProcessString(const S: string): string;
var
  SB:TStringBuilder;
  P:PChar;
  C:Char;
begin
SB:=TStringBuilder.Create;
P:=PChar(S);
  repeat
  C:=P^;
  Inc(P);
    case C of
    #0:;
    '\':
      begin
      C:=P^;
      Inc(P);
        case C of
        #0,'n':SB.Append(sLineBreak);
        '\','"':SB.Append(C);
        't':SB.Append(#9);
        else SB.Append('\').Append(C);
        end;
      end;
    else SB.Append(C);
    end;
  until P^=#0;
Result:=SB.ToString;
SB.Free;
end;

我在内部 case 语句中检查 #0 是否将 "such \ strings" 送入例程,i。 e.从源中读取的一系列字符串,分成多个部分,然后一个一个地格式化。到目前为止这很好用,但是它无法正确地将 '\t' 解析为 '\t' 和类似的结构,它 returns 只是 #9。我真的想不出任何原因。哦,旧版本也有这个错误顺便说一句。

你的循环永远运行,因为 P 永远不会是 nil 开始,而不是因为你的指针数学有问题(尽管我将在下面进一步讨论)。 PChar() 总是 return 一个非 nil 指针。如果 S 不为空,PChar() return 是指向第一个 Char 的指针,但如果 S 为空,则 PChar()return 指向 const 内存 中的空终止符的指针。您的代码没有考虑后一种可能性。

如果要将 S 作为空终止的 C 字符串处理(为什么不考虑 S 的完整 Length() 呢?),那么您需要使用while P^ <> #0 do 而不是 while P <> nil do.

除此之外:

  • P 应声明为 PChar 而不是 MarshaledString。没有理由在这种情况下或这种方式下使用 MarshaledString

  • 在将单个 Char 传递给 DoIt() 的情况下,使用 TStringBuilder.Append(Char) 会更有效。事实上,我建议完全摆脱 DoIt(),因为它并没有给你带来任何有用的东西。

  • 为什么要将 '\'#0 视为换行符?要考虑输入字符串末尾的 \ 字符?如果您遇到这种情况,您将递增 P 超过空终止符,然后您将处于未定义的区域,因为您正在读取周围的内存。或者您的输入字符串是否真的嵌入了 #0 个字符,然后是最后一个空终止符?这对于文本数据来说是不常见的格式。

尝试更像这样的东西(如果真的有嵌入的 #0 个字符):

class function UTIL.ProcessString(const S: string): string;
var
  SB: TStringBuilder;
  P: PChar;
begin
  Result := '';
  P := PChar(S);
  if P^ = #0 then Exit;
  SB := TStringBuilder.Create;
  try
    repeat
      if P^ <> '\' then
      begin
        SB.Append(P^);
        Inc(P);
      end else
      begin
        Inc(P);
        case P^ of
          '\','"': SB.Append(P^);
          #0, 'n': SB.Append(sLineBreak);
          't':     SB.Append(#9);
          else     SB.Append('\'+P^);
        end;
        Inc(P);
      end;
    until P^ = #0;
    Result := SB.ToString;
  finally
    SB.Free;
  end;
end;

或者这个(如果没有嵌入 #0 个字符):

class function UTIL.ProcessString(const S: string): string;
var
  SB: TStringBuilder;
  P: PChar;
  Ch: Char;
begin
  Result := '';
  P := PChar(S);
  if P^ = #0 then Exit;
  SB := TStringBuilder.Create;
  try
    repeat
      Ch := P^;
      Inc(P);
      if Ch <> '\' then
        SB.Append(Ch)
      else
      begin
        Ch := P^;
        if Ch = #0 then
        begin
          // up to you if you really need this or not:
          // SB.Append(sLineBreak);
          Break;
        end;
        Inc(P);
        case Ch of
          '\','"': SB.Append(Ch);
          'n':     SB.Append(sLineBreak);
          't':     SB.Append(#9);
          else     SB.Append('\'+Ch);
        end;
      end;
    until P^ = #0;
    Result := SB.ToString;
  finally
    SB.Free;
  end;
end;