如何将动态数组作为未类型化的常量传递?

3

我正在调用一个函数,该函数需要:

  • 未指定类型的 const
  • 字节长度

这是 Delphi 中常见的一种模式。例如:

procedure DoStuff(const Data; DataLen: Integer);

在这个测试用例中,DoStuff 的作用是确保它接收到四个字节。
0x21 0x43  0x65  0x87

为了测试目的,我们将在我们的小端Intel机器上将该字节序列解释为一个32位无符号整数:0x87654321。这使完整的测试函数如下:

procedure DoStuff(const Data; DataLen: Integer);
var
    pba: PByteArray;
    plw: PLongWord;
begin
    //Interpret data as Longword (unsigned 32-bit)
    plw := PLongWord(@Data);
    if plw^ <> $87654321 then
        raise Exception.Create('Fail');

    //Interpret data as array of bytes
    pba := PByteArray(@Data);
    if (pba[0] <> $21) or (pba[1] <> $43) or (pba[2] <> $65) or (pba[3] <> $87) then
        raise Exception.Create('Fail');

//  ShowMessage('Success');
end;

为了解释的目的,错误检查已经被阐明。

测试

我可以开始测试,确保我能正确地将字节传递给我的DoStuff函数。

//Pass four bytes in a LongWord
var lw: LongWord;
lw := $87654321;
DoStuff(lw, 4); //works

因此,将一个LongWord传递给一个接受未命名const的函数的方法就是直接传递变量。

  • 错误@lw
  • 错误Addr(lw)
  • 不好的Pointer(@lw)^
  • 正确的lw

我还可以传递一个8字节的类型;由于我只读取前四个字节:

//Pass four bytes in QuadWord
var qw: Int64;
qw := $7FFFFFFF87654321;
DoStuff(qw, 4); //works

数组

现在我们来看一些稍微复杂的东西:如何传递数组:

//Pass four bytes in an array
var data: array[0..3] of Byte;
data[0] := $21;
data[1] := $43;
data[2] := $65;
data[3] := $87;
DoStuff(data[0], 4); //Works

//Pass four bytes in a dynamic array
var moreData: TBytes;
SetLength(moreData, 4);
moreData[0] := $21;
moreData[1] := $43;
moreData[2] := $65;
moreData[3] := $87;
DoStuff(moreData[0], 4); //works

这两个都可以正常工作。但在某些情况下可能会更加棘手,请看下面的一些例子:

//Pass four bytes at some point in an array
var data: array[0..5] of Byte;
data[2] := $21;
data[3] := $43;
data[4] := $65;
data[5] := $87;
DoStuff(data[2], 4); //Works

//Pass four bytes at some point in a dynamic array
var moreData: TBytes;
SetLength(moreData, 6);
moreData[2] := $21;
moreData[3] := $43;
moreData[4] := $65;
moreData[5] := $87;
DoStuff(moreData[2], 4); //works

因为 untyped const 操作符隐式地通过引用传递,所以我们是通过引用传递数组中第三个字节(并且没有浪费的临时数组复制)。
从这里我可以推断出规则:如果想向未定类型的 const 传递一个数组,需要传递一个索引数组。
DoStuff(data[n], ...);

两个问题

第一个问题是如何将一个空的动态数组传递给一个未指定类型的常量函数。例如:

var
   data: TBytes;
begin
   data := GetData;
   DoStuff(data[0], Length(0));

如果data为空(由于范围检查错误),则此通用代码会失败。

另一个问题是一些人对传递 data[0]的语法提出了批评,他们认为应该直接使用data

//Pass four bytes in an array without using the array index notation
data[0] := $21;
data[1] := $43;
data[2] := $65;
data[3] := $87;
DoStuff(data, 4); //works

这是可行的。这意味着我失去了之前的能力:传递索引数组。但一个真正的问题是,当与动态数组一起使用时它会失败:

//Pass four bytes in a dynamic array without using the array index notation
SetLength(moreData, 4);
moreData[0] := $21;
moreData[1] := $43;
moreData[2] := $65;
moreData[3] := $87;
DoStuff(moreData, 4); //FAILS

问题在于动态数组的内部实现细节。动态数组实际上是一个指针,而数组则是一个真正的数组。
这是否意味着如果我传递一个数组,我必须弄清楚它是哪种类型,并使用不同的解决语法?
//real array
DoStuff(data, 4); //works for real array
DoStuff(data, 4); //fails for dynamic array
DoStuff(Pointer(data)^, 4); //works for dynamic array

我真的应该这样做吗?难道没有更正确的方法吗?

额外解决办法

因为我不想失去对数组进行索引的能力:

DoStuff(data[67], 4);

我可以保留索引符号:

DoStuff(data[0], 4);

确保处理好边缘情况:

if Length(data) > 0 then
   DoStuff(data[0], 4)
else
   DoStuff(data, 0); //in this case data is dummy variable

所有这一切的目的都是为了不在内存中复制数据,而是通过引用传递它。


对于动态数组:DoStuff(Pointer(data)^,Min(4,Length(data))); - LU RD
@DavidHeffernan 很好地发现了我发布时漏掉的最后一个“expected”值,感谢指正。 - Ian Boyd
1个回答

2

这其实很简单。

DoStuff(data, ...);           // for a fixed length array
DoStuff(Pointer(data)^, ...); // for a dynamic array

这是正确的方法。动态数组是指向第一个元素的指针,如果数组为空,则为nil

一旦放弃类型安全并使用未类型化的参数,就只能期望在调用此类函数时会有更多的摩擦。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接