delphi中的堆栈溢出错误

Tim*_*ram 5 stack-overflow delphi

我有一个调用几个函数的过程:

procedure TForm1.Button1Click(Sender: TObject);
var
  rawData: TRawData;
  rawInts: TRawInts;
  processedData: TProcessedData;
begin
  rawData := getRawData();
  rawInts := getRawInts(rawData);
  processedData := getProcessedData(rawInts);
end;
Run Code Online (Sandbox Code Playgroud)

数据类型定义如下:

TRawData = array[0..131069] of Byte;
TRawInts = array[0..65534] of LongInt;
TProcessedData = array[0..65534] of Double;
Run Code Online (Sandbox Code Playgroud)

只运行程序:

rawData := getRawData();
rawInts := getRawInts(rawData);
Run Code Online (Sandbox Code Playgroud)

工作完全没问题.但是,当我尝试运行时:

getProcessedData(rawInts)
Run Code Online (Sandbox Code Playgroud)

我收到stackoverflow错误.我不明白为什么会这样.功能代码getProcessedData非常简单:

function getProcessedData(rawInts : TRawInts) : TProcessedData;
var
  i: Integer;
  tempData: TProcessedData;
  scaleFactor: Double;
begin
  scaleFactor := 0.01;

  for i := 0 to 65534 do
    tempData[i] := rawInts[i] * scaleFactor;

  Result := tempData;
end;
Run Code Online (Sandbox Code Playgroud)

为什么会导致错误?

Rob*_*edy 13

线程的默认最大堆栈大小为1 MB.三个局部变量Button1Click共131,070 + 65,535*4 + 65,535*8 = 917,490字节.调用时getProcessedData,通过value传递参数,这意味着该函数在堆栈上生成参数的本地副本.这增加了SizeOf(TRawInts)= 262,140字节,使堆栈至少达到1,179,630字节,或大约1.1 MB.你的堆栈溢出了.

您可以TRawInts通过引用传递数组来减少堆栈使用.然后该函数将不会自己复制.Zdravko的回答建议使用var,但由于函数不需要修改传入的数组,所以应该使用const.

function getProcessedData(const rawInts: TRawInts): TProcessedData;
Run Code Online (Sandbox Code Playgroud)

天真地,我们可能期望tempDataResult变量getProcessedData占用额外的堆栈空间,但实际上,它们可能不会.首先,大型返回类型通常会导致编译器更改函数签名,因此它更像是使用var参数而不是返回值声明函数:

procedure getProcessedData(rawInts: TRawInts; var Result: TProcessedData);
Run Code Online (Sandbox Code Playgroud)

然后相应地转换呼叫:

getProcessedData(rawInts, processedData);
Run Code Online (Sandbox Code Playgroud)

因此,Result不会占用更多的堆栈空间,因为它实际上只是调用者帧中变量的别名.

此外,有时编译器会识别函数末尾的赋值,例如Result := tempData,意味着它tempData不需要任何自己的空间.相反,编译器可能会将您的函数看作是您一直直接编写的Result:

begin
  scaleFactor := 0.01;

  for i := 0 to 65534 do
    Result[i] := rawInts[i] * scaleFactor;
end;
Run Code Online (Sandbox Code Playgroud)

但是,最好不要指望编译器进行那些节省内存的更改.相反,最好不要首先在堆栈上如此重视.为此,您可以使用动态数组.这些将把大量内存从堆栈中移出并进入,这是用于动态分配的内存的一部分.首先更改数组类型的定义:

type
  TRawData = array of Byte;
  TRawInts = array of Integer;
  TProcessedData = array of Double;
Run Code Online (Sandbox Code Playgroud)

然后,在返回这些类型的函数中,使用SetLength指定每个数组的长度.例如,我们已经看到的功能可能是这样的:

function getProcessedData(const rawInts: TRawInts): TProcessedData;
var
  i: Integer;
  scaleFactor: Double;
begin
  Assert(Length(rawInts) = 65535);
  SetLength(Result, Length(rawInts));

  scaleFactor := 0.01;

  for i := 0 to High(rawInts) do
    Result[i] := rawInts[i] * scaleFactor;
end;
Run Code Online (Sandbox Code Playgroud)