Annonce

Réduire
Aucune annonce.

Cours langage C n°7 - Adresses mémoire et pointeurs

Réduire
Ceci est une discussion importante.
X
X
  • Filtre
  • Heure
  • Afficher
Tout nettoyer
nouveaux messages

  • Cours langage C n°7 - Adresses mémoire et pointeurs

    ADRESSES MÉMOIRE ET POINTEURS

    Autant vous dire tout de suite, ça va être dur ! Posez-vous calmement, la concentration totale pour un cerveau est d'environ 15 minutes, faites cette lecture en plusieurs fois, c'est un conseil...

    C'est quoi un pointeur ?

    Quel nom bizarre ! J'ai déjà entendu parler des pointeurs en prison, mais en informatique, qu'est-ce que ça peut bien être ?

    Souvenez-vous ! Une variable contient une valeur, exemple

    Code:
    int n = 5;
    La variable n contient la valeur 5... Eh bien un pointeur est une variable, contenant non pas une valeur mais une adresse mémoire d'une autre variable.

    Purée! ça commence, c'est quoi une adresse mémoire ? Pas de stress, vous connaissez votre adresse, celle de votre maison, elle indique l'endroit où vous habitez (j'espère pour vous ) ? Eh bien une adresse mémoire, c'est la même chose, c'est l'endroit en mémoire où va se trouver cette variable.

    Une adresse ressemble à une valeur hexadécimale (on s'en fou) du style : 0x7fff46b980a0

    Ça sert à quoi un pointeur ?

    Son principal objectif, est de modifier la valeur originale contenue dans une variable via une adresse. Travailler avec les adresses mémoires permet aussi de rendre un code bien plus efficace, mais cela est loin d'être notre priorité aujourd'hui...

    Est-on obliger d'utiliser les pointeurs?

    Oui, et indirectement vous l'utilisez déjà, avec les tableaux de caractères

    Attention je n'ai pas dis qu'un tableau de caractères est un pointeur, j'ai juste dis qu'il y a un rapport...

    Déclaration d'un pointeur

    On utilisera l'opérateur *, juste avant le nom de la variable

    Exemple:

    Code:
    #include <stdio.h>
    
    int main(void)
    {
        int *p; /* déclaration d'un pointeur dont le nom de variable est p */
    
        /* suite du code */
    
        return 0;
    }
    Comment ça se lit ce type de code ?

    On va dire que p est une variable pouvant contenir l'adresse mémoire d'une variable de type entier.

    Initialisation d'un pointeur

    Un pointeur est une variable, elle a donc les mêmes droits qu'une tout autre variable, seulement n'oublions pas, elle contient une adresse... Si on a pas d'adresse contenue dans cette variable on l'exprimera par le terme NULL !

    Code:
    #include <stdio.h>
    
    int main(void)
    {
        int *p = NULL; /* déclaration et initialisation de la variable p sans adresse (valeur NULL) */
    
        /* suite du code */
    
        return 0;
    }
    ou

    Code:
    #include <stdio.h>
    
    int main(void)
    {
        int *p; /* déclaration de la variable p, contenant adresse d'une variable de type entier */
    
        p = NULL; /* initialisation de la variable p sans adresse (valeur NULL) */
    
        /* suite du code */
    
        return 0;
    }
    Comment avoir l'adresse mémoire d'une variable ?

    Ok, je veux bien créer une variable contenant une adresse mémoire, mais je l'obtiens comment cette adresse ?

    On va utiliser un opérateur un peu particulier & qui sera placé à côté de la variable où l'on veut connaître son adresse.

    Exemple:

    Code:
    #include <stdio.h>
    
    int main(void)
    {
        int n = 2;
    
        int *p = &n; /* adresse mémoire de la variable n enregistrée dans la variable p */
    
        /* suite du code */
    
        return 0;
    }
    p est une variable contenant l'adresse de la variable n...

    Comment depuis une adresse mémoire, avoir la valeur de la variable?

    Bon vous avez votre adresse mémoire enregistrée dans une variable de type pointeur sur un entier, comment je fais pour avoir la valeur de cette variable ?

    On va déréférencer la variable à l'aide de l'opérateur *...

    Code:
    #include <stdio.h>
    
    int main(void)
    {
        int n = 2;
    
        int *p = &n;
    
        int value = *p; /* déréférencement de la variable p pour avoir sa valeur enregistrée dans la variable value */
    
        printf("%d\n", value); /* Affichage valeur de la variable value donc 2 */
    
        return 0;
    }
    Modifier le contenu (valeur) d'une variable via son adresse

    Souvenez vous ! Le principal objectif d'un pointeur est de modifier la valeur originale d'une variable via son adresse...

    Pour cela on doit
    1. Assigner l'adresse de la variable à modifier au pointeur
    2. Déréférencer avec l'opérateur * le pointeur est lui assigner une nouvelle valeur
    3. Afficher le nouveau contenu de cette variable à modifier



    En code ça nous donne cela...

    Code:
    #include <stdio.h>
    
    int main(void)
    {
        int n = 2;
    
        int *p = &n; /* Assigne l'adresse de n dans p */
    
        *p = 3; /* déréférencement de la variable p et on assigne la nouvelle valeur */
    
        printf("%d\n", n); /* Affichage valeur de la variable n donc 3 */
    
        return 0;
    }
    ou

    Code:
    #include <stdio.h>
    
    int main(void)
    {
        int n = 2;
    
        int *p = NULL; /* initialise à NULL dans p */
        
        p = &n; /* assigne adresse de n dans p */
    
        *p = 3; /* déréférencement de la variable p et on assigne la nouvelle valeur */
    
        printf("%d\n", n); /* Affichage valeur de la variable n donc 3 */
    
        return 0;
    }
    Manipuler d'autres types en mémoire

    Évidemment on peut, essayons avec le type char

    Code:
    #include <stdio.h>
    
    int main(void)
    {
        char letter = 'A';
    
        char *p = &letter; /* adresse de letter dans p */
    
        *p = 'Z'; /* déréférencement de la variable p et on assigne la nouvelle valeur */
    
        printf("%c\n", letter); /* Affichage valeur de la variable letter donc Z */
    
        return 0;
    }
    Les pointeurs et les tableaux

    Souvenez-vous ! Un tableau de caractères

    Plusieurs cases les unes derrière les autres avec comme valeur pour chaque case une lettre.

    On imagine donc bien que de pointer sur une adresse d'un tableau va un peu être différent...

    Code:
    #include <stdio.h>
    
    int main(void)
    {
        char chaine[] = "Hello";
    
        char *p = &chaine[0]; /* adresse de la 1ère case du tableau chaine dans p */
    
        *p = 'h'; /* déréférencement de la variable p et on assigne la nouvelle valeur 'h' de la 1ère case du tableau chaine */
    
        printf("%s\n", chaine); /* Affichage valeur de la variable chaine donc "hello" */
    
        return 0;
    }
    Ok ça semble logique, mais on a voulu simplifier un peu &chaine[0], on peut l'écrire de cette façon

    Code:
    #include <stdio.h>
    
    int main(void)
    {
        char chaine[] = "Hello";
    
        char *p = chaine; /* adresse de la 1ère case du tableau chaine dans p */
    
        *p = 'h'; /* déréférencement de la variable p et on assigne la nouvelle valeur 'h' de la 1ère case du tableau chaine */
    
        printf("%s\n", chaine); /* Affichage valeur de la variable chaine donc "hello" */
    
        return 0;
    }
    Ah oui c'est plus simple, mais il faut quand même savoir que faire char *p = chaine, c'est enregistrer l'adresse mémoire de la 1ère case du tableau dans la variable p. Sans la démonstration du 1er code, impossible de comprendre cela

    Super, je commence à comprendre, je veux faire plus, comment modifier les deux lettres 'l' de "hello" ?

    Les deux lettres 'l' se trouvent à la case 2 et 3 du tableau chaine, faisons cette modification, on remplacera 'l' par 'L'

    Code:
    #include <stdio.h>
    
    int main(void)
    {
        char chaine[] = "Hello";
    
        char *p = chaine; /* adresse de la 1ère case du tableau chaine dans p */
    
        p += 2; /* je vais à la case mémoire 2 */
    
        *p = 'L'; /* je modifie le 1er 'l' par 'L' */
    
        p += 1; /* je vais à la case mémoire 3 */
    
        *p = 'L';
    
        printf("%s\n", chaine); /* Affichage valeur de la variable chaine donc "HeLLo" */
    
        return 0;
    }
    Non mais fred, c'est trop fort, tu fais des opérations sur des pointeurs ? Oui on joue carrément avec la mémoire, je me déplace comme je veux dedans, super non?

    Eh! Ça vous dit qu'on fasse cela avec une boucle for et while ? Allez c'est parti !

    Code:
    #include <stdio.h>
    
    int main(void)
    {
        char chaine[] = "Hello";
        char *p = NULL; /* adresse initialisé à NULL dans p */
    
        for (p=chaine; *p!='\0'; p++) /* Pour adresse de chaine[0] à valeur déréférencée de p valant fin de chaine '\0', on incrémente par case mémoire */
        {
            if (*p == 'l') /* on détecte lors du référencement que valeur vaut 'l', valeur à modifier */
            {
                *p = 'L'; /* assignation de la nouvelle valeur 'L' */
            }
        }
    
        printf("%s\n", chaine); /* Affichage valeur de la variable chaine donc "HeLLo" */
    
        return 0;
    }
    C'est un peu plus complexe, mais vous avez les bases pour le comprendre, prenez le temps !

    Avec la boucle while, identique évidemment...

    Code:
    #include <stdio.h>
    
    int main(void)
    {
        char chaine[] = "Hello";
        char *p = chaine; /* adresse chaine[0] dans p */
    
        while (*p != '\0') /* Tant que la valeur déréférencée de p ne vaut pas la fin de chaine '\0' */
        {
            if (*p == 'l') /* Si valeur déréférencée vaut 'l' */
            {
                *p = 'L'; /* Assigner nouvelle valeur 'L' */
            }
    
            p++; /* incrémenter de 1 la case mémoire */
        }
    
        printf("%s\n", chaine); /* Affichage valeur de la variable chaine donc "HeLLo" */
    
        return 0;
    }
    EXERCICES

    Pas d'exercices, je vous laisse digérer, tester tous ces codes et comprendre. Ça prendra déjà pas mal de temps...

    Par contre, le cours suivant sera sur les fonctions, et les pointeurs seront de la fête lors des exercices.

    Merci de m'avoir lu et à bientôt pour le prochain cours

  • #2
    Code:
    #include <stdio.h>
    
    int main(void)
    {
        int n = 2;
    
        int *p = &n;
    
        int value = *p; /* déréférencement de la variable p pour avoir sa valeur enregistrée dans la variable value */
    
        printf("%d\n", value); /* Affichage valeur de la variable value donc 2 */
    
        return 0;
    }
    C'est pareil que si l'on donner la valeur de la variable n à la variable value. Dans cet exemple je ne vois pas l'interêt du pointeur si ce n'est plus compliqué le code.Pareil ensuite le changement de variable ne peut ton pas faire plus simple ?
    comme :

    Code:
    #include <stdio.h>
    
    int main(void)
    {
        int n = 2;
    
        int p = 3; /* initialise à NULL dans p */
        
        n = p; /* transfert de variable */
    
        printf("%d\n", n); /* Affichage valeur de la variable n donc 3 */
    
        return 0;
    }
    Pour les caractères on a besoin d'initialiser le pointeur à NULL et sinon pourquoi : int *p = NULL; et char *p=NULL;
    C'est comme lorsque l'on utilise les boucles int i=0; signaler que l'opération doit démarrer de 0 ?


    Ensuite ce code:
    Code:
    #include <stdio.h>
    
    int main(void)
    {
        char chaine[] = "Hello";
    
        char *p = chaine; /* adresse de la 1ère case du tableau chaine dans p */
    
        p += 2; /* je vais à la case mémoire 2 */
    
        *p = 'L'; /* je modifie le 1er 'l' par 'L' */
    
        p += 1; /* je vais à la case mémoire 3 */
    
        *p = 'L';
    
        printf("%s\n", chaine); /* Affichage valeur de la variable chaine donc "HeLLo" */
    
        return 0;
    }
    J'ai compris quand je vais à p +=2; je reinitialise et je repart de ce l comme o et je prends p +=1;
    Hello
    012
    01

    Je relis encore voire si j'ai d'autres questions merci pour ce cours un Thanks encore largement mérité pour ce travail. Si tu fait de moi un codeur en C et apres C++ tu pourra apprendre à un bourricot à chanter la traviata
    Dernière modification par DreAmuS, 04 juillet 2014, 11h25.

    Commentaire


    • #3
      Comme je l'ai expliqué dans le cours, l'intérêt est de comprendre adresse et pointeur, son intérêt sera grandement vu lors des fonctions au prochain cours.

      Ensuite, encore une fois t'as pas lu Un pointeur est une variable contenant une adresse, si on initialise une variable de type pointeur sans adresse, on place la valeur NULL à la place...

      J'ai compris quand je vais à p +=2; je reinitialise et je repart de ce l comme o et je prends p +=1;
      Hello
      012
      01
      Non on réinitialise pas, faut être précis dans les termes, on se déplace par case mémoire,

      case mémoire 0 -> adresse case 0 du tableau chaine
      case mémoire 1 -> adresse case 1 du tableau chaine
      case mémoire 2 -> ...
      ...

      L'objectif dans ce cours est de manipuler/écrire sur la mémoire de ton PC et non l'écriture brute sur ton disque dur.

      Quand je fais p += 2, je pointe sur la case mémoire 2 qui contient l'adresse de la case 2 du tableau chaine équivalent à &chaine[2]

      Il faut relire le tout début, je pense que tu l'as un peu zappé

      Commentaire


      • #4
        Ok Merci de ta réponse fred oui en lisant mieux il est clair que ca rentre mieux
        J'ai compris le principe j'espère que je serais capable de le mettre en oeuvre dans ton prochain cours des fonctions.

        Commentaire


        • #5
          @Dreamus,

          Exceptionnellement, voici un intérêt direct des pointeurs, il y a une question primordiale à se poser dans le code, réponds-y en testant...

          Code:
          #include <stdio.h>
          
          void swap1(int a, int b);
          void swap2(int *a, int *b);
          
          int main(void)
          {
              int v1 = 15;
              int v2 = 10;
          
              /* le but est d'inverser la valeur v1 et la valeur v2 */
          
              /* Utilisation de swap1 */
          
              swap1(v1, v2);
              printf("avec swap1: v1=%d, v2=%d\n", v1, v2); /* v1 et v2 ont-ils été inversés ? */
          
              /* Utilisation de swap2 */
          
              swap2(&v1, &v2);
              printf("avec swap2: v1=%d, v2=%d\n", v1, v2); /* v1 et v2 ont-ils été inversés ? */
          
              return 0;
          }
          
          void swap1(int a, int b)
          {
              /* fonction inversion a et b sans pointeur */
          
              int temp;
          
              temp = a;
              a = b;
              b = temp;
          }
          
          void swap2(int *a, int *b)
          {
              /* fonction inversion a et b avec pointeur */
          
              int temp;
          
              temp = *a;
              *a = *b;
              *b = temp;
          }

          Commentaire


          • #6
            Dans le premier on a pas une inversion de code

            avec swap1: v1=15, v2=10

            Mais dans la seconde partie la valeur change.

            avec swap2: v1=10, v2=15

            En fait tu declare une fonction avec 2 variable et leur assigne une valeur à chacun mais lorsque tu utilise les pointeurs ils s'inversent.
            Désoler je ne comprends pas pourquoi ? Vu que l'on assigne juste le pointeur donc comment il sait qu'il doit l'inverser pour swap2

            Commentaire


            • #7
              Avec swap1, tu travailles sans les adresses des variables à modifier (v1 et v2), la modification n'est donc que locale (à l'intérieur de la fonction). (manipulation valeur)

              Avec swap2, tu travailles directement avec les adresses de v1 et v2, les modification sont donc écrites directement en mémoire (manipulation mémoire)

              Commentaire


              • #8
                Bonjour à tous,

                J'ai une petite question :

                J'ai a peu prés compris le principe des pointeurs cependant quand je l'associe aux tableaux il y a un Hik' :
                Code:
                #include <stdio.h>
                #include <stdlib.h>
                #define  TAILLE_MAX 100
                
                
                int main( int argc, char *argv[])
                {
                    char tableau[TAILLE_MAX] = {0};
                    
                    printf("%p\n",tableau); // J'accéde à l'adresse de tableau[0]
                    printf("%d\n",*tableau); // J'accéde à la valeur de tableau[0]
                    printf("%d\n",*(tableau+1)); // J'accéde à la valeur de tableau[1]
                    printf("%d\n\n",*(tableau+2)); // J'accéde à la valeur de tableau[2]
                
                   
                    return 0;
                }
                Avec un petit schéma sa donne ça :

                === ADRESSE === | === VALEUR ===

                "tableau" FFFFFF0 | FFFFF1
                "tableau[0]" FFFFFF1 | 0
                "tableau[1]" FFFFFF2 | 0
                "tableau[2]" FFFFFF3 | 0

                C'est là que je bloque, la variable "tableau" est donc un pointeur qui nous permet d'accéder aux valeurs du tableau

                et en même temps d'enregistrer ces mêmes valeurs ?
                Dernière modification par shirocen, 31 décembre 2014, 12h41.
                deux et deux font cinq

                Commentaire


                • #9
                  Bonjour, je suis pas sur d'avoir compris ta question n'hésite pas à me le dire au quel cas.
                  Ta variable tableau est un pointeur c'est pourquoi tu spécifie l'étoile dans ton exemple pour accéder à la valeur.
                  ToxID : 7322307290A75F5F36142EF206D95374966F10FE2CCD8224BEC07F16137875058C3BC4020609


                  Petite énigme, seriez vous décoder ce code ? WW4gZXJjYmFmciBoYXZpcmVmcnl5ciA/

                  Commentaire


                  • #10
                    Je ne suis pas sûr de comprendre, mais au bout de dix lectures, je tente une réponse...

                    *tableau est la valeur du 1er élément du tableau
                    tableau est l'adresse du 1er élément du tableau

                    Si on veut comparer ce qui est comparable, il faudrait faire

                    Code:
                    #include <stdio.h>
                    
                    int main(void)
                    {
                        char tableau[10] = {0};
                    
                        char *n = tableau;
                        char *p = &(*tableau);
                    
                        printf("%d\n", n==p); /* comparaison des deux adresses, si 1, alors même adresse, sinon renvoie 0 */
                    
                        /* Utiliser %p pour connaître l'adresse de n et l'adresse de p */
                    
                        return 0;
                    }
                    Ci-dessus le code permettant de comparer deux adresses. On voit bien que tableau est la même adresse que &(*tableau).

                    Commentaire


                    • #11
                      C'est exactement ça.
                      D'ailleurs ton code doit bien le mettre en valeur. Rassure toi les pointeurs font partie de ces concepts qui donnent mal au crâne quand on les abordes la première fois.

                      Si tu as encore des difficultés,envoie moi un mp et je tenterai de t'expliquer de vive voix sur Skype. En revanche je suis en vacance pour quelques jours je serais seulement disponible à partir de dimanche.
                      ToxID : 7322307290A75F5F36142EF206D95374966F10FE2CCD8224BEC07F16137875058C3BC4020609


                      Petite énigme, seriez vous décoder ce code ? WW4gZXJjYmFmciBoYXZpcmVmcnl5ciA/

                      Commentaire


                      • #12
                        Merci pour vos réponses !

                        DONC ! :

                        Quand on crée un tableau, on crée un TABLEAU (tableau[0], tableau[1]..) ET un POINTEUR -> tableau, qui permet de parcourir la mémoire alloué au tableau.
                        deux et deux font cinq

                        Commentaire


                        • #13
                          @shirocen,

                          J'aimerais pas être dans ta tête

                          pointeur est une variable pointant sur l'emplacement mémoire d'une variable, sa valeur sera une adresse

                          Pour tableau j'ai déjà exprimé ce qu'elle représente... Que n'as-tu pas compris ?

                          Commentaire


                          • #14
                            *Viens juste de relire le cours de Fred'*

                            Envoyé par shirocen Voir le message
                            Quand on crée un tableau, on crée un TABLEAU (tableau[0], tableau[1]..) ET un POINTEUR -> tableau, qui permet de parcourir la mémoire alloué au tableau.
                            J'ai vraiment dis n'importe quoi :s

                            Code:
                            #include <stdio.h>
                            
                            int main(void)
                            {
                                char tableau[10] = {0};
                            
                                char *n = tableau;
                                char *p = &(*tableau);
                            
                                printf("%d\n", n==p); /* comparaison des deux adresses, si 1, alors même adresse, sinon renvoie 0 */
                            
                                /* Utiliser %p pour connaître l'adresse de n et l'adresse de p */
                            
                                return 0;
                            }
                            Quand on créé un tableau (ici "tableau"), la variable "tableau" stocke l'adresse de la première valeur du tableau.

                            "tableau" est donc un pointeur qui va nous permettre d'accéder aux différentes valeurs du tableau.

                            Une petite question :

                            Pourquoi avoir adopter/inventer l'écriture : tableau[0] équivalente à *tableau ?
                            Dernière modification par shirocen, 11 janvier 2015, 12h52.
                            deux et deux font cinq

                            Commentaire


                            • #15
                              Quand on créé un tableau (ici "tableau"), la variable "tableau" stocke l'adresse de la première valeur du tableau.
                              Ouf enfin ça vient Content de voir que mes cours te rendent plus service que les cours du SDZ, c'était le but, car leur pédagogie me gênait un peu...

                              Pourquoi avoir adopter/inventer l'écriture : tableau[0] équivalente à *tableau ?
                              Question de lisibilité, de simplicité d'écriture, t'avoueras que tab[1] est plus lisible que *(tab+1), non ?

                              "tableau" est donc un pointeur qui va nous permettre d'accéder aux différentes valeurs du tableau.
                              En fait je crois que tu as compris, mais c'est mal dit, tableau est l'adresse mémoire du 1er élément du tableau.

                              Code:
                              #include <stdio.h>
                              
                              int main(void)
                              {
                              	int tab[] = {5, 3, 2};
                              
                              	printf("%d\n", tab == &(tab[0])); /* retourne 1, donc tab et &(tab[0]) sont équivalents */
                              
                                  return 0;
                              }
                              Dernière modification par fred, 11 janvier 2015, 21h18.

                              Commentaire

                              Chargement...
                              X