C++函数输出异常值:子类数组传入父类指针后的奇怪输出
为什么
a[i].a会访问到B的b成员? 这是典型的指针类型不匹配导致的内存访问错位问题,本质是你把B类型的数组传递给了期望A类型指针的函数,从而触发了未定义行为(UB),我们一步步拆解来理解:
1. 类的内存布局
先看你的类定义:
class A { public: int a; public: A():a(1){}; }; class B:public A { public: int b; public: B():b(2){}; };
假设你的系统中int占4字节:
A类只有一个int成员,所以sizeof(A) = 4,每个A对象在内存中就是一个4字节的整数。B类是A的公有派生类,继承了A的a,再加上自己的b,所以sizeof(B) = 8。每个B对象的内存布局是:先放A的a(前4字节),再放自己的b(后4字节),也就是每个B对象看起来像[a=1][b=2]。
2. 数组传递与指针步长的问题
当你在main中调用print(b)时,数组b会退化为指针,但这里有个关键:b是B类型的数组,退化后的指针是B*,但print函数接收的是A*类型的参数,C++允许这种隐式转换(因为B是A的派生类)。
问题出在print函数的循环里:
for(int i=0; i<10; ++i) { cout<<a[i].a<<" "; }
这里的a[i]等价于*(a + i),而指针加法的步长是由指针的类型决定的:a是A*类型,所以每次i增加1,指针会跳过sizeof(A)(4字节)。
但实际数组里的元素是B类型,每个元素占8字节。所以:
- 当
i=0:a+0指向第一个B对象的a,输出1,没问题。 - 当
i=1:a+1跳过4字节,指向的是第一个B对象的b成员的地址!此时代码把这个4字节的内存当成A对象的a成员来读取,而b的值是2,所以输出2。 - 当
i=2:a+2跳过8字节,刚好指向第二个B对象的a,输出1。 - 以此类推,就出现了你看到的
1 2 1 2 ...的循环输出。
3. 这是未定义行为!
虽然你看到了一个看似有规律的输出,但这种操作属于C++中的未定义行为——标准没有规定这种情况下程序的输出,不同编译器、不同平台可能会有不同的结果,甚至崩溃。
如何修复?
如果你想正确遍历B数组,应该让print函数接收B*类型的参数:
void print(B* b) { for(int i=0; i<10; ++i) { cout<<b[i].a<<" "; } }
或者用模板来支持任意类型的数组,更灵活:
template<typename T> void print(T* arr, int size) { for(int i=0; i<size; ++i) { cout<<arr[i].a<<" "; } } // 调用时:print(b, 10);
内容的提问来源于stack exchange,提问作者Eduard Rostomyan
相关产品推荐
相关产品推荐

