71

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

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

4

6 回答 6

87

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

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

于 2013-08-16T12:12:07.830 回答
76

类型对象的内存布局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,以十六进制表示。

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

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

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

于 2013-08-16T12:21:12.733 回答
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*type 具有与 相同的地址b。正如预期的那样。

于 2013-08-16T12:22:48.693 回答
7

如果我可以添加到迈克的出色答案中,如果您将它们转换为那样,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]
于 2013-08-16T12:18:58.567 回答
2

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

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

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

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

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

于 2013-08-16T19:00:43.807 回答
0

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

于 2013-08-22T20:15:47.943 回答