C语言 为什么不允许在指针类型之间进行左值强制转换?

s8vozzvw  于 2023-05-06  发布在  其他
关注(0)|答案(1)|浏览(169)

在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

做同样的事我想不出在任何情况下,这种命名会证明是模棱两可的,或者它会对语言产生任何负面影响。

gojuced7

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*类型,所以你不能使用不同的类型在那个指针上做指针算术,然后以某种方式将它存储为原始类型的指针算术值。这里的错位是一个严重的问题。
至于如何处理这样的代码,最不坏的选择是使用字符类型的临时变量:

uint8_t* byte_ptr = (uint8_t*)hdr;
byte_ptr += hdr->BlockSize;
hdr = (RELOC_ENTRY*) byte_ptr;

这也是非常可疑和未定义的行为,也不能保证有效。但你做了什么...
许多旧的、讨厌的Windows API头文件早于灵活的数组成员,并利用了“struct hack”,这也是它的一个特色。这里的其他不好的做法是匈牙利符号,在typedef后面隐藏指针,使用UINT16作为位字段等等。从给定的结构中生成定义良好的C代码是不可能的。
正确编写的具有灵活数组成员的代码如下所示:

typedef struct
{
    uint32_t     PageRVA;
    RELOC_ENTRY  entry[];
} RELOC_BLOCK_HDR;

这是假设BlockSizesizeof(RELOC_ENTRY)相同-但如果不是,那么原始代码中的所有赌注都是无效的。如果RELOC_ENTRY没有填充,那么必须发明一个不同的 Package 器结构,32字节大,包含2个RELOC_ENTRY对象。

相关问题