c++ - Los valores del puntero son diferentes pero se comparan igual. ¿Por qué?
pointers multiple-inheritance (6)
Algunas buenas respuestas aquí, pero hay una versión corta. "Dos objetos son iguales" no significa que tengan la misma dirección. Significa poner datos en ellos y extraer datos de ellos es equivalente.
¡Un pequeño ejemplo genera un resultado extraño!
#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;
}
}
Es muy sorprendente para mí que la salida sea la siguiente:
The address of b is 0x003E9A9C
The address of c is 0x003E9A98
b is equal to c
Lo que me hace preguntar es:
0x003E9A9C no es igual a 0x003E9A98, pero la salida es "b es igual a c"
El diseño de la memoria de un objeto de tipo C
se verá más o menos así:
| <---- C ----> |
|-A: a-|-B: b-|- c -|
0 4 8 12
Agregué el desplazamiento en bytes desde la dirección del objeto (en una plataforma como la tuya con sizeof (int) = 4).
En general, tienes dos punteros, los pb
a pb
y a pc
para mayor claridad. pc
apunta al inicio de todo el objeto C, mientras que pb
apunta al inicio del subobjeto B:
| <---- C ----> |
|-A: a-|-B: b-|- c -|
0 4 8 12
pc-^ pb-^
Esta es la razón por la cual sus valores son diferentes. 3E9A98 + 4 es 3E9A9C, en hex.
Si ahora compara esos dos indicadores, el compilador verá una comparación entre B*
y C*
, que son tipos diferentes. Entonces tiene que aplicar una conversión implícita, si es que hay una. pb
no se puede convertir en C*
, pero al revés es posible: convierte pc
en B*
. Esa conversión dará un puntero que apunte al subobjeto B de donde sea que apunte pc
- es la misma conversión implícita que se usa cuando se define B* pb = pc;
. El resultado es igual a pb
, obviamente:
| <---- C ----> |
|-A: a-|-B: b-|- c -|
0 4 8 12
pc-^ pb-^
(B*)pc-^
Entonces cuando se comparan los dos indicadores, el compilador de hecho compara los punteros convertidos, que son iguales.
En computación (o, mejor dicho, deberíamos decir en matemáticas) puede haber muchas nociones de igualdad. Cualquier relación que sea simétrica, reflexiva y transitiva puede emplearse como igualdad.
En su programa, está examinando dos nociones algo diferentes de igualdad: identidad de implementación bit a bit (dos punteros son exactamente la misma dirección) versus otro tipo de igualdad basada en identidad de objeto, que permite dos vistas en el mismo objeto, a través de referencias de diferentes tipo estático, para ser considerado como referencia del mismo objeto.
Estas vistas de tipos diferentes utilizan punteros que no tienen el mismo valor de dirección, porque se adhieren a diferentes partes del objeto. El compilador lo sabe y, por lo tanto, genera el código correcto para la comparación de igualdad que tiene en cuenta este desplazamiento.
Es la estructura de los objetos provocados por la herencia lo que hace necesario tener estos desplazamientos. Cuando hay varias bases (gracias a la herencia múltiple), solo una de esas bases puede estar en la dirección baja del objeto, por lo que el puntero a la parte base es el mismo que el puntero al objeto derivado. Las otras partes de la base están en otra parte del objeto.
Por lo tanto, la comparación ingenua y en bits de los punteros no arrojaría los resultados correctos de acuerdo con la vista orientada a objetos del objeto.
Sé que hay una respuesta, pero tal vez esto sea más sencillo y respaldado por un ejemplo.
Hay una conversión implícita de C*
a B*
en el operando c
aquí if (b == c)
Si vas con este código:
#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;
}
}
Usted obtiene:
The address of b is 0x0x88f900c
The address of c is 0x0x88f9008
The address of (B*)c is 0x0x88f900c
b is equal to c
Por lo tanto, c
casted to B*
type tiene la misma dirección que b
. Como se esperaba.
Si puedo agregar a la excelente respuesta de Mike, si los cataloga como void*
entonces obtendrá su comportamiento esperado:
if ((void*)(b) == (void*)(c))
^^^^^^^ ^^^^^^^
huellas dactilares
b is not equal to c
Hacer algo similar en C (el lenguaje) en realidad irritó al compilador debido a los diferentes tipos de punteros comparados.
Tengo:
warning: comparison of distinct pointer types lacks a cast [enabled by default]
Un objeto C
contiene dos sub-objetos, de los tipos A
y B
Obviamente, estos deben tener direcciones diferentes ya que dos objetos separados no pueden tener la misma dirección; entonces, como máximo, uno de ellos puede tener la misma dirección que el objeto C
Es por eso que imprimir los punteros da valores diferentes.
Comparar los punteros no solo compara sus valores numéricos. Solo se pueden comparar punteros del mismo tipo, por lo que el primero debe convertirse para coincidir con el otro. En este caso, c
se convierte en B*
. Esta es exactamente la misma conversión utilizada para inicializar b
en primer lugar: ajusta el valor del puntero para que apunte al subobjeto B
en lugar de al objeto C
, y los dos punteros ahora se comparan iguales.