Значения указателей различны, но они сравниваются одинаково. Зачем?


короткий пример выводит странный результат!

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

6 ответов:

A C объект содержит два подобъекта, типов A и B. Очевидно, что они должны иметь разные адреса, так как два отдельных объекта не могут иметь один и тот же адрес; поэтому не более одного из них может иметь тот же адрес, что и

макет памяти объекта типа C будет выглядеть примерно так:

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

я добавил смещение в байтах от адреса объекта(в платформе, подобной вашей с sizeof (int) = 4).

в вашем основном, у вас есть два указателя, я переименую их в pb и pc для ясности. pc указывает на начало всего объекта C, в то время как pb указывает на начало подобъекта B:

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

это причина, почему их ценности различны. 3E9A98+4 - это 3E9A9C, в шестнадцатеричном виде.

если вы теперь сравните эти два указателя, компилятор увидит сравнение между a B* и C*, которые являются различными типами. Поэтому он должен применить неявное преобразование, если оно есть. pb не может быть преобразован в C*, но возможен и другой путь-он преобразует pc на B*. Это преобразование даст указатель, который указывает на B подобъект wherever pc точки - это то же самое неявное преобразование, используемое при определении B* pb = pc;. Результат равен pb, очевидно:

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

поэтому при сравнении двух указателей компилятор фактически сравнивает преобразованные указатели, которые равны.

Я знаю, что есть ответ, но, возможно, это будет более простым и подкреплена примером.

существует неявное преобразование из C* до B* on 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. Как и ожидалось.

если я могу добавить к превосходному ответу Майка, если вы бросите их как 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]

в вычислениях (или, скорее, мы должны сказать, в математике) может быть много понятий равенства. Любое отношение, которое является симметричным, рефлексивным и транзитивным, может использоваться как равенство.

в вашей программе вы рассматриваете два несколько разных понятия равенства: идентичность побитовой реализации (два указателя на один и тот же адрес) и другой вид равенства, основанный на идентичности объекта, который позволяет два представления об одном и том же объекте, через ссылки другой статический тип, чтобы правильно рассматривать как ссылку на один и тот же объект.

эти по-разному типизированные представления используют указатели, которые не имеют одного и того же значения адреса, поскольку они фиксируются на разных частях объекта. Компилятор знает это и поэтому он генерирует правильный код для сравнения равенства, который учитывает это смещение.

именно структура объектов, вызванных наследованием, делает необходимым наличие этих смещений. Когда существует несколько баз (благодаря множественному наследованию), только одна из этих баз может быть в Нижнем адресе объекта, так что указатель на базовую часть совпадает с указателем на производный объект. Другие базовые части находятся в другом месте объекта.

таким образом, наивное, побитовое сравнение указателей не даст правильных результатов в соответствии с объектно-ориентированным представлением объекта.

некоторые хорошие ответы здесь, но есть короткая версия. "Два объекта одинаковы" не означает, что они имеют один и тот же адрес. Это означает, что ввод данных в них и извлечение данных из них эквивалентно.