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


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”


13
我看不出这个问题与C(语言)有什么关系。标题和标签似乎与问题的内容不一致。
CB Bailey

而您得到的奇怪结果到底是什么呢?
molbdnilo

6
我已固定标题,如果您不同意,请复查并回滚。
jrok

2
@PeterT就是整个问题所在,什么显然使OP感到惊讶:为什么原始地址不同,而直接比较则相反。演员只会掩饰令人惊讶的差异,而不能解释;-)
Arne Mertz

2
我想拍四张闭上票的推特。平等(意味着什么以及可以存在多少种)的概念对于数学和计算至关重要。
卡兹(Kaz)2013年

Answers:


87

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

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


ptrdiff_t在比较之前强制转换为a可以避免导致相等的转换?或许更正确地说,这是ptrdiff_t演员阵容的合法适用吗?还是应该void*
jww

75

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

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

我添加了与对象地址的偏移量(以字节为单位)(在像您的平台中,sizeof(int)= 4)。

在你的主,你有两个指针,我将它们重命名为pbpc的清晰度。pc指向整个C对象pb的开始,而指向B子对象的开始:

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

这就是它们的值不同的原因。3E9A98 + 4是3E9A9C(十六进制)。

如果现在比较这两个指针,则编译器将看到aB*和a之间的比较C*,它们是不同的类型。因此,必须进行隐式转换(如果有的话)。pb无法将其转换为C*,但反之亦然-将其转换pcB*。该转换将提供一个指向B子对象的指针,无论该子对象pc指向何处-它与定义时所使用的隐式转换相同B* pb = pc;。结果等于pb,显然:

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

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


8

我知道有一个答案,但是也许这会更直接并得到一个示例的支持。

有一个隐式转换,从C*B*c操作这里if (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。如预期的那样。


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]

2

在计算中(或者更确切地说,应该在数学中说),可以有很多相等的概念。任何对称,自反和及物的关系都可以视为平等。

在您的程序中,您正在研究两种稍有不同的相等性概念:逐位实现身份(两个指针指向完全​​相同的地址)与另一种基于对象身份的相等性,后者通过不同的引用允许同一对象的两个视图静态类型,可以适当地视为引用同一对象。

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

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

因此,根据对象的面向对象的观点,对指针进行幼稚的按位比较不会产生正确的结果。


1

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


1
我想分享的故事不属于答案。接受过技术写作工作面试后,我被要求解释C中的指针。我的答案涉及类型检查,反引用等。这被认为太挑剔了;他们寻找的答案是“指针是内存地址”。我认为该示例表明该答案过于简单。
艾萨克·拉比诺维奇
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.