指针值不同但它们相等。为什么?

时间:2013-08-16 12:03:50

标签: c++ pointers multiple-inheritance

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

#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”

6 个答案:

答案 0 :(得分:87)

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

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

答案 1 :(得分:74)

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

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

我在对象的地址中添加了偏移量(在像你这样的平台中,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-^

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

答案 2 :(得分:8)

我知道有一个答案,但也许这会更直接,并通过一个例子备份。

此处C*

B*操作数上隐式转换cif (b == c)

如果你使用这段代码:

#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的地址相同。正如所料。

答案 3 :(得分:7)

如果我可以添加Mike的优秀答案,如果你将它们转换为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]

答案 4 :(得分:2)

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

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

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

这是由继承带来的对象的结构,这使得必须具有这些偏移。当存在多个基数时(由于多重继承),这些基数中只有一个可以位于对象的低地址,因此指向基本部分的指针与指向派生对象的指针相同。其他基础部分在对象的其他地方。

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

答案 5 :(得分:1)

这里有一些好的答案,但有一个简短的版本。 “两个对象是相同的”并不意味着它们具有相同的地址。这意味着将数据放入其中并从中获取数据是等效的。