鱼C论坛

 找回密码
 立即注册
查看: 1287|回复: 13

分析一个c++程序的输出

[复制链接]
发表于 2023-5-11 09:28:41 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
本帖最后由 6YKT6ZKm5LqI 于 2023-5-16 19:49 编辑

今天的c++实验要我们分析一段代码的输出,我调试了很久但是还是不明白代码的第46、47、48行,也就是pb[2].data、pb[1].data与pb[0].data的值,我的理解是pb[2].data、pb[1].data与pb[0].data应该输出派生类的data值,也就是12,11,10,但是运行结果一个也对不上,麻烦大家帮我看看
#include <iostream>
using namespace std;

class Base
{
public:
    Base() : data(count)
    {
        cout << "Base-ctor" << endl;
        ++count;
    }
    ~Base()
    {
        cout << "Base-dtor" << endl;
        --count;
    }
    static int count;
    int data;
};
int Base::count;

class Derived : public Base
{
public:
    Derived() : data(count), data1(data)
    {
        cout << "Derived-ctor" << endl;
        ++count;
    } 
    ~Derived()
    {
        cout << "Derived-dtor" << endl;
        --count;
    }
    static int count;
    int data1;
    int data;
};
int Derived::count = 10;

int main()
{
    cout << sizeof(Base) << endl;   
    cout << sizeof(Derived) << endl;
    Base *pb = new Derived[3];  
    cout << pb[2].data << endl;   
    cout << pb[1].data << endl;    
    cout << pb[0].data << endl;    
    cout << ((static_cast<Derived *>(pb)) + 2)->data1 << endl;  
    delete[] pb;    
    cout << Base::count << endl;    
    cout << Derived::count << endl; 

    return 0;
}

运行结果

运行结果

想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2023-5-11 09:29:18 | 显示全部楼层

回帖奖励 +4 鱼币

在这段代码中,首先定义了一个名为Base的基类,其中包含一个静态成员变量count和一个普通成员变量data。在构造函数中,data的值被初始化为count的值,然后count自增。在析构函数中,count自减。

接着定义了一个名为Derived的派生类,继承自Base类。Derived类中包含一个静态成员变量count和两个普通成员变量data和data1。在构造函数中,data的值被初始化为count的值,data1的值被初始化为data的值,然后count自增。在析构函数中,count自减。

在主函数中,先输出了Base和Derived的大小,然后定义了一个指向Derived类型的Base指针pb,并将其指向一个Derived类型的数组。接着输出了pb[2].data、pb[1].data和pb[0].data的值,这里需要注意的是,pb是一个指向Base类型的指针,因此pb[ i].data实际上是访问的Base类中的data成员,而不是Derived类中的data成员。而在Derived类中,data的初始值为10,因此pb[2].data、pb[1].data和pb[0].data的值分别为10、11、12。

接着输出了((static_cast<Derived *>(pb)) + 2)->data1的值,这里需要先将pb强制转换为Derived类型的指针,然后再加上2,即访问了第三个Derived对象的data1成员,其值为12。

最后释放了pb指向的内存,并输出了Base::count和Derived::count的值,其中Base::count为0,Derived::count为13。这是因为在构造函数中count自增,而在析构函数中count自减,因此最后的count值为13。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2023-5-11 09:31:16 | 显示全部楼层
isdkz 发表于 2023-5-11 09:29
在这段代码中,首先定义了一个名为Base的基类,其中包含一个静态成员变量count和一个普通成员变量data。在 ...

我也是这么理解的,可是pb[2].data、pb[1].data与pb[0].data的值从运行结果上看不是12、11、10
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2023-5-11 09:34:04 | 显示全部楼层
6YKT6ZKm5LqI 发表于 2023-5-11 09:31
我也是这么理解的,可是pb[2].data、pb[1].data与pb[0].data的值从运行结果上看不是12、11、10

看起来这是由于对象切片(object slicing)的问题。在C++中,对象切片发生在基类对象被赋值给派生类对象,或者基类指针或引用指向派生类对象时,会丢失派生类特有的数据和行为。

在你的代码中,你通过基类指针(Base*)访问派生类数组的元素(Derived[3]),这样就发生了对象切片,丢失了Derived部分的数据和行为,只保留了Base部分的数据和行为。

具体来说,当你访问pb[2].data,pb[1].data和pb[0].data时,你实际上访问的是Base部分的data,而不是你期望的Derived部分的data。也就是说,你看到的输出是基类的data值,而不是派生类的data值。

另外,你的派生类Derived和基类Base都有一个名为"data"的成员,这可能会引起混淆。当你通过基类指针访问"data"时,实际上是访问的基类的"data",而不是派生类的"data"。

如果你想要访问派生类的data,你需要将基类指针转换为派生类指针,然后再访问data。例如,你可以这样访问派生类的data:
cout << static_cast<Derived*>(&pb[2])->data << endl;

这样就可以访问到派生类的data值了。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2023-5-11 10:00:45 | 显示全部楼层
isdkz 发表于 2023-5-11 09:34
看起来这是由于对象切片(object slicing)的问题。在C++中,对象切片发生在基类对象被赋值给派生类对象 ...

但是如果访问的是基类data,pb[2].data、pb[1].data与pb[0].data的值应该是2、1、0,前两个变量的输出仍然与运行结果不匹配
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2023-5-11 10:02:51 | 显示全部楼层
6YKT6ZKm5LqI 发表于 2023-5-11 10:00
但是如果访问的是基类data,pb[2].data、pb[1].data与pb[0].data的值应该是2、1、0,前两个变量的输出仍 ...

你的理解是正确的,我要道歉,之前我没注意到这个问题。让我们再次深入研究一下这个问题。

在你的代码中,你创建了一个类型为Derived的动态数组,然后使用一个类型为Base的指针来引用它。虽然这在C++中是合法的,但是当你使用基类指针来操作派生类的数组时,往往会导致错误。

具体来说,当你通过基类指针访问数组元素(如pb[2]、pb[1]和pb[0])时,C++会假设每个数组元素的大小与基类的大小相同。然而,实际上,派生类的大小可能比基类大(特别是当派生类添加了新的成员变量或函数时)。因此,当你通过基类指针访问派生类数组的元素时,C++可能会错误地计算数组元素的位置,从而导致错误的结果。

在你的代码中,Base类的大小是8字节(一个int成员变量和一个虚函数指针),而Derived类的大小是16字节(因为它增加了两个int成员变量)。因此,当你通过基类指针访问pb[2]、pb[1]和pb[0]时,C++实际上是访问了内存中错误的位置。

简而言之,你不应该通过基类指针来操作派生类的数组。如果你需要操作的是派生类的数组,你应该使用派生类的指针。如果你需要通过基类指针来操作对象,你应该创建基类的指针数组,而不是基类指针的数组。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2023-5-11 17:47:33 | 显示全部楼层

回帖奖励 +4 鱼币

想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2023-5-11 18:24:19 | 显示全部楼层

回帖奖励 +4 鱼币

想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2023-5-11 18:24:29 | 显示全部楼层

回帖奖励 +4 鱼币

bibi
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2023-5-11 18:29:31 From FishC Mobile | 显示全部楼层

回帖奖励 +4 鱼币

币拿走
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2023-5-11 18:51:14 | 显示全部楼层
本帖最后由 yinda_peng 于 2023-5-11 18:52 编辑

不会咧
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2023-5-11 18:52:12 | 显示全部楼层
@夏季的春秋 你来看,我不会C++
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2023-5-11 20:50:33 | 显示全部楼层
c++没学过,不会捏
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2023-5-13 13:54:28 | 显示全部楼层
在C++中,派生类对象的布局被编译器决定,因此在不同的编译器、操作系统及计算机架构下,表现可能不同。

对于给定的代码片段,由于动态分配内存的方式是使用指向派生类对象的基类指针,因此在访问 pb[2].data、pb[1].data 和 pb[0].data 时,只会访问到对象中第一个 Base 类成员变量 data。而这些对象在内存中的布局将取决于特定的编译器实现,所以无法确切预测这些数据成员的值。

但是可以确定的是,因为 Derived 类型对象的内存空间大于 Base 类型对象的内存空间,在向一个指向 Base 类型的指针赋值为指向 Derived 对象的指针时,最后一块空闲区域可能没有被完全填充(或者说未被正确地重新初始化)。因此,如果将 pb 转换为 Derived* 指针,则 ((static_cast<Derived *>(pb)) + 2)->data1 的值可能会受到初始值的影响,而这个值也没有办法精确预测。

想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2024-9-28 01:26

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表