提问人:EatingTechnobladesRemainsAt3am 提问时间:5/2/2023 最后编辑:Peter MortensenEatingTechnobladesRemainsAt3am 更新时间:5/2/2023 访问量:183
为什么不允许指针类型之间的左值转换?
Why aren't lvalue casts between pointer types allowed?
问:
在 Windows PE 格式中,有一个包含标头数组的重定位部分:
typedef struct _RELOC_BLOCK_HDR
{
UINT32 PageRVA;
UINT32 BlockSize;
} RELOC_BLOCK_HDR, *PRELOC_BLOCK_HDR;
在每个标头之后,标头后面都有一个值数组:
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
做同样的事情?我想不出在什么情况下,这种命名法会被证明是模棱两可的,或者它会对语言产生任何负面影响。
答:
*(char**)&hdr += hdr->BlockSize
在任何情况下都是错误的。您正在将 a 转换为 a,并且这些是不兼容的类型 - 取消引用会调用未定义的行为。所以代码是错误的,尽管它现在看起来“有效”。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 标头早于灵活的数组成员,并利用了“结构黑客”,这也是一种味道。这里的其他不良做法是匈牙利符号、在 typedef 后面隐藏指针、使用 for 位字段等。不可能从给定的结构中生成定义良好的 C 代码。UINT16
使用灵活数组成员正确编写的代码如下所示:
typedef struct
{
uint32_t PageRVA;
RELOC_ENTRY entry[];
} RELOC_BLOCK_HDR;
这假设与 - 相同,但如果不是,那么所有赌注在原始代码中也都关闭了。如果没有填充,那么必须发明一个不同的包装结构,32 字节大,包含 2 个对象。BlockSize
sizeof(RELOC_ENTRY)
RELOC_ENTRY
RELOC_ENTRY
评论
buffer
buffer += offset
offset
buffer
buffer
char *p = buffer; *p += offset;
buffer
buffer
uint8_t* ptr = (uint8_t*)array; ptr[byte_no] = something;