有一些Windows结构体是可变长度的,它们通常会有一个固定长度的头部,然后接下来是一个可变长度的数组。当这些结构体被声明的时候,它们通常会被声明为一个只有一个字节元素的数组,如下图所示:
如果我们观察一下头文件中的定义,就会发现,这里的ANYSIZE_ARRAY被定义成了1,也就是说,这个结构体的结尾是一个只包含1个字节的数组。
通过这样的声明,我们可以分配一个可变长度的结构体,如下图所示:
然后,我们可以通过下图所示的方式来对结构体进行初始化:
有一些开发者可能会认为这个结构体应该像下图这样定义:
然后,结构体的内存分配就看起来像下图这样进行:
这样的做法,会有两个缺陷:一个表面上可见的缺陷,和一个致命缺陷,容我慢慢道来。
首先,这样的设计,会导致客户非常难以访问一个可变长度的数据,对TOKEN_GROUPS结构体的初始化可能如下图这样进行:
真正的缺陷在于,上述代码会在一台64位Windows系统上崩溃,下面是SID_AND_ATTRIBUTES结构体的定义:
上述的结构体定义中,第一个成员是一个PSID指针。SID_AND_ATTRIBUTES结构体需要指针对齐,而在一台64位的Windows系统上是8字节对齐的。另一方面,TOKEN_GROUPS结构仅仅包含一个DWORD,因此它仅需要在4字节边界上对齐,而sizeof(TOKEN_GROUPS)的值为4。
我希望你能看出来现在是什么样一个状况。
从底层结构上来看,SID_AND_ATTRIBUTES结构不会对齐到一个8字节的边界,而是会对齐到4个字节边界,而用来填充这中间的间隙的部分被忽视了。所以,当尝试访问这个数组的成员的时候,会直接导致一个STATUS_DATATYPE_MISALIGNMENT异常。
你可能会问了,那为什么不使用一个长度为0的数组,而使用1个字节的数组呢?
因为:”时间旅行目前还只是一个幻想而已。”
在1999年之前,标准C语言还不支持一个长度为0的数组。而Windows的开发远远早于这个时间点,因而不大可能会利用这个语言特性。
可变长度的数据结构,在网络数据传输中颇有用处。我为啥知道?兄弟我吃过这方面的亏。
Raymond Chen的《The Old New Thing》是我非常喜欢的博客之一,里面有很多关于Windows的小知识,对于广大Windows平台开发者来说,确实十分有帮助。本文来自:《Why do some structures end with an array of size 1?》