我是C++的新手,正在尝试多态。我有以下代码:
#include <iostream>
class Base1
{
protected:
int b1;
public:
int m() { return 1; }
};
class Base2
{
protected:
int b2;
public:
int n() { return 2; }
};
class Der : public Base1, public Base2
{
protected:
int d1;
public:
int m() { return 11; }
int n() { return 21; }
};
int main()
{
Der *ptr = new Der();
Base1 *b1 = ptr;
Base2 *b2 = ptr;
std::cout << "d: " << ptr << ", m: " << ptr->m() << ", n: " << ptr->n() << "\n";
std::cout << "b1: " << b1 << ", m: " << b1->m() << "\n";
std::cout << "b2: " << b2 << ", n: " << b2->n() << "\n";
delete ptr;
return 0;
}
当我运行此代码时,有趣的是b2移位了4个字节,这是我的输出:
d: 0x564eab6cbeb0, m: 11, n: 21
b1: 0x564eab6cbeb0, m: 1
b2: 0x564eab6cbeb4, n: 2
为什么只有b2会发生这种情况?我想这与事物在内存中的存储方式有关,因为如果删除b1中的int字段,b2不会受到影响。有没有办法轻松查看堆栈和堆?我想看看会发生什么。 (我正在使用Virtual Studio Code)
最佳答案
OP的样本(简化了一点)
struct Base1 {
int b1;
};
struct Base2 {
int b2;
};
struct Der: Base1, Base2 { };
可能会导致以下内存布局:
// start of Der
// start of Base1
0000: Base1::b1 // type int
// start of Base2
0004: Base2::b2 // type int
因此,当实例化
struct Der
时,其内容的一部分是struct Base2
的实例,但它并不始于Der
实例的相同地址。用
Der *ptr = new Der();
初始化
Base2 *b2 = ptr;
不会将
ptr
中的地址纯副本复制到b2
中。还涉及从Der*
到Base2*
的隐式转换。编译器知道Der
和Base2
类的关系。因此,该转换导致Base2
中的Der
偏移量无声添加。为了展示这一点,我做了一个小演示。 (我不确定它的说服力如何。):
#include <iostream>
struct Base1 {
int b1 = 1;
};
struct Base2 {
int b2 = 2;
};
struct Der: Base1, Base2 { };
int main()
{
Der *ptr = new Der;
Base2 *b2;
std::cout << "ptr:" << ptr << ", ptr1->b2: " << ptr->b2 << '\n';
b2 = ptr;
std::cout << "b2: " << b2 << ", b2->b2: " << b2->b2 << '\n';
}
使用gcc 4.1.2进行编译,您可以在实际分配发生的地方找到以下代码:
mov %rax, QWORD PTR [%rbp-24] # %rbp-24 <- storage of ptr on stack
add %rax, 4
mov QWORD PTR [%rbp-32], %rax # %rbp-32 <- storage of b2 on stack
Live Demo on CompilerExplorer
注意:
使用最现代的编译器版本进行编译将发出类似的
add
命令,但还会发出许多其他东西,(通过眼睛)不像使用旧版本的生成代码那样容易破译。因此,我选择了我能找到的最古老的gcc
。关于c++ - 当我为指针分配C++中派生类的指针时,地址发生变化,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/62425019/