web-dev-qa-db-ja.com

ポインター値は異なりますが、比較は同じです。どうして?

短い例では、奇妙な結果が出力されます!

#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

私が不思議に思うのは:

x003E9A9Cは0x003E9A98と等しくありませんが、出力は「bはcと等しい」

71
xmllmx

Cオブジェクトには、A型とB型の2つのサブオブジェクトが含まれます。明らかに、2つの別々のオブジェクトが同じアドレスを持つことはできないため、これらには異なるアドレスが必要です。したがって、これらの多くてもCオブジェクトと同じアドレスを持つことができます。そのため、ポインターを印刷すると異なる値が得られます。

ポインターの比較は、単に数値を比較するだけではありません。比較できるのは同じタイプのポインターのみであるため、最初のポインターは他のポインターと一致するように変換する必要があります。この場合、cB*に変換されます。これは、最初にbを初期化するために使用されるのとまったく同じ変換です。BオブジェクトではなくCサブオブジェクトを指すようにポインター値を調整します、および2つのポインターは等しく比較されます。

87
Mike Seymour

タイプCのオブジェクトのメモリレイアウトは次のようになります。

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

オブジェクトのアドレスからのバイト単位のオフセットを追加しました(sizeof(int)= 4のようなプラットフォームの場合)。

メインには2つのポインターがあります。わかりやすくするために、pbpcに名前を変更します。 pcはCオブジェクト全体の開始を指し、pbはBサブオブジェクトの開始を指します。

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

これが、値が異なる理由です。 3E9A98 + 4は3E9A9C、16進数です。

これら2つのポインターを比較すると、コンパイラーは、異なるタイプのB*C*の比較を確認します。そのため、暗黙の変換があれば、それを適用する必要があります。 pbC*に変換できませんが、逆の方法も可能です-pcB*に変換します。この変換により、pcが指す場所のBサブオブジェクトを指すポインターが得られます。これは、B* pb = pc;を定義したときに使用される暗黙的な変換と同じです。結果はpbと等しく、明らかに:

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

そのため、2つのポインターを比較するとき、コンパイラーは実際、変換されたポインターを比較しますが、これらは等しいです。

74
Arne Mertz

答えがあることは知っていますが、おそらくこれはより簡単で、例によって裏付けられるでしょう。

ここでif (b == c)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

したがって、cB*型にキャストすると、bと同じアドレスになります。予想通り。

8
luk32

マイクのすばらしい答えに加えて、あなたが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]
7
Nobilis

コンピューティング(または、数学では言うべき)では、平等の多くの概念があります。対称的、再帰的、推移的な関係は、平等として使用できます。

あなたのプログラムでは、2つのやや異なる等式の概念を調べています:ビット単位の実装ID(2つのポインターがまったく同じアドレスを指す)対オブジェクトIDに基づく別の種類の等式。同じオブジェクトを参照していると適切に見なされる静的型。

これらの異なるタイプのビューは、オブジェクトの異なる部分にラッチするため、同じアドレス値を持たないポインターを使用します。コンパイラはこれを認識しているため、このオフセットを考慮に入れた等値比較の正しいコードを生成します。

これらのオフセットを必要とするのは、継承によってもたらされるオブジェクトの構造です。複数のベースがある場合(複数の継承のおかげ)、それらのベースの1つだけがオブジェクトの下位アドレスになり、ベース部分へのポインターは派生オブジェクトへのポインターと同じになります。他のベース部分は、オブジェクト内のどこかにあります。

したがって、ポインタの単純なビット単位の比較では、オブジェクトのオブジェクト指向ビューに従って正しい結果が得られません。

2
Kaz

ここにいくつかの良い答えがありますが、短いバージョンがあります。 「2つのオブジェクトが同じ」ということは、同じアドレスを持っているという意味ではありません。それは、それらにデータを入れ、それらからデータを取り出すことは同等であることを意味します。

1