我刚刚调试了一个函数,该函数返回一个令我担心的字符串。我一直假设返回字符串的函数的隐式Result变量在函数调用开始时将为空,但以下(简化)代码产生了意外结果:
function TMyObject.GenerateInfo: string;
procedure AppendInfo(const AppendStr: string);
begin
if(Result > '') then
Result := Result + #13;
Result := Result + AppendStr;
end;
begin
if(ACondition) then
AppendInfo('Some Text');
end;
多次调用此函数导致:
"Some Text"
第一次,
"Some Text"
"Some Text"
第二次,
"Some Text"
"Some Text"
"Some Text"
第三次,等等。
要修复它,我必须初始化结果:
begin
Result := '';
if(ACondition) then
AppendInfo('Some Text');
end;
是否需要初始化字符串函数结果?为什么(技术上)?为什么编译器不会发出警告“W1035函数的返回值'xxx'可能是未定义的”字符串函数?我是否需要遍历所有代码以确保设置值,因为如果未明确设置结果,则期望函数中的空字符串不可靠?
我在新的测试应用程序中对此进行了测试,结果是相同的。
procedure TForm1.Button1Click(Sender: TObject);
var
i: integer;
S: string;
begin
for i := 1 to 5 do
S := GenerateInfo;
ShowMessage(S); // 5 lines!
end;
答案 0 :(得分:32)
这不是错误,而是“feature”:
对于字符串,动态数组,方法 指针,或变体结果, 效果与相同 函数结果被声明为 其他var参数跟随 声明的参数。换一种说法, 调用者传递额外的32位 指向变量的指针 哪个返回功能结果。
即。你的
function TMyObject.GenerateInfo: string;
真的是这样:
procedure TMyObject.GenerateInfo(var Result: string);
注意“ var ”前缀(不是“ out ”,如您所料!)。
SUCH 不直观,因此会导致代码中出现各种问题。有问题的代码 - 只是此功能结果的一个示例。
查看并投票赞成this request。
答案 1 :(得分:4)
我们之前遇到过这种情况,我想可能早在Delphi 6或7中。是的,即使编译器没有给你一个警告,你也需要初始化你的字符串Result变量,for正是你遇到的原因。字符串变量 正在初始化 - 它不是作为垃圾引用启动的 - 但是当你期望它时,它似乎没有重新初始化。 / p>
至于为什么会发生......不确定。这是一个错误,所以它不一定需要一个理由。我们只看到它在循环中重复调用函数时发生;如果我们在循环外调用它,它按预期工作。看起来调用者正在为Result变量分配空间(并在重复调用相同的函数时重用它,从而导致错误),而不是函数分配它自己的字符串(并在每次调用时分配一个新字符串。)
如果您使用短字符串,则调用者会分配缓冲区 - 这是大值类型的长期行为。但这对AnsiString没有意义。也许编译器团队在Delphi 2中首次实现长字符串时忘记改变语义。
答案 2 :(得分:2)
这不是Bug。根据定义,函数内部没有初始化变量,包括Result。
所以你的结果在第一次通话时是不会发现的,并且可以保留任何东西。如何在编译器中实现它是无关紧要的,并且您可以在不同的编译器中获得不同的结果。
答案 3 :(得分:1)
看起来您的功能应该像这样简化:
function TMyObject.GenerateInfo: string;
begin
if(ACondition) then
Result := 'Some Text'
else
Result := '';
end;
您通常不希望在函数的赋值右侧使用Result。
无论如何,出于说明的目的,您也可以这样做,但不建议:
procedure TForm1.Button1Click(Sender: TObject);
var
i: integer;
S: string;
begin
for i := 1 to 5 do
begin
S := ''; // Clear before you call
S := GenerateInfo;
end;
ShowMessage(S); // 5 lines!
end;
答案 4 :(得分:0)
这看起来像是D2007中的一个错误。我刚刚在Delphi 2010中对它进行了测试并获得了预期的行为。 (1行而不是5行。)
答案 5 :(得分:0)
如果您认为对字符串进行一些自动管理可以让您的生活更轻松,那么您只是部分正确。所有这些事情也都是为了使字符串逻辑一致并且副作用免费。
在很多地方都有通过引用传递的字符串,通过值传递,但所有这些行都期望 VALID 字符串,其内存管理计数器是有效的,而不是垃圾值。因此,为了保持字符串有效,唯一可以肯定的是它们应该在首次引入时进行初始化。例如,对于任何局部变量字符串,这是必需的,因为这是引入字符串的位置。所有其他字符串用法,包括function():string(实际上是Alexander正确指出的过程(var Result:string))只需要有效字符串在堆栈上,而不是初始化 。这里的有效性来自这样一个事实,即(var Result:string)构造说“我正在等待一个有效的变量,该变量在之前被引入”。更新:因为Result的实际内容是意外的,但由于逻辑相同,如果它是对此函数的唯一调用,左侧有一个局部变量,则保证字符串的空白。
答案 6 :(得分:0)
以下,在没有优化的情况下编译,产生sTemp为空字符串的预期结果。如果将该功能交换为程序调用,则会得到不同的结果。
实际的程序单元似乎有不同的规则。
无可否认,这是一个极端情况。
program Project1;
{$APPTYPE CONSOLE}
uses System.SysUtils;
function PointlessFunction: string;
begin
end;
procedure PointlessProcedure(var AString: string);
begin
end;
var
sTemp: string;
begin
sTemp := '1234';
sTemp := PointlessFunction;
//PointlessProcedure(sTemp);
WriteLn('Result:' + sTemp);
ReadLn;
end.