Forum Programmation.c Passage par référence

Posté par . Licence CC by-sa
Tags : aucun
4
23
oct.
2013

Je vous préviens tout de suite, ce qui suit est de la grosse question de newbie.

Alors voilà. J'arrive à faire quelques trucs en C, utiliser libcurl par exemple, mais j'ai toujours cette sale impression de ne pas bien saisir la notion de pointeur. Vos explications seront les bienvenues.

Considérant le code suivant :

#include <stdio.h>

void add (int a, int b, int *c)
{
    *c=a+b;
}

int main()
{
    int a = 4;
    int b = 2;
    int c = 0;
    add (a, b, &c);
    printf ("c = %i\n", c);
    return (0);
}

La sortie standard me sort « c = 6 »

Est-ce que la bonne manière d'interpréter :

*c=a+b

est de dire c pointera sur l'entier résultat de a+b

J'ai honte…

  • # Nop

    Posté par (page perso) . Évalué à 0.

    Non la traduction plus juste serait de dire que le la valeur associée au pointeur c contient la valeur a+b.

    Soit c un pointeur, *c représente la valeur pointée par le pointeur.

    Alors que &variable représente la case mémoire (le pointeur) qui contient la valeur.

    Donc &(*c) == c.

    Est-ce plus clair ?

    Dire c pointera sur l'entier résultat de a+b ressemblerai plus à :

    c = &(a+b);
    

    Mais sans avoir tester la syntaxe, le problème est que la valeur résultat risque d'être déférencer et donc c pointera une case non valide en mémoire.

    • [^] # Re: Nop

      Posté par . Évalué à 1.

      Avec la dernière syntaxe c=&(a+b) ça ne peut pas marcher car cela n'aura aucun effet sur le contenu de c dans la fonction main.

      et on ne peut pas obtenir l'adresse de (a+b). on ne peut obtenir l'adresse que des variables et des fonctions.

      • [^] # Re: Nop

        Posté par (page perso) . Évalué à 2.

        Oui merci, je m'en doutais un peu (je n'avais pas tester la syntaxe). J'avais mis cette phrase plus pour indiquer à quoi ressemblerai " est de dire c pointera sur l'entier résultat de a+b "

  • # Adresse mémoire

    Posté par . Évalué à 4.

    Bonjour,

    Pas tout-à-fait : l'expression en question doit plutôt être lue « ce qui est pointé par c va recevoir le résultat de a+b. ».

    Un pointeur est une variable qui contient une adresse en mémoire. Le format de celle-ci est donc dépendant de l'architecture sur laquelle tu travaille mais, a contrario, sera complètement indépendant de la donnée qui se trouve à cette adresse.

    Quand tu passes des arguments à une fonction, ceux-ci se retrouvent dans la pile et sont gérés comme s'il s'agissait de variables locales. Ainsi, mais si elles portent le même nom, les variables « a », « b » et « c » de ta fonction add sont complètement indépendantes de celle de ta fonction main et, par conséquent, les arguments en langage C sont toujours transmis par COPIE.

    Mais dans le cas présent, ce que tu passes en troisième paramètre est littéralement « l'adresse de la variable c ». Puisque « c » est un entier, l'information en question est donc forcément un « pointeur sur un entier », d'où la manière de rédiger le prototype de add.

    Passer à une fonction l'adresse d'une variable extérieure à cette fonction lui permet donc d'écrire dedans, entre autres.

  • # Merci

    Posté par . Évalué à 2.

    Merci pour vos explications.

    J'ai effectivement utilisé a, b et c comme noms de variables en ayant bien conscience que le a de main() n'est pas le a de add()…

    Mais dans le cas présent, ce que tu passes en troisième paramètre est littéralement « l'adresse de la variable c ». Puisque « c » est un entier, l'information en question est donc forcément un « pointeur sur un entier », d'où la manière de rédiger le prototype de add.

    Dire c pointera sur l'entier résultat de a+b ressemblerai plus à :
    c = &(a+b);

    Je crois que j'ai compris.

    Sauf :

    Mais sans avoir tester la syntaxe, le problème est que la valeur résultat risque d'être déférencer et donc c pointera une case non valide en mémoire.

    Bon, c'est « testé » et « déréférencé » ? Mais pourquoi c pointerait sur une « case non valide » ?

    • [^] # Re: Merci

      Posté par . Évalué à 3.

      Mais pourquoi c pointerait sur une « case non valide » ?

      // cette fonction retourne l'adresse d'une variable locale
      int * f(void){
          int c = 0;
          return &c; // warning du compilo
      }
      
      int main(void){
          int * pc = f();
          printf("%d\n",*pc);
      // pc pointe dans un contexte qui n'existe plus (celui du dernier appel de fonction).
      // il est possible que des choses se soit produite entre le moment ou f retourne et celui où on lit ce qu'il y a l'adresse pc (un traitement de signal par exemple).
      }

      Please do not feed the trolls

      • [^] # Re: Merci

        Posté par (page perso) . Évalué à 2.

        Encore plus con.
        Prend un pointeur non initialisé et fait des opération avec.

        En C, les variables non initialisées peuvent avoir n'importe qu'elle valeur suivant l'état de la RAM à ce moment là. Si ton pointeur non initialisé contient une adresse ou une valeur qui rend l'opération non valide, c'est le crash garantie.

    • [^] # Re: Merci

      Posté par . Évalué à 1.

      Dans l'exemple que tu as donné il n'y a pas de souci de "case non valide".

      • [^] # Re: Merci

        Posté par . Évalué à 2.

        Oui c'est bien ce que je pensais, tu confirmes et je t'en remercie. Je pense que j'ai bien compris avec l'exemple de Zylabon.

  • # un petit dessin

    Posté par . Évalué à 5. Dernière modification le 23/10/13 à 18:58.

    Ça c'est de la mémoire, avec une valeur c quelque part

    |_|_|_|_|_|_|c|_|_|_|...
     0 1 2 3 4 5 6 7 8 9
    

    À l'adresse 3, je vais mettre un pointeur sur c :

    |_|_|_|6|_|_|c|_|_|_|...
     0 1 2 3 4 5 6 7 8 9
    

    Si j'appelle la fonction add avec add(2,4,6);, add va calculer 2 + 4 et écrire le résultat à l'adresse désignée par son dernier argument, à savoir 6.

    Voir le code assembleur aide à comprendre :

    int add1(int a, int b){
        return a + b;
    }
    
    void add2(int a, int b, int * c){
        *c = a+b;
    }

    En assembleur x86, les opération binaire sont toutes de la forme a+=b, en assembleur addl b,a
    Et ce qu'on écrit f[45] en c s'écrit 45(f) en assembleur (f) est équivalent à *f

    add1:
        pushl   %ebp       // sauver le contexte (pas important ici)
        movl    %esp, %ebp // idem
        movl    12(%ebp), %eax  // copier second paramètre dans le registre eax
        movl    8(%ebp), %edx   // copier le premier paramètre dans edx
        addl    %edx, %eax      // eax = eax + edx
        popl    %ebp       //restaurer le contexte
        ret                // retourner (le résultat est alors dans eax)
    add2:
        pushl   %ebp
        movl    %esp, %ebp
        movl    12(%ebp), %eax
        movl    8(%ebp), %edx     // comme add1
        addl    %eax, %edx        // mais là a+b se retrouve dans edx
        movl    16(%ebp), %eax    // on copie le troisième paramètre dans eax
        movl    %edx, (%eax)      // et on copie le contenu de edx à l'adresse dans eax
        popl    %ebp        // on restaure le contexte
        ret                 // la fonction ne retourne rien, il y a n'importe quoi dans eax

    Please do not feed the trolls

  • # référence

    Posté par . Évalué à 1.

    Sinon, pour chipoter sur le titre, il n'y a pas de références en C. Il n'y a que des pointeurs. Les références c'est en C++.

Suivre le flux des commentaires

Note : les commentaires appartiennent à ceux qui les ont postés. Nous n'en sommes pas responsables.