-1

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処理の最後に P が '' であるため、行が False と評価されないことを示しているため、コードは範囲外の操作を実行しようとします。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;

#0内側の case ステートメントで、ルーチンに供給されているかどうかを確認し"such \ strings"ます。つまり、ソースから読み取られ、1 つずつフォーマットされた断片に分割された文字列のシーケンスです。これまでのところ、これはうまく機能しますが、'\\t'as'\t'および同様の構造を正しく解析できず、 #9. 何か原因が思い浮かびません。ああ、古いバージョンにもこのバグがありました。

4

1 に答える 1

5

あなたのループは永遠に実行されます。これPnil、ポインターの計算の問題ではなく、最初から存在しないためです (ただし、これについては後で詳しく説明します)。 PChar()常にnilポインタを返します。Sが空でない場合はPChar()、最初の へのポインタを返しますが、が空のChar場合は、メモリ内の null ターミネータへのポインタを返します。あなたのコードは後者の可能性を考慮していません。SPChar()const

Snull で終わる C 文字列として処理したい場合は(代わりに完全なLength()ofSを考慮に入れてみませんか?)、while P^ <> #0 do代わりに. を使用する必要がありwhile P <> nil doます。

それはさておき:

  • PPCharの代わりにとして宣言する必要がありMarshaledStringます。MarshaledStringこの状況またはこの方法で使用する理由はありません。

  • に単一をTStringBuilder.Append(Char)渡す場合に使用すると、より効率的です。実際、何も役に立たないので、完全に取り除くことをお勧めします。CharDoIt()DoIt()

  • なんで'\'#0改行扱いなの?\入力文字列の末尾にある文字を考慮するには? そのような状況に遭遇した場合P、null ターミネーターを超えてインクリメントしていて、周囲のメモリを読み取っているため、未定義の領域にいます。または、入力文字列に実際に文字が埋め込まれており#0、最後に null ターミネータが含まれていますか? これは、テキスト データとしては珍しい形式です。

次のようなことを試してください (実際に#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;
于 2015-06-10T04:32:02.363 に答える