一些Windows结构都是可变大小的,一个固定大小的头之后是一个可变大小的数组。当这些结构被声名的时候,数组大小都声明为1,举个例子:
typedef struct _TOKEN_GROUPS {
DWORD GroupCount;
SID_AND_ATTRIBUTES Groups[ANYSIZE_ARRAY];
} TOKEN_GROUPS, *PTOKEN_GROUPS;
如果你看这个头文件,你将看到这个ANYSIZE_ARRAY被定义为1,因此这个结构中声明了一个动态扩展大小的数组。
使用这个声明,你会像下面这个代码一样对一个可变大小的TOKEN_GROUPS结构进行内存分配:
PTOKEN_GROUPS TokenGroups =
malloc(FIELD_OFFSET(TOKEN_GROUPS, Groups[NumberOfGroups]));
初始化化数组:
TokenGroups->GroupCount = NumberOfGroups;
for (DWORD Index = 0; Index = NumberOfGroups; Index++) {
TokenGroups->Groups[Index] = ...;
}
很多人认为这个结构应该设计成下面这个样子:
typedef struct _TOKEN_GROUPS {
DWORD GroupCount;
} TOKEN_GROUPS, *PTOKEN_GROUPS;
(在文中, 以斜体书写的代码都是错误的或是假设的)
分配内存的代码变成了:
PTOKEN_GROUPS TokenGroups =
malloc(sizeof(TOKEN_GROUPS) +
NumberOfGroups * sizeof(SID_AND_ATTRIBUTES));
这个方法有两个缺点,一个是次要的,而另外一个是致命的。
首先,次要的缺点是:这种做法非常难以存取可变大小数据。初始化TOKEN_GROUPS代码变成:
TokenGroups->GroupCount = NumberOfGroups;
for (DWORD Index = 0; Index = NumberOfGroups; Index++) {
((SID_AND_ATTRIBUTES *)(TokenGroups + 1))[Index] = ...;
}
真正的缺点是致命的。上述的代码会在64位Windows上crash。SID_AND_ATTRIBUTES结构如下:
typedef struct _SID_AND_ATTRIBUTES {
PSID Sid;
DWORD Attributes;
} SID_AND_ATTRIBUTES, * PSID_AND_ATTRIBUTES;
通过观察,结构中第一个成员变量是一个指针,PSID。SID_AND_ATTRIBUTES结构需要指针对齐,在64位Windows是8位对齐。也就是说,被设想的TOKEN_GROUPS 结构是一个DWORD,因此只需要4位对齐。sizeof(TOKEN_GROUPS)是4。
我相信你已经找到问题的所在了。
在被设想的结构定义中,SID_AND_ATTRIBUTES数组将不是8个字节对齐,而只是4个字节对齐。没有在GroupCount 和第一个SID_AND_ATTRIBUTES之间增加padding。在读取结构中数组的时候会导致STATUS_DATATYPE_MISALIGNMENT异常。
也许你会说,为什么不用0长度的数据来代替1呢?
因为从1999起在C标准中0长度的数组将不再合法。