web-dev-qa-db-fra.com

Les valeurs du pointeur sont différentes mais elles se comparent égales. Pourquoi?

Un court exemple donne un résultat étrange!

#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;
    }
}

C'est très surprenant pour moi que la sortie soit la suivante:

The address of b is 0x003E9A9C
The address of c is 0x003E9A98
b is equal to c

Ce qui me fait me demander, c'est:

x003E9A9C n'est pas égal à 0x003E9A98, mais la sortie est "b est égal à c"

71
xmllmx

Un objet C contient deux sous-objets, de types A et B. Évidemment, ceux-ci doivent avoir des adresses différentes car deux objets distincts ne peuvent pas avoir la même adresse; donc au plus l'un d'entre eux peut avoir la même adresse que l'objet C. C'est pourquoi l'impression des pointeurs donne des valeurs différentes.

La comparaison des pointeurs ne compare pas simplement leurs valeurs numériques. Seuls les pointeurs du même type peuvent être comparés, donc le premier doit être converti pour correspondre à l'autre. Dans ce cas, c est converti en B*. C'est exactement la même conversion utilisée pour initialiser b en premier lieu: il ajuste la valeur du pointeur de sorte qu'il pointe vers le sous-objet B plutôt que l'objet C , et les deux pointeurs se comparent désormais égaux.

87
Mike Seymour

La disposition de la mémoire d'un objet de type C ressemblera à ceci:

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

J'ai ajouté le décalage en octets de l'adresse de l'objet (dans une plate-forme comme la vôtre avec sizeof (int) = 4).

Dans votre principal, vous avez deux pointeurs, je vais les renommer en pb et pc pour plus de clarté. pc pointe vers le début de l'ensemble de l'objet C, tandis que pb pointe vers le début du sous-objet B:

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

C'est la raison pour laquelle leurs valeurs sont différentes. 3E9A98 + 4 est 3E9A9C, en hex.

Si vous comparez maintenant ces deux pointeurs, le compilateur verra une comparaison entre un B* et un C*, qui sont de types différents. Il doit donc appliquer une conversion implicite, s'il y en a une. pb ne peut pas être converti en C*, mais l'inverse est possible - il convertit pc en B*. Cette conversion donnera un pointeur qui pointe vers le sous-objet B de partout où pc pointe - c'est la même conversion implicite utilisée lorsque vous avez défini B* pb = pc;. Le résultat est égal à pb, évidemment:

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

Ainsi, lors de la comparaison des deux pointeurs, le compilateur compare en fait les pointeurs convertis, qui sont égaux.

74
Arne Mertz

Je sais qu'il y a une réponse, mais ce sera peut-être plus simple et étayé par un exemple.

Il y a une conversion implicite de C* En B* Sur l'opérande c ici if (b == c)

Si vous allez avec ce code:

#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;
    }
}

Vous obtenez:

The address of b is 0x0x88f900c
The address of c is 0x0x88f9008
The address of (B*)c is 0x0x88f900c
b is equal to c

Ainsi, c converti en type B* A la même adresse que b. Comme prévu.

8
luk32

Si je peux ajouter à l'excellente réponse de Mike, si vous les utilisez comme void* alors vous obtiendrez votre comportement attendu:

if ((void*)(b) == (void*)(c))
    ^^^^^^^       ^^^^^^^

impressions

b is not equal to c

Faire quelque chose de similaire sur C (le langage) a irrité le compilateur en raison des différents types de pointeurs comparés.

J'ai eu:

warning: comparison of distinct pointer types lacks a cast [enabled by default]
7
Nobilis

En informatique (ou plutôt en mathématiques), il peut y avoir de nombreuses notions d'égalité. Toute relation symétrique, réflexive et transitive peut être utilisée comme égalité.

Dans votre programme, vous examinez deux notions d'égalité quelque peu différentes: l'identité d'implémentation au niveau du bit (deux pointeurs étant exactement à la même adresse) par rapport à un autre type d'égalité basé sur l'identité d'objet, qui permet deux vues sur le même objet, à travers des références de différents type statique, pour être correctement considéré comme référençant le même objet.

Ces vues de type différent utilisent des pointeurs qui n'ont pas la même valeur d'adresse, car ils s'accrochent à différentes parties de l'objet. Le compilateur le sait et génère donc le code correct pour la comparaison d'égalité qui prend en compte ce décalage.

C'est la structure des objets provoquée par l'héritage qui nécessite d'avoir ces décalages. Lorsqu'il existe plusieurs bases (grâce à l'héritage multiple), une seule de ces bases peut se trouver à la basse adresse de l'objet, de sorte que le pointeur vers la partie de base est le même que le pointeur vers l'objet dérivé. Les autres parties de base se trouvent ailleurs dans l'objet.

Ainsi, une comparaison naïve et au niveau du bit des pointeurs ne donnerait pas les résultats corrects selon la vue orientée objet de l'objet.

2
Kaz

Quelques bonnes réponses ici, mais il existe une version courte. "Deux objets sont identiques" ne signifie pas qu'ils ont la même adresse. Cela signifie y mettre des données et en retirer des données est équivalent.

1
Isaac Rabinovitch