IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

 C++ Discussion :

Passage par référence


Sujet :

C++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre émérite
    Avatar de Antoine_935
    Profil pro
    Développeur web/mobile
    Inscrit en
    Juillet 2006
    Messages
    883
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations professionnelles :
    Activité : Développeur web/mobile

    Informations forums :
    Inscription : Juillet 2006
    Messages : 883
    Par défaut Passage par référence
    Salut à tous.

    Actuellement en train d'étudier en détail le C++, une petite question se pose à moi.
    Elle concerne le passage de variables par références. Etant habitué au C, c'est une nouveauté pour moi, et je voudrais en saisir tous les détails.

    J'ai pu lire dans le tutoriel qu'un passage par référence n'est rien d'autre que le passage de la variable elle-même. Jusque là, pas de souci, c'est bien intégré.

    Ce que je ne suis pas sur d'avoir compris, c'est comment le compilateur peut effectuer ça. J'ai une petite idée, mais je serais incapable de l'expliquer (c'est dire à quel point c'est flou).

    Quelqu'un sait-il me donner des informations là dessus ?

  2. #2
    Invité(e)
    Invité(e)
    Par défaut
    Bonjour,
    Citation Envoyé par Antoine_935 Voir le message
    J'ai pu lire dans le tutoriel qu'un passage par référence n'est rien d'autre que le passage de la variable elle-même. Jusque là, pas de souci, c'est bien intégré.

    Ce que je ne suis pas sur d'avoir compris, c'est comment le compilateur peut effectuer ça. J'ai une petite idée, mais je serais incapable de l'expliquer (c'est dire à quel point c'est flou).

    Quelqu'un sait-il me donner des informations là dessus ?
    Alors, le meilleur moyen de savoir, c'est de regarder soi-même :
    Un code extrêmement simple :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    void fun_cpy(int c)    
    {
        c++;
    }
     
    void fun_ref(int &r)
    {
        r++;
    }
     
    int main(void) 
    {
        int i = 0;
     
        fun_cpy(i);
        fun_ref(i);
     
        return 0;
    }
    Deux fois la même fonction, la seule différence : dans un cas le passage de paramètre se fait pas copie, dans l'autre par référence.

    Compilons le tout avec gcc : g++ -c -S 09-05-26.cc résultat : le fichier assembleur 09-05-26.s est créé :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    	.file	"09-05-26.cc"
    	.text
    	.align 2
    .globl __Z7fun_cpyi
    	.def	__Z7fun_cpyi;	.scl	2;	.type	32;	.endef
    __Z7fun_cpyi:
    	pushl	%ebp
    	movl	%esp, %ebp
    	incl	8(%ebp)
    	popl	%ebp
    	ret
    	.align 2
    .globl __Z7fun_refRi
    	.def	__Z7fun_refRi;	.scl	2;	.type	32;	.endef
    __Z7fun_refRi:
    	pushl	%ebp
    	movl	%esp, %ebp
    	movl	8(%ebp), %eax
    	incl	(%eax)
    	popl	%ebp
    	ret
    	.def	___main;	.scl	2;	.type	32;	.endef
    	.align 2
    .globl _main
    	.def	_main;	.scl	2;	.type	32;	.endef
    _main:
    	pushl	%ebp
    	movl	%esp, %ebp
    	subl	$24, %esp
    	andl	$-16, %esp
    	movl	$0, %eax
    	addl	$15, %eax
    	addl	$15, %eax
    	shrl	$4, %eax
    	sall	$4, %eax
    	movl	%eax, -8(%ebp)
    	movl	-8(%ebp), %eax
    	call	__alloca
    	call	___main
    	movl	$0, -4(%ebp)
    	movl	-4(%ebp), %eax
    	movl	%eax, (%esp)
    	call	__Z7fun_cpyi
    	leal	-4(%ebp), %eax
    	movl	%eax, (%esp)
    	call	__Z7fun_refRi
    	movl	$0, %eax
    	leave
    	ret
    EDIT : A partir d'ici, je raconte des c******s
    On regarde dans le main comment sont appelées les deux fonctions :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    	movl	-4(%ebp), %eax
    	movl	%eax, (%esp)
    	call	__Z7fun_cpyi
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    	leal	-4(%ebp), %eax
    	movl	%eax, (%esp)
    	call	__Z7fun_refRi
    C'est la même chose ! Les deux fonctions sont appelées de la même façon.

    La différence se situe dans le code de la fonction lui même.

    Que fait la fonction qui travaille par copie ?
    La variable i est d'abord empilée depuis eax vers esp.
    La fonction va copier cette variable en local movl %esp, %ebp et l'incrémenter incl 8(%ebp).
    Dans le cas par référence, la fonction va récupérer la variable movl %esp, %ebp mais va travailler avec le registre eax (où est stoquée la variable i) movl 8(%ebp), %eax puis l'incrémente incl (%eax)
    Dernière modification par Invité(e) ; 27/05/2009 à 10h14.

  3. #3
    Membre émérite
    Avatar de Antoine_935
    Profil pro
    Développeur web/mobile
    Inscrit en
    Juillet 2006
    Messages
    883
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations professionnelles :
    Activité : Développeur web/mobile

    Informations forums :
    Inscription : Juillet 2006
    Messages : 883
    Par défaut
    Wow, merci pour cette réponse très utile
    A vrai dire ce n'est pas vraiment au passage des entiers que je pensais, mais plutôt à des structures / classes.
    Mais mnt que je connais l'option gcc pour ça, je vais pouvoir découvrir ça de moi-même.

    Un tout grand merci à toi

  4. #4
    Expert éminent
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 395
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 395
    Par défaut
    La réponse de mabu est fausse:
    Citation Envoyé par mabu Voir le message
    C'est la même chose ! Les deux fonctions sont appelées de la même façon.
    Note pour mabu: Se racheter une paire d'yeux, ceux-là sont périmés:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    	movl	-4(%ebp), %eax
    	movl	%eax, (%esp)
    	call	__Z7fun_cpyi
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    	leal	-4(%ebp), %eax
    	movl	%eax, (%esp)
    	call	__Z7fun_refRi
    Dans l'un, c'est la valeur qui est passée. Dans l'autre, c'est son adresse.

    Un exemple plus clair est de comparer avec le passage par valeur d'un pointeur:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    void fun_ref(int &r)
    {
        r++;
    }
     
    void fun_ptr(int *p)
    {
        (*p)++;
    }
     
    int main(void) 
    {
        int i = 0;
     
        fun_ref(i);
        fun_ptr(&i);
     
        return 0;
    }
    Je n'ai pas le temps de compiler, mais c'est généralement le même code assembleur:
    Un passage par référence non-constante, c'est du sucre syntaxique: Un passage par pointeur déguisé.
    SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.

    "Aw, come on, who would be so stupid as to insert a cast to make an error go away without actually fixing the error?"
    Apparently everyone.
    -- Raymond Chen.
    Traduction obligatoire: "Oh, voyons, qui serait assez stupide pour mettre un cast pour faire disparaitre un message d'erreur sans vraiment corriger l'erreur?" - Apparemment, tout le monde. -- Raymond Chen.

  5. #5
    Invité(e)
    Invité(e)
    Par défaut
    Citation Envoyé par Médinoc Voir le message
    La réponse de mabu est fausse:

    Note pour mabu: Se racheter une paire d'yeux, ceux-là sont périmés:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    	movl	-4(%ebp), %eax
    	movl	%eax, (%esp)
    	call	__Z7fun_cpyi
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    	leal	-4(%ebp), %eax
    	movl	%eax, (%esp)
    	call	__Z7fun_refRi
    Dans l'un, c'est la valeur qui est passée. Dans l'autre, c'est son adresse.

    Bon, ça me rassure d'un côté car la démarche me paraissait louche.
    Sinon, je vois le toubib des yeux demain

  6. #6
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    Salut,

    Pour faire simple, nous pourrions dire que, du point de vue du code généré, le passage par référence s'effectue comme le passage d'un pointeur.

    D'un point de vue pratique, et pour se raccrocher à ce que tu connais du C, nous pourrions dire qu'une référence est l'équivalent d'un pointeur, avec certaines particularités:
    • Tu ne peux pas rendre l'adresse d'une référence (alors que tu peux prendre l'adresse d'un pointeur pour en faire un pointeur de pointeur)
    • tu utilise exactement la même syntaxe pour accéder aux différents membres (utilisation du point "." au lieu de la flèche droite "->")
    • tu travaille directement sur la variable au lieu de travailler sur l'adresse de la variable (pas besoin de la "déréférencer" (sous la forme de *maref ) si la variable transmise à la base n'est pas elle-même un pointeur)
    • tu dois respecter les mêmes règles que pour la variable d'origines (si la variable d'origine n'est pas un pointeur, tu ne peux pas utiliser l'allocation dynamique, alors que si la variable d'origine est un pointeur, tu peux faire)
    • tu peux déclarer une référence constante ce qui empêche la variable d'être modifiée, alors que, lorsque tu déclares un pointeur constant, c'est l'adresse qui est constante et non la variable qui se trouve à l'adresse indiquée
    (tu remarquera que toutes ces particularités découlent d'un fait unique: la référence réagit exactement comme la variable d'origine )
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  7. #7
    Expert éminent
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 395
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 395
    Par défaut
    Citation Envoyé par koala01 Voir le message
    tu peux déclarer une référence constante ce qui empêche la variable d'être modifiée, alors que, lorsque tu déclares un pointeur constant, c'est l'adresse qui est constante et non la variable qui se trouve à l'adresse indiquée
    Attention: On peut avoir des pointeurs qui empêchent eux-même une modification de la variable à travers eux...

    Par contre, une référence constante a des effets qu'un pointeur n'a pas, sur les objets passés en paramètre: conversion implicite, etc.
    En clair, contrairement à un pointeur ou à une référence non-constante, une référence constante peut "pointer" sur une rvalue.
    SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.

    "Aw, come on, who would be so stupid as to insert a cast to make an error go away without actually fixing the error?"
    Apparently everyone.
    -- Raymond Chen.
    Traduction obligatoire: "Oh, voyons, qui serait assez stupide pour mettre un cast pour faire disparaitre un message d'erreur sans vraiment corriger l'erreur?" - Apparemment, tout le monde. -- Raymond Chen.

  8. #8
    Membre émérite
    Avatar de Antoine_935
    Profil pro
    Développeur web/mobile
    Inscrit en
    Juillet 2006
    Messages
    883
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations professionnelles :
    Activité : Développeur web/mobile

    Informations forums :
    Inscription : Juillet 2006
    Messages : 883
    Par défaut
    Tu ne peux pas rendre l'adresse d'une référence
    Tu veux dire pour aller modifier cette référence dans un contexte extérieur ? Autrement, prendre l'adresse d'une référence fonctionne sans souci, sous g++ du moins, et tant qu'on s'assure que la variable existera toujours quand on la demande.

    A ce propos d'ailleurs, j'avais un peu joué avec les références, et je suis arrivé à une autre question, qui se trouve en commentaire dans le code:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    #include <iostream>
    using namespace std;
     
    class Built {
    public:
        int& i;
        Built(int& i): i(i) {}
    };
     
    class Builder {
    private:
        int i;
     
    public:
        Builder() { i = 5; }
        Built* getBuilt() { return new Built(i); }
    };
     
    int main(void) {
        Builder* b = new Builder(); // b contient une variable nommée i
        Built* t = b->getBuilt(); // t contient une référence à la variable ci-dessus
     
        cout << t->i << endl;
     
        delete b; // Qu'en est-il de i ?
     
        // Avec g++, ceci va toujours afficher 0
        // mais j'imagine que c'est dépendant du compilo, voire de l'os
        // cela dit, on peut imaginer du coup que la destruction d'un objet
        // remette la mémoire à zero d'un certaine manière... quid alors ?
        cout << t->i << endl;
     
        return 0;
    }
    Un passage par référence non-constante, c'est du sucre syntaxique: Un passage par pointeur déguisé.
    Clair et net, merci Mais est-il possible alors de faire "pointer" cette référence vers une sorte de NULL ?
    Je demande ça car il m'a été impossible d'avoir une variable d'instance qui soit une référence sans pouvoir la spécifier à la construction même.
    Enfin j'imagine que, du coup, utiliser un pointeur est précisément la même chose.
    Par contre tu spécifies dans ta phrase "non-constante". Quelles sont les différences, autrement ?

    une référence constante peut "pointer" sur une rvalue
    bien mais... je n'ai tjs pas compris précisément (ni même vaguement) ce qu'était une rvalue, lvalue et machintrucvalue... Y a-t-il un bon lien ou une âme charitable pour m'expliquer ça ?

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Passage par référence
    Par e1lauren dans le forum Débuter avec Java
    Réponses: 4
    Dernier message: 01/09/2006, 12h59
  2. Passage par copie vs passage par référence
    Par bolhrak dans le forum C++
    Réponses: 11
    Dernier message: 20/08/2006, 23h37
  3. Réponses: 4
    Dernier message: 26/12/2005, 17h01
  4. Passage par référence
    Par difficiledetrouver1pseudo dans le forum Langage
    Réponses: 9
    Dernier message: 28/09/2005, 11h17
  5. Problème très rapide de passage par référence
    Par Noxexplorer dans le forum ASP
    Réponses: 2
    Dernier message: 23/06/2005, 10h02

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo