Delphi11下如何正确分配含动态数组的Win32变长结构体
问题根因
触发范围检查异常的核心原因是结构体尾部可变长度成员被声明为固定长度数组array[0..0] of ULONG,Delphi编译器的范围检查机制会严格校验数组下标是否落在声明的[0,0]区间内,因此访问下标≥1的元素时必然抛出异常。通过{$R-}关闭范围检查只是临时绕过编译器校验,会掩盖其他潜在的数组越界问题,不属于合规实现。
两种合规实现方案
两种方案均不需要关闭范围检查,完全兼容Win32 API调用约定。
方案1:指针偏移访问(全Delphi版本兼容)
不需要修改现有结构体定义,访问可变数组成员时不直接通过结构体的固定数组下标访问,而是先获取数组首地址,通过指针算术访问元素,编译器不会对指针访问触发固定数组的范围检查:
// 访问示例 var pOffsets: PULONG; i: Integer; begin // 先获取Offsets数组的首地址 pOffsets := @aBuffer^.Offsets[0]; // 合法访问范围:i ∈ [0, aBuffer^.ProcessorCount - 1] for i := 0 to aBuffer^.ProcessorCount - 1 do begin // 指针偏移访问,等价于C语言的数组下标访问 Writeln('Offset ', i, ': ', pOffsets[i]); end; end;
该方案实现简单,兼容从Delphi 7到最新版本的所有开发环境,是处理Windows变长结构体最通用的方案。
方案2:柔性数组定义(Delphi 10.4+原生支持)
Delphi 10.4及后续版本(含你使用的Delphi 11)已经原生支持C语言标准的柔性数组成员(Flexible Array Member),直接将尾部可变数组声明为0起始的无界非托管数组即可,编译器会自动识别其为可变长度尾部成员,不会做固定长度范围校验,且SizeOf计算结构体大小时仅包含前面的固定成员,完全匹配Win32 API的内存布局:
type PSYSTEM_PROCESSOR_PERFORMANCE_DISTRIBUTION = ^SYSTEM_PROCESSOR_PERFORMANCE_DISTRIBUTION; SYSTEM_PROCESSOR_PERFORMANCE_DISTRIBUTION = record ProcessorCount: ULONG; Offsets: array[0..] of ULONG; // 无界柔性数组,匹配C语言定义 end;
使用该定义后,可以直接通过自然的数组下标语法访问元素,只要下标不超过ProcessorCount - 1就不会触发范围检查,代码可读性最高。注意不要将该无界柔性数组和Delphi托管动态数组array of ULONG混淆,后者带有引用计数和长度字段头,内存布局与Win32 API不匹配,禁止用于互操作结构体定义。
内存分配注意事项
你当前使用的「两次调用NtQuerySystemInformation获取所需缓冲区大小→分配对应内存→再次调用获取数据」的流程是完全正确的,仅需要注意三个细节:
- 分配内存时不要手动计算结构体大小,完全使用API第一次返回的
bufferSize值分配即可,该值已经包含了所有Offsets元素所需的存储空间 - 缓冲区使用完成后必须调用
FreeMem释放分配的内存,避免内存泄漏 - 业务逻辑层面必须保证访问
Offsets的下标不超过ProcessorCount - 1,该边界属于业务逻辑校验范围,编译器无法自动检查
内容的提问来源于stack exchange,提问作者Quelltextknecht

