C++ 理解c++;指向父/基类的指针
有人问我这个面试问题,我弄错了。“输出是什么”:我的答案是135,实际输出是136。这意味着指向两个父类的指针不相等,即使它们通过了先前的测试,即等于子类。 我以为我理解C++指针,但这让我难于解释。虽然我想我知道发生了什么,但我不知道为什么。任何C++专家都能提供技术解释吗? 看起来前两个比较在本质上更符合逻辑,而最后一个比较更符合字面意义C++ 理解c++;指向父/基类的指针,c++,pointers,inheritance,C++,Pointers,Inheritance,有人问我这个面试问题,我弄错了。“输出是什么”:我的答案是135,实际输出是136。这意味着指向两个父类的指针不相等,即使它们通过了先前的测试,即等于子类。 我以为我理解C++指针,但这让我难于解释。虽然我想我知道发生了什么,但我不知道为什么。任何C++专家都能提供技术解释吗? 看起来前两个比较在本质上更符合逻辑,而最后一个比较更符合字面意义 #include <iostream> class A { public: A() : m_i(0) { } pr
#include <iostream>
class A
{
public:
A() : m_i(0) { }
protected:
int m_i;
};
class B
{
public:
B() : m_d(0.0) { }
protected:
double m_d;
};
class C
: public A, public B
{
public:
C() : m_c('a') { }
private:
char m_c;
};
int main()
{
C c;
A *pa = &c;
B *pb = &c;
const int x = (pa == &c) ? 1 : 2;
const int y = (pb == &c) ? 3 : 4;
const int z = (reinterpret_cast<char*>(pa) == reinterpret_cast<char*>(pb)) ? 5 : 6;
std::cout << x << y << z << std::endl;
return 0;
}
#包括
甲级
{
公众:
A():m_i(0){}
受保护的:
国际货币基金组织;
};
B类
{
公众:
B():mud(0.0){}
受保护的:
双m_d;
};
C类
:公共A、公共B
{
公众:
C():m_C('a'){}
私人:
charm_c;
};
int main()
{
C C;
A*pa=&c;
B*pb=&c;
常数int x=(pa==&c)?1:2;
常数y=(pb==&c)?3:4;
const int z=(重新解释铸造(pa)=重新解释铸造(pb))?5:6;
std::cout如果您打印出pa
和pb
可能会更清楚发生了什么
std::cout << "A: " << pa << std::endl;
std::cout << "B: " << pb << std::endl;
std::cout << "C: " << &c << std::endl;
(您的输出可能不同,重要的是它们并不都相等)
这是因为C
对象在内存中的表示方式。由于C
既是a
又是B
,它需要每个部分的数据成员。C
的实际布局是这样的(忽略填充):
当您将C*
转换为a*
时,编译器知道a
部分从偏移量0开始,因此指针值不会改变。对于C*
到B*
需要偏移sizeof(int)
(加上填充)。对于计算x
和y
,此偏移处理会自动完成。对于z
,由于使用重新解释强制转换C++通常会一个接一个地布置类结构,因此它被忽略。让我们检查以下代码示例:
#include <iostream>
#include <stdio.h>
//#define DO_PACKSTRUCTURES
//#define DEFINE_VFUNC
#ifdef DO_PACKSTRUCTURES
#pragma pack(push, 1)
#endif
class A
{
public:
A() : m_i(0) { }
#ifdef DEFINE_VFUNC
virtual
#endif
void myfunc()
{
}
protected:
int m_i;
};
class B
{
public:
B() : m_d(0.0) { }
#ifdef DEFINE_VFUNC
virtual
#endif
void myfunc2()
{
}
protected:
double m_d;
};
class C
: public A, public B
{
public:
C() : m_c('a') { }
#ifdef DEFINE_VFUNC
virtual
#endif
void myfunc3()
{
}
char m_c;
};
#ifdef DO_PACKSTRUCTURES
#pragma pack(pop)
#endif
void pprint(char* prefix, void* p)
{
printf("%s = %p\r\n", prefix, p);
}
int main()
{
C c;
A *pa = &c;
B *pb = &c;
pprint("&c", &c);
pprint("pa", pa);
printf("\r\n");
pprint("pb", pb);
pprint("pa + sizeof(A)", ((char*)pa) + sizeof(A));
printf("\r\n");
pprint("&c.m_c", &c.m_c);
pprint("pb + sizeof(B)", ((char*)pb) + sizeof(B));
printf("\r\n");
printf("sizeof(A)=%d\r\n", sizeof(A));
printf("sizeof(B)=%d\r\n", sizeof(B));
printf("sizeof(C)=%d\r\n", sizeof(C));
printf("sizeof(int)=%d\r\n", sizeof(int));
printf("sizeof(double)=%d\r\n", sizeof(double));
printf("sizeof(char)=%d\r\n", sizeof(char));
printf("sizeof(void*)=%d\r\n", sizeof(void*));
pa->myfunc();
c.myfunc2();
c.myfunc3();
return 0;
}
指向C*的指针与A*的指针相同,因为A是内存布局开始的第一个基类
C*:
A
B
C members (m_c)
pb不等于(pa+sizeof(A))-因为编译器在A和B之间添加了一些对齐字节以加快对B的访问。不确定这些优化有多重要-使同一类的数百万实例可能会对性能产生影响
Win32/Debug或Release/DO_PACKSTRUCTURES已定义&DEFINE_VFUNC未定义:
&c = 00BBF7A4
pa = 00BBF7A4
pb = 00BBF7AC
pa + sizeof(A) = 00BBF7A8
& c.m_c = 00BBF7B4
pb + sizeof(B) = 00BBF7B4
sizeof(A) = 4
sizeof(B) = 8
sizeof(C) = 24
sizeof(int) = 4
sizeof(double) = 8
sizeof(char) = 1
sizeof(void*) = 4
&c = 00B9F770
pa = 00B9F770
pb = 00B9F774
pa + sizeof(A) = 00B9F774
&c.m_c = 00B9F77C
pb + sizeof(B) = 00B9F77C
sizeof(A)=4
sizeof(B)=8
sizeof(C)=13
sizeof(int)=4
sizeof(double)=8
sizeof(char)=1
sizeof(void*)=4
现在我们没有添加任何对齐或填充字节(因为#pragma pack(push,1))-我们将C类变小了,现在pb==(pa+sizeof(A))。现在我们还可以看到什么是“分配”C类,即sizeof(int)/sizeof(double)+sizeof(char)=4+8+1=13
Win32/Debug或Release/DO_PACKSTRUCTURES&DEFINE_VFUNC已定义:
&c = 007EFCF4
pa = 007EFCF4
pb = 007EFCFC
pa + sizeof(A) = 007EFCFC
&c.m_c = 007EFD08
pb + sizeof(B) = 007EFD08
sizeof(A)=8
sizeof(B)=12
sizeof(C)=21
sizeof(int)=4
sizeof(double)=8
sizeof(char)=1
sizeof(void*)=4
与前一种情况一样,我们仍然有指针计算匹配,但是如果我们使用sizeof(类)计算大小,我们将得到正确的大小,但不会得到sizeof(成员类型)-因为'virtual'关键字-virtual关键字本身正在分配额外的虚拟表指针大小
It reflects 21 - 8 - 4 - 1 = 8
8 / sizeof(void*) = 2 - that's two vtables - one from A and another from B class instances.
我不确定为什么C类本身没有自己的vtable——据我所知,它应该有vtable。这是稍后要解决的问题。
微软Visual C++编译器还具有特殊类型的关键字:这也反映了vtable的生成方式。但这是普通开发人员不需要的,除非你正在处理高级COM编程。@SethKitchen我想你的意思是他们有相同的动态类型,但不同的静态类型?pa
是一个A
(静态)指向一个C
(动态),而pb
是指向C
(动态)的B
(静态)你可以考虑添加<代码>语言律师< /代码>标签来解释为什么会发生这种情况。故事的寓意是:不要使用多重继承来代替任何混合。实际上我做了所有这些。我困惑的是为什么陈述:const int y=(Pb==& c)。?3:4;计算结果为true,正如您刚才指出的那样,这在技术上是不正确的。指向“b”的指针显然不等于指向“c”的指针。如果运行此程序,您将看到输出为136。请查看定义b*pb=&c;
现在您看到了吗?如果您不仔细查看。如果您不继续阅读。y=(pb=&c)?3:4
.pb==&c是正确的,因为它是这样定义的,您将&c
的地址分配给bd
。稍后还会进行布尔比较,检查pb==&c
是否正确。当然正确!希望清除它。注意pb不同于&c。但这没关系,因为&c是c作为c*
的地址>。当您比较pb==&c
时,您不能将pb
向上转换为ac
,但可以将c
向下转换为aB
,当您进行比较时,比较结果为真-它产生与赋值相同的结果,因此比较结果为真。@kfsone-您是说在对pb的比较中存在隐式向下转换吗==&c?如果我要在代码中复制它,我会使用哪种类型的转换-reinterpret_cast?@Gio A reinterpret_cast保持指针值不变,但只是将指针(顾名思义)重新解释为其他内容。正如您所看到的,向下转换可能会更改指针的值。要在代码中手动执行此操作(您不想这样做)您可以执行类似于reinterpret\u cast(reinterpret\u cast(&c)+sizeof(A))
的操作。这会忽略填充,并且是特定于编译器的。编译器为您执行此类操作的原因之一。要执行显式向下转换,您可以使用静态\u cast
。
&c = 007EFCF4
pa = 007EFCF4
pb = 007EFCFC
pa + sizeof(A) = 007EFCFC
&c.m_c = 007EFD08
pb + sizeof(B) = 007EFD08
sizeof(A)=8
sizeof(B)=12
sizeof(C)=21
sizeof(int)=4
sizeof(double)=8
sizeof(char)=1
sizeof(void*)=4
It reflects 21 - 8 - 4 - 1 = 8
8 / sizeof(void*) = 2 - that's two vtables - one from A and another from B class instances.