在Windows PE中,有一个包含头数组的重定位部分:
typedef struct _RELOC_BLOCK_HDR
{
UINT32 PageRVA;
UINT32 BlockSize;
} RELOC_BLOCK_HDR, *PRELOC_BLOCK_HDR;
在每个header之后,都有一个header后面的值数组:
typedef struct _RELOC_ENTRY
{
UINT16 Offset : 12;
UINT16 Type : 4;
} RELOC_ENTRY, * PRELOC_ENTRY;
假设我们有一个指向其中一个头的指针:
PRELOC_BLOCK_HDR hdr;
我们想把字节偏移量应用于它,以获得指向下一个头的指针:
*(char**)&hdr += hdr->BlockSize
这种方式似乎有点愚蠢。为什么我们不能用
(char*)hdr += hdr->BlockSize
做同样的事我想不出在任何情况下,这种命名会证明是模棱两可的,或者它会对语言产生任何负面影响。
1条答案
按热度按时间gojuced71#
*(char**)&hdr += hdr->BlockSize
在任何情况下都是错误的。您正在将RELOC_BLOCK_HDR**
转换为char**
,这是不兼容的类型-取消引用此char**
会调用未定义的行为。因此,代码是错误的,尽管它现在看起来“工作”。C语言中有一条规则,允许我们使用字符指针逐字节检查任何其他类型。这个特殊的规则将指针应用到字符类型
char*
/unsigned char*
/signed char*
(可能还有uint8_t*
),但它不会“递归”地应用到char**
。至于为什么
(char*)hdr +=
不起作用-hdr
实际上是RELOC_BLOCK_HDR*
类型,所以你不能使用不同的类型在那个指针上做指针算术,然后以某种方式将它存储为原始类型的指针算术值。这里的错位是一个严重的问题。至于如何处理这样的代码,最不坏的选择是使用字符类型的临时变量:
这也是非常可疑和未定义的行为,也不能保证有效。但你做了什么...
许多旧的、讨厌的Windows API头文件早于灵活的数组成员,并利用了“struct hack”,这也是它的一个特色。这里的其他不好的做法是匈牙利符号,在typedef后面隐藏指针,使用
UINT16
作为位字段等等。从给定的结构中生成定义良好的C代码是不可能的。正确编写的具有灵活数组成员的代码如下所示:
这是假设
BlockSize
与sizeof(RELOC_ENTRY)
相同-但如果不是,那么原始代码中的所有赌注都是无效的。如果RELOC_ENTRY
没有填充,那么必须发明一个不同的 Package 器结构,32字节大,包含2个RELOC_ENTRY
对象。