如何将动态数组作为无类型常量传递?
How to pass dynamic array as untyped const?
我调用的函数需要:
- 无类型常量
- 字节长度
这是一种常见的 Delphi 模式。例如:
procedure DoStuff(const Data; DataLen: Integer);
在此示例测试用例中,所有 DoStuff
都会确保它接收到四个字节:
0x21 0x43 0x65 0x87
出于测试目的,我们将在我们的 little-endian 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^ <> 654321 then
raise Exception.Create('Fail');
//Interpret data as array of bytes
pba := PByteArray(@Data);
if (pba[0] <> ) or (pba[1] <> ) or (pba[2] <> ) or (pba[3] <> ) then
raise Exception.Create('Fail');
// ShowMessage('Success');
end;
为了说明目的,错误检查已经被阐明。
测试
我可以开始测试我是否可以正确地将字节传递给我的 DoStuff
函数。
//Pass four bytes in a LongWord
var lw: LongWord;
lw := 654321;
DoStuff(lw, 4); //works
因此,将 LongWord
传递给采用未类型化 const 的函数的方法就是传递变量。即:
- 错误:
@lw
- 错误:
Addr(lw)
- 不好:
Pointer(@lw)^
- 正确:
lw
我也可以传一个8字节的类型;因为我只读了前四个字节:
//Pass four bytes in QuadWord
var qw: Int64;
qw := FFFFFFF87654321;
DoStuff(qw, 4); //works
数组
现在我们得到了一些更棘手的东西:传递数组:
//Pass four bytes in an array
var data: array[0..3] of Byte;
data[0] := ;
data[1] := ;
data[2] := ;
data[3] := ;
DoStuff(data[0], 4); //Works
//Pass four bytes in a dynamic array
var moreData: TBytes;
SetLength(moreData, 4);
moreData[0] := ;
moreData[1] := ;
moreData[2] := ;
moreData[3] := ;
DoStuff(moreData[0], 4); //works
这些都可以正常工作。但在你们有些人犹豫之前,让我们看看更棘手的案例:
//Pass four bytes at some point in an array
var data: array[0..5] of Byte;
data[2] := ;
data[3] := ;
data[4] := ;
data[5] := ;
DoStuff(data[2], 4); //Works
//Pass four bytes at some point in a dynamic array
var moreData: TBytes;
SetLength(moreData, 6);
moreData[2] := ;
moreData[3] := ;
moreData[4] := ;
moreData[5] := ;
DoStuff(moreData[2], 4); //works
因为 untyped const
运算符通过引用隐式传递,我们传递的是从数组中的第 3 个字节开始的引用(并且我们没有浪费的临时数组副本)。
从这里我可以推断出规则,如果我想将一个数组传递给一个无类型的 const 你传递一个索引数组:
DoStuff(data[n], ...);
两个问题
第一个问题是我如何将 empty 动态数组传递给未类型化的 const 函数。例如:
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] := ;
data[1] := ;
data[2] := ;
data[3] := ;
DoStuff(data, 4); //works
确实有效。这确实意味着我失去了以前的能力:传递索引数组。但真正的问题是它在与 dynamic 数组一起使用时失败:
//Pass four bytes in a dynamic array without using the array index notation
SetLength(moreData, 4);
moreData[0] := ;
moreData[1] := ;
moreData[2] := ;
moreData[3] := ;
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(data, ...); // for a fixed length array
DoStuff(Pointer(data)^, ...); // for a dynamic array
是执行此操作的正确方法。动态数组是指向第一个元素的指针,如果数组为空,则为 nil
。
一旦放弃类型安全并使用无类型参数,在调用此类函数时预计会有更多摩擦是合理的。
我调用的函数需要:
- 无类型常量
- 字节长度
这是一种常见的 Delphi 模式。例如:
procedure DoStuff(const Data; DataLen: Integer);
在此示例测试用例中,所有 DoStuff
都会确保它接收到四个字节:
0x21 0x43 0x65 0x87
出于测试目的,我们将在我们的 little-endian 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^ <> 654321 then
raise Exception.Create('Fail');
//Interpret data as array of bytes
pba := PByteArray(@Data);
if (pba[0] <> ) or (pba[1] <> ) or (pba[2] <> ) or (pba[3] <> ) then
raise Exception.Create('Fail');
// ShowMessage('Success');
end;
为了说明目的,错误检查已经被阐明。
测试
我可以开始测试我是否可以正确地将字节传递给我的 DoStuff
函数。
//Pass four bytes in a LongWord
var lw: LongWord;
lw := 654321;
DoStuff(lw, 4); //works
因此,将 LongWord
传递给采用未类型化 const 的函数的方法就是传递变量。即:
- 错误:
@lw
- 错误:
Addr(lw)
- 不好:
Pointer(@lw)^
- 正确:
lw
我也可以传一个8字节的类型;因为我只读了前四个字节:
//Pass four bytes in QuadWord
var qw: Int64;
qw := FFFFFFF87654321;
DoStuff(qw, 4); //works
数组
现在我们得到了一些更棘手的东西:传递数组:
//Pass four bytes in an array
var data: array[0..3] of Byte;
data[0] := ;
data[1] := ;
data[2] := ;
data[3] := ;
DoStuff(data[0], 4); //Works
//Pass four bytes in a dynamic array
var moreData: TBytes;
SetLength(moreData, 4);
moreData[0] := ;
moreData[1] := ;
moreData[2] := ;
moreData[3] := ;
DoStuff(moreData[0], 4); //works
这些都可以正常工作。但在你们有些人犹豫之前,让我们看看更棘手的案例:
//Pass four bytes at some point in an array
var data: array[0..5] of Byte;
data[2] := ;
data[3] := ;
data[4] := ;
data[5] := ;
DoStuff(data[2], 4); //Works
//Pass four bytes at some point in a dynamic array
var moreData: TBytes;
SetLength(moreData, 6);
moreData[2] := ;
moreData[3] := ;
moreData[4] := ;
moreData[5] := ;
DoStuff(moreData[2], 4); //works
因为 untyped const
运算符通过引用隐式传递,我们传递的是从数组中的第 3 个字节开始的引用(并且我们没有浪费的临时数组副本)。
从这里我可以推断出规则,如果我想将一个数组传递给一个无类型的 const 你传递一个索引数组:
DoStuff(data[n], ...);
两个问题
第一个问题是我如何将 empty 动态数组传递给未类型化的 const 函数。例如:
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] := ;
data[1] := ;
data[2] := ;
data[3] := ;
DoStuff(data, 4); //works
确实有效。这确实意味着我失去了以前的能力:传递索引数组。但真正的问题是它在与 dynamic 数组一起使用时失败:
//Pass four bytes in a dynamic array without using the array index notation
SetLength(moreData, 4);
moreData[0] := ;
moreData[1] := ;
moreData[2] := ;
moreData[3] := ;
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(data, ...); // for a fixed length array
DoStuff(Pointer(data)^, ...); // for a dynamic array
是执行此操作的正确方法。动态数组是指向第一个元素的指针,如果数组为空,则为 nil
。
一旦放弃类型安全并使用无类型参数,在调用此类函数时预计会有更多摩擦是合理的。