两种跨类型指针转换的差异:(obj1*)p与(obj1*)(void*)p
一、两种转换方式的核心差异
假设obj1与obj2无任何继承或关联关系,将obj2*指针p转换为obj1*有两种写法:
- 直接强制转换:
(obj1*)p - 通过
void*中转转换:(obj1*)(void*)p
1. C++标准层面的定义区别
- 直接转换
(obj1*)p等价于reinterpret_cast<obj1*>(p),属于实现定义行为:标准仅保证将转换后的指针转回原类型时能得到原始值,但转换后的指针地址值、寻址行为可能因编译器或平台不同而变化。 - 中转
void*的转换分为两步:- 第一步
(void*)p:任何对象指针都可隐式转换为void*,且void*会完整保留原指针的地址信息,这是标准明确规定的行为。 - 第二步
(obj1*)(void*)p:将void*转换为任意对象指针类型时,标准保证转换后的指针地址与原void*一致(只要目标指针类型的大小能容纳原地址,主流平台均满足),行为确定性更强。
- 第一步
2. 特殊平台的潜在差异
在部分特殊架构(如早期16位分段系统、小众嵌入式平台)中,不同类型指针的地址表示可能存在差异:比如char*可能使用段内寻址,而对象指针使用跨段寻址。直接转换可能导致指针的段属性处理错误,而通过void*中转时,编译器会统一处理地址的表示形式,确保转换后的指针能正确指向目标内存。
二、GCC选择中转void*的原因
结合pool_allocator中_M_refill()的场景(将原始内存块char*转为链表节点_Obj*),GCC选择这种写法主要基于以下几点:
1. 保证跨平台一致性
直接reinterpret_cast的实现定义行为可能在不同平台上产生不一致结果,而void*中转的转换规则在所有符合C++标准的编译器上统一,能确保内存池代码在各种平台上稳定运行。
2. 规避严格别名规则的风险
虽然char*/unsigned char*是严格别名规则的例外(允许通过它们访问任意类型的内存),但直接转换char*到_Obj*仍可能触发编译器的严格别名警告。中转void*的写法能明确告诉编译器这是“内存地址的类型转换”,而非非法的别名访问,避免编译器因优化逻辑误判导致的错误。
3. 代码意图更清晰
中转void*的写法直观表达了“将原始内存地址转换为目标对象指针”的逻辑,相比直接强制转换,更易被其他开发者理解,符合标准库代码的可读性与规范性要求。
三、结合示例代码的分析
在_M_refill()函数中:
__result = (_Obj*)(void*)__chunk; *__free_list = __next_obj = (_Obj*)(void*)(__chunk + __n);
__chunk是char*类型的原始内存块起始地址,通过void*中转后转换为_Obj*,目的是将这块内存当作union _Obj类型的链表节点操作。_Obj作为兼具链表指针和客户端数据占位符的union,这种转换方式确保了内存地址的正确性,让编译器能正确处理后续的链表指针赋值与遍历操作。
内容的提问来源于stack exchange,提问作者Kiyya

