成员函数指针转换逻辑看似合理却出错?标准与编译器表现解析
问题分析:跨无关基类的成员函数指针转换导致崩溃
问题场景
原本认为在C中,将B类的成员函数指针转换为C类成员函数指针后,再转为A类成员函数指针,只要调用目标是C类实体就可以正常运行,但实际运行出现崩溃。经测试:MSVC、GCC编译时报错,Clang编译通过但运行崩溃。请问这属于编译器bug还是未定义行为?C标准对此场景是否有详细说明?
示例代码
#include <iostream> struct A { char i[7]; void test1(){ std::cout << __LINE__; } }; struct B { char i[14]; virtual void test(){ std::cout << __LINE__; } }; struct C:A,B { char i[31]; }; using T = void (C::*)(); int main() { C c; (c.*T(&B::test))();//正常运行 (c.*static_cast<decltype(&A::test1)>(T(&B::test)))();//崩溃 return 0; }
解答
这是未定义行为,不是编译器bug
C++标准对成员函数指针的转换有严格限制,你的代码中存在非法的跨无关基类成员指针转换,具体原因如下:
合法的成员指针转换范围
根据C++标准,static_cast用于成员函数指针转换时,仅允许在同一继承链的类之间进行:- 可将派生类的成员函数指针转换为其直接/间接基类的成员函数指针(前提是基类中存在该成员,且转换符合继承规则);
- 反之,也可将基类的成员函数指针转换为其直接/间接派生类的成员函数指针(如代码中
&B::test转T(C::*)这一步是合法的,因为C继承自B)。
非法转换的本质
代码中第二步,将C::*类型的成员指针转换为A::*类型是非法的:A和B是C的两个独立基类,彼此之间没有继承关系,它们的成员函数指针属于完全无关的类型。这种跨无关类的成员指针转换不在标准允许的范围内,属于未定义行为。编译器差异的原因
- MSVC和GCC在编译阶段就检测到这种非法转换,直接抛出错误,这是符合标准的——编译器可以选择诊断这类未定义行为,但不是必须的;
- Clang允许编译通过,但运行时崩溃是未定义行为的典型表现:编译器不需要强制诊断所有未定义行为,一旦出现,程序的任何行为都是不可预测的(包括崩溃、输出异常结果等)。
C++标准的相关说明
C++标准在[expr.static.cast]章节明确规定:
当static_cast用于转换成员指针时,源类型必须是指向派生类成员的指针,目标类型是指向其基类成员的指针,且基类是派生类的直接或间接基类;或者反过来,源类型是指向基类成员的指针,目标类型是指向其派生类成员的指针,且派生类中该成员的声明与基类兼容。
对于无关类之间的成员指针转换,static_cast是不允许的,此类转换属于未定义行为。
内容的提问来源于stack exchange,提问作者余国良
相关产品推荐
相关产品推荐

