使用指针数学解析字符串的无限循环
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;
我有一个处理类 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;