Nim 中定义柔性数组
2024-12-25 08:33:40

在 Nim 中定义 Win32 中的 MIB_IPADDRTABLE 结构时产生的一个问题:对于包含动态数组的结构应该如何定义?

用 array

刚开始时我参考了 winim 中的定义:

1
2
3
MIB_IPADDRTABLE* {.pure.} = object
dwNumEntries*: DWORD
table*: array[ANY_SIZE, MIB_IPADDRROW]

看上去和原始结构定义一致,其内存布局也和 C 语言一致:

1
echo sizeof(MIB_IPADDRTABLE) # 28 bytes

但是程序运行时会发生崩溃:

1
Error: execution of an external program failed

因为这个类型有边界检查,它的大小在编译时确定为1了,在运行时访问第2个元素后会导致程序异常退出。
除非在编译时使用--boundChecks:off选项关闭边界检查,但这会影响整个程序。

用 UncheckedArray

正确的方法是用 UncheckedArray 类型:

1
2
3
MIB_IPADDRTABLE* = object
dwNumEntries*: DWORD
table*: UncheckedArray[MIB_IPADDRROW]

但与用array类型不同的时,结构体大小发生了变化:

1
echo sizeof(MIB_IPADDRTABLE) # 4 bytes

显然这个4字节是dwNumEntries的长度,数组的长度是零。看看生成的 C 代码:

1
2
3
4
struct tyObject_MIB_IPADDRTABLE__9cIn6VOf2jyMBmImLIY4BwA {
NU32 dwNumEntries;
tyObject_MIB_IPADDRROW_XP__BbHJV3EYHQQqztZYwxSaUg table[SEQ_DECL_SIZE];
};

查看源码得知 SEQ_DECL_SIZE 根据条件验证后被定义为不同的值:

1
2
3
4
5
6
7
8
9
/* declared size of a sequence/variable length array: */
#if defined(__cplusplus) && defined(__clang__)
# define SEQ_DECL_SIZE 1
#elif defined(__GNUC__) || defined(_MSC_VER) || defined(__TINYC__) || \
(defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901)) // C99
# define SEQ_DECL_SIZE /* empty is correct! */
#else
# define SEQ_DECL_SIZE 1000000
#endif

我用的 VC 编译器,SEQ_DECL_SIZE被定义为空,所以不占用长度。

array 和 UncheckedArray 的区别

  1. 大小声明:

    • array 需要在编译时知道确切的大小。例如:array[5, int]
    • UncheckedArray 不需要指定大小。例如:UncheckedArray[int]
  2. 边界检查:

    • array 在默认情况下会进行边界检查,除非使用 --boundChecks:off 编译选项。
    • UncheckedArray 不进行任何边界检查,允许在没有运行时检查的情况下访问任何索引。
  3. 用途:

    • array 通常用于固定大小的数组。
    • UncheckedArray 主要用于与 C 代码交互,特别是处理可变长度数组或作为结构体的最后一个成员(柔性数组成员)。
  4. 内存管理:

    • array 通常作为栈分配或结构体的一部分。
    • UncheckedArray 通常与动态内存分配一起使用。

结论

  • 柔性数组成员的位置一定是处于结构体中最后一个位置。
  • 定义时的数组长度不重要,因为它只有在运行时才能知道确切的大小。
  • UncheckedArray基本用在与 C 交互的地方。

相关阅读

柔性数组