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 is equal to c" です

4

6 に答える 6

87

オブジェクトには、タイプとの 2 つのサブCオブジェクトが含まれます。明らかに、2 つの個別のオブジェクトが同じアドレスを持つことはできないため、これらは異なるアドレスを持つ必要があります。そのため、オブジェクトと同じアドレスを持つことができるのは、これらのうちの 1 つだけです。そのため、ポインターを出力すると異なる値が得られます。ABC

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

于 2013-08-16T12:12:07.830 に答える
76

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

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

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

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

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

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

これら 2 つのポインターを比較すると、コンパイラーは、異なる型である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-^

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

于 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

そのため、 type にcキャストするとB*、 と同じアドレスになり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

コンピューティング (というか、数学で言うべきです) では、多くの等価概念が存在する可能性があります。対称、再帰的、推移的な関係はすべて、等式として使用できます。

プログラムでは、ビットごとの実装 ID (2 つのポインターがまったく同じアドレスを指している) と、オブジェクト ID に基づく別の種類の同等性 (異なるオブジェクトの参照を通じて同じオブジェクトの 2 つのビューを許可する) という 2 つのやや異なる等価概念を調べています。 static 型であり、同じオブジェクトを参照していると正しく見なされます。

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

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

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

于 2013-08-16T19:00:43.807 に答える
0

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

于 2013-08-22T20:15:47.943 に答える