Langage C/C++

Comprendre les pointeurs en C

Les pointeurs sont un concept fondamental en langage C, qui permet de manipuler directement la mémoire. Ils offrent un moyen puissant et flexible d’accéder aux données, mais ils nécessitent une bonne compréhension pour éviter des erreurs comme les fuites de mémoire ou les accès mémoire invalides.

1. Définition des pointeurs

Un pointeur est une variable qui contient l’adresse mémoire d’une autre variable. Au lieu de stocker une valeur, un pointeur stocke l’emplacement en mémoire où cette valeur est située.

2. Déclaration et utilisation des pointeurs

Pour déclarer un pointeur, il faut spécifier le type de données vers lequel il pointe, suivi d’un astérisque (*), qui indique que la variable est un pointeur.

Exemple de déclaration :

int *ptr;   // ptr est un pointeur vers un entier

Ici, ptr est un pointeur qui peut contenir l’adresse d’une variable de type int.

3. Opérateur d’adresse & et déréférencement *

  • Opérateur d’adresse (&) : Il permet de récupérer l’adresse mémoire d’une variable.
  • Opérateur de déréférencement (*) : Il permet d’accéder à la valeur contenue à l’adresse pointée par un pointeur.

Exemple d’utilisation :

int a = 10;    // Une variable entière
int *ptr = &a; // ptr contient l'adresse de la variable a

printf("La valeur de a : %d\n", a);     // Affiche 10
printf("L'adresse de a : %p\n", &a);    // Affiche l'adresse mémoire de a
printf("La valeur à laquelle ptr pointe : %d\n", *ptr);  // Affiche 10

Dans cet exemple :

  • &a donne l’adresse de la variable a et l’assigne au pointeur ptr.
  • *ptr déréférence le pointeur et accède à la valeur stockée à l’adresse à laquelle ptr pointe, soit 10.

4. Pointeurs et tableaux

En C, les tableaux et les pointeurs sont étroitement liés. Le nom d’un tableau est en fait un pointeur vers le premier élément du tableau.

Exemple :

int arr[5] = {1, 2, 3, 4, 5};
int *ptr = arr; // ptr pointe vers le premier élément du tableau

printf("%d\n", *ptr);      // Affiche 1, premier élément du tableau
printf("%d\n", *(ptr + 1)); // Affiche 2, deuxième élément du tableau

Dans cet exemple, arr est un pointeur vers le premier élément du tableau, et *(ptr + 1) permet d’accéder au deuxième élément du tableau en utilisant l’arithmétique des pointeurs.

5. Arithmétique des pointeurs

Les pointeurs supportent l’arithmétique. Vous pouvez ajouter ou soustraire des entiers à un pointeur, ce qui déplace le pointeur vers d’autres emplacements en mémoire.

Exemple :

int arr[3] = {10, 20, 30};
int *ptr = arr; // ptr pointe vers arr[0]

printf("%d\n", *ptr);       // Affiche 10
printf("%d\n", *(ptr + 1)); // Affiche 20
printf("%d\n", *(ptr + 2)); // Affiche 30

Ici, ptr + 1 pointe vers l’élément suivant du tableau. En termes d’arithmétique des pointeurs, lorsqu’un pointeur de type int est incrémenté de 1, il saute de 4 octets (la taille d’un int sur la plupart des systèmes) pour pointer vers le prochain entier.

6. Pointeur nul (NULL)

Un pointeur peut ne pas pointer vers une adresse valide. Dans ce cas, il est souvent initialisé à une valeur spéciale : NULL, qui signifie que le pointeur ne pointe vers aucune adresse en mémoire.

Exemple :

int *ptr = NULL; // ptr ne pointe vers rien

Il est important de vérifier qu’un pointeur n’est pas nul avant de l’utiliser pour éviter des erreurs d’exécution.

7. Pointeurs et fonctions

Les pointeurs sont souvent utilisés pour passer des arguments à des fonctions par référence, permettant à la fonction de modifier la valeur d’une variable en dehors de son propre contexte.

Exemple :

void increment(int *ptr) {
    (*ptr)++; // Déréférence et incrémente la valeur
}

int main() {
    int a = 5;
    increment(&a); // Passe l'adresse de a à la fonction
    printf("%d\n", a); // Affiche 6, car a a été modifié
    return 0;
}

Dans cet exemple, la fonction increment reçoit un pointeur vers la variable a et modifie sa valeur directement.

8. Pointeurs sur pointeurs

Un pointeur peut également pointer vers un autre pointeur. C’est ce qu’on appelle un pointeur de pointeur.

Exemple :

int a = 10;
int *ptr = &a;  // ptr pointe vers a
int **pptr = &ptr;  // pptr pointe vers ptr

printf("La valeur de a : %d\n", **pptr); // Affiche 10

Dans ce cas, pptr est un pointeur qui contient l’adresse de ptr, et **pptr permet d’accéder à la valeur de a.

9. Dangers des pointeurs

Les pointeurs en C sont très puissants, mais ils peuvent aussi être dangereux s’ils sont mal utilisés :

  • Déréférencement d’un pointeur non initialisé : Accéder à une adresse mémoire invalide peut provoquer des erreurs graves.
  • Fuite de mémoire : Si vous allouez de la mémoire dynamiquement (avec malloc) et que vous oubliez de la libérer (avec free), vous pouvez provoquer des fuites de mémoire.
  • Pointeurs sauvages : Des pointeurs qui ne pointent plus vers une adresse valide (après une libération par exemple) peuvent provoquer des comportements imprévisibles s’ils sont utilisés.

👉 Les pointeurs en C sont une fonctionnalité puissante qui permet une gestion fine de la mémoire et des performances élevées. Cependant, leur utilisation correcte exige une grande prudence pour éviter les erreurs communes, telles que les fuites de mémoire ou les violations d’accès. Maîtriser les pointeurs est une étape clé pour progresser dans la programmation en C.

Voici quelques exercices corrigés sur les pointeurs en langage C, conçus pour renforcer votre compréhension de ce concept fondamental.


Exercice 1 : Utilisation basique des pointeurs

Énoncé :

Écrivez un programme qui utilise un pointeur pour accéder et modifier la valeur d’une variable entière.

Solution :

#include <stdio.h>

int main() {
    int a = 10;
    int *p;  // Déclaration du pointeur

    p = &a;  // Assigner l'adresse de 'a' à 'p'

    printf("Valeur initiale de a : %d\n", a);   // Affiche 10
    printf("Adresse de a : %p\n", p);           // Affiche l'adresse de 'a'
    printf("Valeur pointée par p : %d\n", *p);  // Affiche 10

    // Modification de la valeur de 'a' à travers le pointeur
    *p = 20;

    printf("Nouvelle valeur de a : %d\n", a);   // Affiche 20

    return 0;
}

Explication :

Dans cet exercice, un pointeur p est utilisé pour accéder et modifier la variable a. En déréférençant le pointeur (*p), on peut modifier directement la valeur stockée à l’adresse mémoire où se trouve a.


Exercice 2 : Échanger deux valeurs avec des pointeurs

Énoncé :

Écrivez une fonction qui échange les valeurs de deux variables en utilisant des pointeurs.

Solution :

#include <stdio.h>

void echanger(int *x, int *y) {
    int temp;
    temp = *x;  // Sauvegarde la valeur de x
    *x = *y;    // Met y dans x
    *y = temp;  // Met temp dans y
}

int main() {
    int a = 5, b = 10;

    printf("Avant échange : a = %d, b = %d\n", a, b);

    // Appel de la fonction echanger avec les adresses de 'a' et 'b'
    echanger(&a, &b);

    printf("Après échange : a = %d, b = %d\n", a, b);

    return 0;
}

Explication :

La fonction echanger() prend en entrée deux pointeurs. Elle échange les valeurs des deux variables en modifiant directement les valeurs à l’adresse des variables fournies en arguments. Cela permet d’effectuer l’échange sans retour de valeur.


Exercice 3 : Allocation dynamique d’un tableau

Énoncé :

Écrivez un programme qui alloue dynamiquement un tableau de 5 entiers, demande à l’utilisateur de saisir ces entiers, puis affiche les entiers saisis.

Solution :

#include <stdio.h>
#include <stdlib.h>

int main() {
    int *tableau;
    int i;

    // Allocation dynamique d'un tableau de 5 entiers
    tableau = (int *)malloc(5 * sizeof(int));

    if (tableau == NULL) {
        printf("Échec de l'allocation mémoire.\n");
        return 1;  // Sortir si l'allocation échoue
    }

    // Saisie des entiers
    printf("Entrez 5 entiers :\n");
    for (i = 0; i < 5; i++) {
        scanf("%d", &tableau[i]);
    }

    // Affichage des entiers
    printf("Vous avez saisi :\n");
    for (i = 0; i < 5; i++) {
        printf("%d ", tableau[i]);
    }

    // Libérer la mémoire
    free(tableau);

    return 0;
}

Explication :

Dans cet exercice, nous utilisons malloc() pour allouer dynamiquement un tableau d’entiers. Après la saisie des valeurs par l’utilisateur, les valeurs sont affichées, puis la mémoire allouée est libérée avec free().


Exercice 4 : Pointeur sur pointeur

Énoncé :

Écrivez un programme qui utilise un pointeur sur pointeur pour modifier la valeur d’une variable entière.

Solution :

#include <stdio.h>

int main() {
    int a = 50;
    int *p;
    int **pp;  // Pointeur sur pointeur

    p = &a;    // p pointe vers a
    pp = &p;   // pp pointe vers p

    // Affichage des valeurs
    printf("Valeur de a : %d\n", a);      // Affiche 50
    printf("Valeur de *p (a) : %d\n", *p); // Affiche 50
    printf("Valeur de **pp (a) : %d\n", **pp); // Affiche 50

    // Modification de a via le pointeur sur pointeur
    **pp = 100;

    // Affichage après modification
    printf("Nouvelle valeur de a : %d\n", a);  // Affiche 100

    return 0;
}

Explication :

Ici, nous utilisons un pointeur sur pointeur (pp) pour modifier la valeur de la variable a. Le pointeur p contient l’adresse de a, et pp contient l’adresse de p. En déréférençant deux fois (**pp), nous modifions directement la valeur de a.


Exercice 5 : Calcul de la longueur d’une chaîne de caractères avec pointeurs

Énoncé :

Écrivez une fonction qui calcule la longueur d’une chaîne de caractères en utilisant uniquement des pointeurs, sans utiliser la fonction strlen() de la bibliothèque standard.

Solution :

#include <stdio.h>

int longueur_chaine(const char *str) {
    const char *p = str;
    while (*p != '\0') {
        p++;
    }
    return p - str;
}

int main() {
    char chaine[] = "Bonjour";
    printf("Longueur de la chaîne \"%s\" : %d\n", chaine, longueur_chaine(chaine));
    return 0;
}

Explication :

Dans cette fonction, un pointeur p parcourt la chaîne de caractères jusqu’à rencontrer le caractère de fin de chaîne \0. La longueur de la chaîne est ensuite calculée comme la différence entre les adresses de fin et de début de la chaîne (p - str).


Exercice 6 : Inversion d’un tableau avec des pointeurs

Énoncé :

Écrivez une fonction qui inverse un tableau d’entiers en place en utilisant des pointeurs.

Solution :

#include <stdio.h>

void inverser_tableau(int *arr, int taille) {
    int *debut = arr;
    int *fin = arr + taille - 1;
    int temp;

    while (debut < fin) {
        temp = *debut;
        *debut = *fin;
        *fin = temp;

        debut++;
        fin--;
    }
}

int main() {
    int tableau[] = {1, 2, 3, 4, 5};
    int taille = sizeof(tableau) / sizeof(tableau[0]);

    printf("Tableau avant inversion :\n");
    for (int i = 0; i < taille; i++) {
        printf("%d ", tableau[i]);
    }
    printf("\n");

    inverser_tableau(tableau, taille);

    printf("Tableau après inversion :\n");
    for (int i = 0; i < taille; i++) {
        printf("%d ", tableau[i]);
    }
    printf("\n");

    return 0;
}

Explication :

La fonction inverser_tableau() utilise deux pointeurs : debut et fin. Ces pointeurs parcourent le tableau depuis les deux extrémités vers le milieu, en échangeant les éléments jusqu’à ce qu’ils se rencontrent.


Ces exercices couvrent différentes utilisations des pointeurs en C, allant de la manipulation basique à des usages plus complexes comme les pointeurs sur pointeurs et la gestion de tableaux dynamiques. Ils sont conçus pour vous aider à mieux comprendre et maîtriser les pointeurs, un aspect fondamental et puissant du langage C.

Autres articles

Guide : Comment créer un QCM en...
Le QCM en langage C peut être simulé dans un...
Read more
Tableaux en Langage C : Exercices Corrigés
Voici une série d'exercices corrigés sur les tableaux en langage...
Read more
Comment fonctionne la récursion terminale en C...
La récursion terminale en CLa récursion terminale est une forme...
Read more

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *