指针值是不同的,但它们比较起来是相等的。为什么?

一个简短的例子输出一个奇怪的结果!

#include <iostream>


using namespace std;


struct A { int a; };
struct B { int b; };
struct C : A, B
{
int c;
};


int main()
{
C* c = new C;
B* b = c;


cout << "The address of b is 0x" << hex << b << endl;
cout << "The address of c is 0x" << hex << c << endl;


if (b == c)
{
cout << "b is equal to c" << endl;
}
else
{
cout << "b is not equal to c" << endl;
}
}

令我非常惊讶的是,结果应该是这样的:

The address of b is 0x003E9A9C
The address of c is 0x003E9A98
b is equal to c

让我想知道的是:

0x003E9A9C 不等于0x003E9A98,但是输出是“ b 等于 c”

10232 次浏览

C对象包含两个子对象,类型为 AB。显然,这些对象必须具有不同的地址,因为两个不同的对象不能具有相同的地址; 所以最多其中一个对象可以具有与 C对象相同的地址。这就是为什么打印指针会给出不同的值。

比较指针不仅仅是比较它们的数值。只能比较相同类型的指针,因此必须将第一个指针转换为与另一个指针匹配。在这种情况下,c被转换为 B*。这与初始化 b时使用的转换完全相同: 它调整指针值,使其指向 B子对象而不是 C对象,现在两个指针比较相等。

如果我可以补充迈克的绝妙答案,如果你把他们当作 void*,那么你将得到你所期望的行为:

if ((void*)(b) == (void*)(c))
^^^^^^^       ^^^^^^^

指纹

b is not equal to c

在 C (语言)上做类似的操作实际上会因为比较不同类型的指针而激怒编译器。

我有:

warning: comparison of distinct pointer types lacks a cast [enabled by default]

C类型对象的内存布局如下:

|   <---- C ---->   |
|-A: a-|-B: b-|- c -|
0      4      8     12

我从对象的 Address (在类似于 sizeof (int) = 4的平台中)添加了以字节为单位的偏移量。

在主要部分中,您有两个指针,为了清楚起见,我将它们重命名为 pbpcpc指向整个 C 对象的开始,而 pb指向 B 对象的开始:

   |   <---- C ---->   |
|-A: a-|-B: b-|- c -|
0      4      8     12
pc-^   pb-^

这就是为什么它们的值不同的原因。3E9A98 + 4是3E9A9C,十六进制。

如果您现在比较这两个指针,编译器将看到 B*C*之间的比较,它们是不同的类型。因此,如果存在隐式转换的话,它必须应用隐式转换。pb不能转换成 C*,但是反过来也是可能的-它可以把 pc转换成 B*。这个转换将给出一个指向 pc所指向的 B 子对象的指针-它与定义 B* pb = pc;时使用的隐式转换相同。结果显然等于 pb:

   |   <---- C ---->   |
|-A: a-|-B: b-|- c -|
0      4      8     12
pc-^   pb-^
(B*)pc-^

因此,当比较两个指针时,编译器实际上会比较转换后的指针,它们是相等的。

我知道有一个答案,但也许这将是更直接和支持的一个例子。

在这里的 if (b == c)操作数上有一个从 C*B*的隐式转换

如果你用这个代码:

#include <iostream>


using namespace std;


struct A { int a; };
struct B { int b; };
struct C : A, B
{
int c;
};


int main()
{
C* c = new C;
B* b = c;


cout << "The address of b is 0x" << hex << b << endl;
cout << "The address of c is 0x" << hex << c << endl;
cout << "The address of (B*)c is 0x" << hex << (B*)c << endl;


if (b == c)
{
cout << "b is equal to c" << endl;
}
else
{
cout << "b is not equal to c" << endl;
}
}

你会得到:

The address of b is 0x0x88f900c
The address of c is 0x0x88f9008
The address of (B*)c is 0x0x88f900c
b is equal to c

因此,c转换为 B*类型与 b具有相同的地址。

在计算(或者说,我们应该说在数学中)中可以有许多平等的概念。任何对称的、自反的和传递的关系都可以用作等式。

在您的程序中,您正在检查两个稍微不同的相等概念: 按位实现标识(两个指针指向完全相同的地址)与另一种基于对象标识的相等,后者允许通过不同静态类型的引用对同一对象的两个视图被恰当地视为对同一对象的引用。

这些不同类型的视图使用不具有相同地址值的指针,因为它们锁定对象的不同部分。编译器知道这一点,因此它为相等性比较生成正确的代码,该比较考虑到了这个偏移量。

正是继承带来的对象结构使得有必要拥有这些偏移量。当有多个基(多亏了多重继承)时,只有其中一个基可以位于对象的低地址,因此指向基部的指针与指向派生对象的指针相同。其他基本部分在物体的其他地方。

因此,根据面向对象的视图,天真的、按位比较指针不会产生正确的结果。

这里有一些很好的答案,但也有一个简短的版本。“两个对象是相同的”并不意味着他们有相同的地址。这意味着把数据输入它们和从它们中提取数据是等价的。