Delphi 通过指针修改字符串变量的内容是否安全?
假设我有一个通过引用传递Delphi 通过指针修改字符串变量的内容是否安全?,delphi,Delphi,假设我有一个通过引用传递Str参数的过程,我想通过该过程修改给定变量的内容,例如 procedure Replace(var Str: string); var PStr: PChar; i: Integer; begin PStr := @Str[1]; for i := 1 to Length(Str) do begin PStr^ := 'x'; Inc(PStr); end; end; 这是一种可接受的指针用法吗?我不确定它是否有内存泄漏 在PStr
Str
参数的过程,我想通过该过程修改给定变量的内容,例如
procedure Replace(var Str: string);
var
PStr: PChar;
i: Integer;
begin
PStr := @Str[1];
for i := 1 to Length(Str) do begin
PStr^ := 'x';
Inc(PStr);
end;
end;
这是一种可接受的指针用法吗?我不确定它是否有内存泄漏
在PStr:=@Str[1]
中到底发生了什么,编译器是在内部复制Str
还是什么
这种代码优化值得吗?如果通过引用传递
Str
,为什么需要另一个指向字符串的指针?除此之外,应该没有内存泄漏:PStr
使用字符串第一个元素的地址初始化,然后递增,因此它将始终指向字符串中的一个字符
编译不会在内部复制Str
。指针的用途之一是避免复制。当你说
PStr := @Str[1]
是的,
PStr
现在将存储Str[1]
的地址,即字符串中第一个字符的地址。是的,只要不超出字符串的边界,它是安全的。该字符串附加了元数据,可以告诉它的长度,如果您写入的长度超过了该字符串的长度,则不会泄漏内存,但可能会损坏它。我确信这适用于AnsiString和PAnsiChar,但在Delphi 2009及更高版本中是否仍适用于unicode字符串?我认为应该是这样,因为字符串的字符(str[I])和PChar指向的字符的大小都应该是2字节
请有更多unicode字符串经验的人确认这一点好吗
这是一种可接受的指针用法吗
你需要确保不要打电话
PStr := @Str[1];
对于空字符串,因为这样会崩溃。最简单的方法是将该行替换为
PStr := PChar(Str);
因此,编译器将确保返回指向字符串第一个字符的指针或指向#0
的指针。正如Ken在一篇评论中正确指出的那样,在这种情况下没有对UniqueString()
的调用,因此您需要自己完成
我不确定它是否有内存泄漏
不,没有内存泄漏。获取字符串的指针将在内部调用UniqueString()
,但对字符串的写访问也会发生这种情况,因此字符指针没有什么特殊之处
PStr:=@Str[1]中到底发生了什么,编译器是在内部复制Str还是什么
不,它只是确保字符串是唯一的(这样通过指针的写访问不会更改共享相同数据的任何其他字符串的内容)。之后,它返回一个指向字符串中该字符的指针,然后您可以将其视为任何其他PChar
变量,将其传递给API函数,并对其进行递增,依此类推
这种代码优化值得吗
这不仅是值得的,而且对于大型字符串来说,真正实现良好的性能也是必要的。这是因为编译器不够聪明,不能只调用一次
UniqueString()
,但它会为字符串中某个字符的每次写访问插入对它的调用。因此,如果您逐个字符地处理一个大字符串,那么所有这些调用都会带来很大的开销。就像D2010中一样,codegen在这种结构上使用了写时复制
Unit9.pas.34: S := 'abcd';
004B32EF 8D45F4 lea eax,[ebp-$0c]
004B32F2 BA98334B00 mov edx,$004b3398
004B32F7 E89C35F5FF call @UStrLAsg
Unit9.pas.35: P := @S[1];
004B32FC 8D45F4 lea eax,[ebp-$0c]
004B32FF E8343FF5FF call @UniqueStringU ; <== here you are
004B3304 8945F0 mov [ebp-$10],eax
Unit9.pas.36: Exit;
004B3307 EB61 jmp $004b336a
Unit9.pas.34:S:='abcd';
004B32EF 8D45F4 lea eax,[ebp-$0c]
004B32F2 BA98334B00 mov edx,$004b3398
004B32F7 E89C35F5FF呼叫@USTRASG
单元9.pas.35:P:=@S[1];
004B32FC 8D45F4 lea eax,[ebp-$0c]
004B32FF E8343FF5FF调用@UniqueStringU;你确定编译器没有复制吗?我见过它插入对UniqueString
的调用,但我不知道它这样做时的规则是什么。肯定是为了通过括号运算符写入字符串,但也可能是为了通过这种方式获取指向字符的指针。即使编译器确实复制了一个副本,这也不是内存泄漏;新副本存储在Str
@Largerbaer中,我尝试使用指针来加快其执行速度,而不是Str[I]:='x'
。当代码在循环中时,我会考虑速度。@Astaroth,执行肯定不会快很多。它可能会快一点,但这是你可以通过测量自己轻松确定的。这就是您确定优化是否值得的方式。您可以通过暂时禁用范围检查来获得相同的改进。@Rob,非常感谢您的注释和范围检查提示。@Rob Kennedy:“当然可以”?在问题中这样一个简单的循环中使用PChar
而不是索引字符串访问只占用我的系统大约13%的时间(10 MB字符串为5.1毫秒,而不是39毫秒)。因此,对于任何性能敏感的非平凡代码(即不能被StringOfChar()
替换),使用PChar
可能是一种值得优化的方法。它删除了对UniqueString()
的所有调用,只有一个调用除外。它与范围检查没有任何关系。您的强制转换是不可接受的,当一个空字符串传递给您的过程时,它将崩溃。改为使用PStr:=PChar(Str)
,它将返回指向#0
的PChar,而不是崩溃。@mghie,谢谢你的建议,但是对于空字符串的情况,我可以在它前面添加一个先发制人的检查,例如,如果Str='',则退出
编写函数的另一种方法是Str:=StringOfChar('x',Length(Str))
@Astaroth:是的,如果您喜欢自己编写编译器免费提供的代码。请注意,您实际上重复了一个循环无论如何都会执行的检查。@Astaroth:您的解决方案听起来像是过早的优化;仅当速度是一个问题时才进行优化。如果这不是一个问题,那么就使用罗伯·肯尼迪的建议。向PCha抛出一条线索