先看下面一段代码:
1 |
|
你可以尝试分析一下,这段代码的结果吗?我们可以回顾一下继承的概念
运行这段代码之后我们发现,结果如下:
1 | mingw64: |
为什么会出现这种情况呢?我们一步步来分析。
代码关键点解释
类结构:
Base
类:包含成员变量a = 1
,以及虚函数print(int n = 2)
,默认参数为2。函数输出"Base:" << a + n
。Derive
类:继承自Base
,添加成员变量b = 3
,并覆盖虚函数print(int n = 10)
,默认参数改为10。函数输出"Derive:" << b + n
。- 虚函数(
virtual
)支持运行时多态:通过基类指针调用时,实际执行派生类的覆盖函数(动态绑定)。
结构体(类)在内存中的存放规则
为了更方便的访问结构体对象中的成员变量,通常需要在内存中
对齐存储
,所以通常需要空一段内存,这便是填充
。所以一个结构体对象在内存中的存储原则
是:结构体的起始地址 = 最大成员对齐值的整数倍
每个成员的起始地址能够被该成员大小整除
结构体的大小 = 最大成员的整数倍
在64位系统中,每个内存单元的大小通常为64b即8B,所以一般按照8B对齐,指针大小通常也为8B
在32位系统中,每个内存单元的大小通常时32b即4B,所以一般按照4B对齐,指针大小通常也为4B
按照上面的原则,可以知道上述父子类对象在不同系统中,内存的存储方式:
默认参数的绑定规则:
- 默认参数是静态绑定(在编译时基于指针或引用类型确定),而非动态绑定。
- 当通过基类指针调用虚函数时:
- 函数实现(函数体)使用派生类的版本(动态绑定)。
- 默认参数值使用基类定义的默认值(静态绑定),忽略派生类的默认值,所以运算时
n的值始终为2
。
数组分配问题:
Base* arr = new Derive[10]
:这里分配了一个包含10个Derive
对象的数组,但用Base*
指针指向它。
我们希望情况如下:
但实际上呢???
输出结果分析
1. 第一次调用:arr[7].print()
输出结果:
mingw64构建运行时:
1
Derive :5
mingw32构建运行时:
1
exited with code -1073741819
两个编译器为啥输出结果不同?:
关键在于arr[7] 有没有指向[Derive7]
mingw64构建时:
虽然Base和Derive大小不同,但是由于
内存对齐
使得Base的大小和Derive大小相同,阴差阳错的使得arr[7]指向了Derive[7]的位置,使得可以调用Derive的print函数,输出为5;mingw32构建时:
指针算术错误:
arr
指向Derive[10]
数组,但arr[7]
基于sizeof(Base)
计算地址(例如,在32位系统中,arr + 7 * 8 = arr + 56
)。实际第7个Derive
对象位于arr + 7 * 12 = arr + 84
(假设sizeof(Derive)=12
)。因此,arr[7]
指向无效内存。所以输出错误值。为什么不是多态输出? 即使忽略指针错误,理论上:
- 函数调用应使用
Derive::print
实现(虚函数动态绑定)。 - 但默认参数静态绑定为
Base::print
的n=2
(因为arr
类型是Base*
)。 - 输出应为
"Derive:" << b + 2
(即Derive:5
),但由于对象地址错误,b
的值可能无效或取自错误内存。
- 函数调用应使用
2. 第二次调用:ptr->print()
- 输出结果:
Derive:5
- 原因:
ptr
指向一个有效的Derive
对象(new Derive()
),多态正确:ptr
类型是Base*
,但对象是Derive
,因此虚函数调用Derive::print
实现(动态绑定)。- 默认参数静态绑定:调用
ptr->print()
未提供参数,编译器基于ptr
的静态类型(Base*
)使用Base::print
的默认值n=2
(忽略Derive::print
的n=10
)。 - 因此,实际调用
Derive::print(2)
:b + n = 3 + 2 = 5
。- 输出:
"Derive:5"
。
- 关键原理:默认参数在编译时确定,基于指针类型(
Base*
),而函数体在运行时确定,基于对象类型(Derive
)。
- 原因:
- 本文作者: 迪丽惹Bug
- 本文链接: https://lyroom.github.io/2025/07/03/c-继承中的父子纠葛/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!