string:=const:为什么本地和结果的实现不同?
在Delphi函数中,结果经常作为var参数实现(尽管有QC票据,但不是out参数) 字符串常量基本上是带有负refcounter的变量,这将抑制自动内存[de]分配 它确实抑制了它:下面的代码不会泄漏string:=const:为什么本地和结果的实现不同?,string,delphi,delphi-xe2,refcounting,String,Delphi,Delphi Xe2,Refcounting,在Delphi函数中,结果经常作为var参数实现(尽管有QC票据,但不是out参数) 字符串常量基本上是带有负refcounter的变量,这将抑制自动内存[de]分配 它确实抑制了它:下面的代码不会泄漏 type TDealRecord = record id_Type: Integer; Price: extended; Remark: String; end; const const_loop = 100000000; function TestVar: T
type
TDealRecord = record
id_Type: Integer;
Price: extended;
Remark: String;
end;
const const_loop = 100000000;
function TestVar: TDealRecord;
//procedure TestVar;
var
Li: Integer;
LRec: TDealRecord;
begin
for Li := 1 to const_loop do begin
FillChar(Lrec,SizeOf(LRec), 0);
LRec.Remark := 'Test';
// FillChar(Result,SizeOf(Result), 0);
// Result.Remark := 'Test';
end;
end;
但是改变被操纵的变量,它立刻开始严重泄漏
function TestVar: TDealRecord;
//procedure TestVar;
var
Li: Integer;
LRec: TDealRecord;
begin
for Li := 1 to const_loop do begin
// FillChar(Lrec,SizeOf(LRec), 0);
// LRec.Remark := 'Test';
FillChar(Result,SizeOf(Result), 0);
Result.Remark := 'Test';
end;
end;
事实证明,string:=const
是通过不同的调用实现的,具体取决于左值:
UniqueString
调用一样
为什么会有这种差异?在与David Heffernan讨论之后,我开始认为Delphi编译器不知道它为变量指定的值是什么。一种有位置的“类型擦除”。它无法区分全局常量、局部堆栈变量和局部字符串表达式。它无法判断函数退出后源是否存在。虽然我们知道这是字符串文字、全局常量或任何生命周期与函数执行无关的东西,但编译器只是丢失了这些信息。相反,它扮演着防御角色,总是克隆价值观——只是为了有可能它将不复存在。我不确定,但这看起来很合理。尽管这种粗略的、不分青红皂白的codegen规则的结果在Delphi中又是一个难题:(在Delphi中,常量字符串总是在分配给另一个全局变量而不是局部变量时被复制,以避免在某些边界情况下发生访问冲突 使用来源,卢克 请参阅以下代码从System.pas中提取:
{99.03.11
分配给全局变量时使用此函数。
复制文字是为了防止出现以下情况:
分配的DLL或包为变量分配一个文本,然后
卸载—从而导致字符串内存(在代码中
要删除的DLL段,因此保留
指向无效内存的全局变量。
}
程序_LStrAsg(var dest;const source);
变量
S、 D:指针;
P:PStrRec;
温度:长;
开始
S:=指针(源);
如果是零那么
开始
P:=PStrRec(整数-sizeof(StrRec));
如果P.refCnt<0,则//复制字符串文字
开始
温度:=P.长度;
S:=\u新的翻译(临时);
移动(指针(源)^,S^,临时);
P:=PStrRec(整数-sizeof(StrRec));
结束;
联锁增量(P.refCnt);
结束;
....
因此,简而言之,根据设计,为了避免在卸载DLL或包时发生访问冲突,并且确实包含一些发送回主进程的常量值,始终创建本地副本
您有两个功能:
或LStrAsg
,当字符串有可能成为常量时由编译器生成-这是上面的代码UStrAsg
或LStrLAsg
(添加的ustrasg
代表“local”)由编译器在源字符串为local时生成,因此没有常量:在这种情况下,L
将不被检查,因此它将比上面的代码快P.refCnt<0
结果
变量时,您是在赋值给该函数外部可见的变量。当您赋值给局部时,它是该函数的专用变量。@David,是的,是的,但“那又怎样?”!为什么禁止将外部变量指向“-1”常量?如果需要,它将根据需要进行分离。在函数中主动这样做看起来是过早的优化,并且在“常识”下引入了完全不同的行为不会有什么区别。我只能认为编译器无法跟踪它从中分配的内容。它认为它分配的不是全局常量,而是堆栈上的一个内部表达式,这将丢失。那么立即克隆是有意义的。但这似乎太粗糙,太“全局”规则是它相当低效。Arnaud-看看_ustrasg和_LStrLAsg。它们也用于将常量复制到字符串。它们不会克隆它们。虽然有时可能会使用您的_LStrAsg,但有时Delphi会使用另一个具有不同行为的函数。为什么对看起来基本相同的操作进行拆分,这是问题的核心question.DLL/BPL卸载是公平的,但与const问题无关。BPL局部常量将与其BPL一起卸载。因此,感谢您的提示,但您的答案是不正确的。我实际上展示了以下代码(至少在XE2中)subversits ustrasg…当源字符串是本地字符串时,因此没有常量概念-它实际上是为字符串常量调用的,无论是命名的还是文字的。更重要的是,想象这样一个例程:procedure XXX;var s:string;h:THandle;begin h:=LoadPackage('PK1.BPL');s:=PK1.Unit1.Const1;UnloadPackage(h);ShowMessage(s);end;
这一次DLL将被卸载,指向const的字符串指针将变为游离指针。我不认为Delphi会检测到这一点,并通过自动克隆字符串来保护我。P.refCnt<0不会被检查
实际上也是不正确的。它将始终被检查,但如果它是负数,那么InterlockedIncrement
将d被跳过,这实际上会增加多核环境中的速度。简历:你引用的是Delphi RTL源代码还是FreePascal源代码?如果是Delphi,那么是哪一个版本?看看其他Pascal编译器似乎很有趣,但不是Delphi。有任何事实性的评论吗?看起来我有一个个人仇恨粉丝:-DSorry,我
{ 99.03.11
This function is used when assigning to global variables.
Literals are copied to prevent a situation where a dynamically
allocated DLL or package assigns a literal to a variable and then
is unloaded -- thereby causing the string memory (in the code
segment of the DLL) to be removed -- and therefore leaving the
global variable pointing to invalid memory.
}
procedure _LStrAsg(var dest; const source);
var
S, D: Pointer;
P: PStrRec;
Temp: Longint;
begin
S := Pointer(source);
if S <> nil then
begin
P := PStrRec(Integer(S) - sizeof(StrRec));
if P.refCnt < 0 then // make copy of string literal
begin
Temp := P.length;
S := _NewAnsiString(Temp);
Move(Pointer(source)^, S^, Temp);
P := PStrRec(Integer(S) - sizeof(StrRec));
end;
InterlockedIncrement(P.refCnt);
end;
....