Langage C/C++

Guide Complet : Pointeur de Pointeur en C

×

Recommandés

Guide : Implémenter get_iemedans des fichiers avec...
La fonction get_iemepermet de récupérer le...
En savoir plus
Guide : Implémenter un Fichier en Tableau...
Les fichiers en tableaux circulaires (ou...
En savoir plus
Tableaux en Langage C : Exercices Corrigés
Voici une série d'exercices corrigés sur...
En savoir plus
L'Arithmétique des Pointeurs en Langage C
L'arithmétique des pointeurs est une fonctionnalité...
En savoir plus
Exercices corrigés en langage C : Les...
Les structures en langage C sont...
En savoir plus
Comment savoir si un nombre est pair...
En programmation, il est souvent nécessaire...
En savoir plus

Un pointeur de pointeur (ou double pointeur) en C est une variable qui contient l’adresse d’un pointeur, lequel pointe lui-même vers une autre variable. Les pointeurs de pointeurs sont utilisés pour gérer des structures complexes, comme des tableaux 2D, pour manipuler des pointeurs dans des fonctions, et pour gérer dynamiquement la mémoire.


1. Définition et Syntaxe

Un pointeur de pointeur est déclaré en ajoutant deux astérisques (**) dans sa définition.

Syntaxe :

type **nom_du_pointeur;

Exemple :

int **ptr; // ptr est un pointeur vers un pointeur d'entiers

Dans cet exemple, ptr peut contenir l’adresse d’un autre pointeur qui, lui, pointe vers un entier.


2. Principe Fondamental

Pour comprendre le fonctionnement, imaginons trois niveaux de mémoire :

  1. Variable : Contient la valeur réelle.
  2. Pointeur : Contient l’adresse de la variable.
  3. Pointeur de pointeur : Contient l’adresse du pointeur.

Exemple de base :

#include <stdio.h>

int main() {
    int a = 10;
    int *p = &a;  // p pointe vers a
    int **pp = &p; // pp pointe vers p

    printf("Valeur de a : %d\n", a);
    printf("Adresse de a (contenue dans p) : %p\n", (void *)p);
    printf("Adresse de p (contenue dans pp) : %p\n", (void *)pp);
    printf("Valeur de a via pp : %d\n", **pp); // Double déréférencement
    return 0;
}

3. Applications des Pointeurs de Pointeurs

3.1 Modification d’un pointeur dans une fonction

Un pointeur de pointeur est souvent utilisé pour modifier un pointeur dans une fonction.

Exemple :

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

void allouerMemoire(int **ptr) {
    *ptr = malloc(sizeof(int)); // Alloue de la mémoire pour un entier
    if (*ptr != NULL) {
        **ptr = 42; // Initialise la valeur pointée
    }
}

int main() {
    int *p = NULL;

    allouerMemoire(&p); // Passe l’adresse de p
    if (p != NULL) {
        printf("Valeur : %d\n", *p); // Affiche 42
        free(p); // Libère la mémoire
    }
    return 0;
}

3.2 Allocation Dynamique d’un Tableau 2D

Les pointeurs de pointeurs sont utilisés pour créer des tableaux 2D dynamiques.

Exemple :

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

int main() {
    int rows = 3, cols = 4;
    int **matrix = malloc(rows * sizeof(int *)); // Alloue un tableau de pointeurs

    for (int i = 0; i < rows; i++) {
        matrix[i] = malloc(cols * sizeof(int)); // Alloue chaque ligne
    }

    // Initialisation
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            matrix[i][j] = i + j;
        }
    }

    // Affichage
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            printf("%d ", matrix[i][j]);
        }
        printf("\n");
    }

    // Libération de la mémoire
    for (int i = 0; i < rows; i++) {
        free(matrix[i]);
    }
    free(matrix);

    return 0;
}

3.3 Tableaux de chaînes de caractères

Les pointeurs de pointeurs sont utilisés pour gérer des tableaux de chaînes de caractères.

Exemple :

#include <stdio.h>

int main() {
    char *noms[] = {"Alice", "Bob", "Charlie"};
    char **ptr = noms; // Pointeur vers un tableau de chaînes

    for (int i = 0; i < 3; i++) {
        printf("%s\n", ptr[i]); // Affiche chaque chaîne
    }
    return 0;
}

3.4 Manipulation d’une liste chaînée

Les pointeurs de pointeurs permettent d’insérer des éléments dans une liste chaînée.

Exemple :

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

typedef struct Node {
    int data;
    struct Node *next;
} Node;

void ajouterEnTete(Node **head, int valeur) {
    Node *nouveau = malloc(sizeof(Node));
    if (nouveau != NULL) {
        nouveau->data = valeur;
        nouveau->next = *head;
        *head = nouveau;
    }
}

void afficherListe(Node *head) {
    while (head != NULL) {
        printf("%d -> ", head->data);
        head = head->next;
    }
    printf("NULL\n");
}

int main() {
    Node *head = NULL;

    ajouterEnTete(&head, 10);
    ajouterEnTete(&head, 20);
    ajouterEnTete(&head, 30);

    afficherListe(head);

    // Libération de la mémoire
    Node *temp;
    while (head != NULL) {
        temp = head;
        head = head->next;
        free(temp);
    }

    return 0;
}

4. Précautions à Prendre

  1. Initialisation : Assurez-vous que les pointeurs et pointeurs de pointeurs sont correctement initialisés avant usage. int **pp = NULL;
  2. Vérification des allocations : Vérifiez toujours que malloc ne retourne pas NULL.
  3. Libération de mémoire : Libérez chaque niveau de mémoire alloué pour éviter les fuites. free(pointeur[i]); // Libère chaque ligne free(pointeur); // Libère les pointeurs des lignes
  4. Évitez les accès invalides : Assurez-vous que les pointeurs pointent vers des zones valides avant de les déréférencer.

5. Résumé des Avantages

AvantagesDescription
Manipulation flexiblePermet de modifier des pointeurs à l’intérieur des fonctions.
Gestion de structures complexesSimplifie la gestion des tableaux 2D ou des listes chaînées.
Optimisation mémoirePrend en charge des allocations dynamiques pour des besoins variés.

Cas Particuliers des Pointeurs de Pointeurs en C

Les pointeurs de pointeurs en C sont des outils puissants pour manipuler des structures complexes, mais leur utilisation peut présenter des cas particuliers qui nécessitent une attention particulière. Ce guide couvre ces situations, avec des explications et des exemples.


1. Pointeur de Pointeur NULL

Un pointeur de pointeur peut être NULL, comme n’importe quel autre pointeur. Il est essentiel de vérifier son état avant de l’utiliser pour éviter des comportements indéfinis.

Exemple :

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

void verifierPointeur(int **pp) {
    if (pp == NULL || *pp == NULL) {
        printf("Le pointeur ou le contenu du pointeur est NULL\n");
    } else {
        printf("Valeur : %d\n", **pp);
    }
}

int main() {
    int *p = NULL;
    int **pp = &p;

    verifierPointeur(pp); // Affiche : Le pointeur ou le contenu du pointeur est NULL

    int x = 42;
    p = &x;
    verifierPointeur(pp); // Affiche : Valeur : 42

    return 0;
}

2. Pointeur Dangling (Dangling Pointer)

Un pointeur de pointeur peut devenir dangling (non valide) si la mémoire à laquelle il fait référence est libérée.

Exemple de comportement problématique :

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

int main() {
    int *p = malloc(sizeof(int));
    int **pp = &p;

    *p = 100;
    free(p); // Libère la mémoire pointée par p

    printf("Valeur après free : %d\n", **pp); // Comportement indéfini

    return 0;
}

Solution :

Toujours réinitialiser les pointeurs après libération.

free(p);
p = NULL; // Évite le problème

3. Tableaux Dynamiques de Pointeurs

Les tableaux dynamiques impliquent souvent l’utilisation de pointeurs de pointeurs. Cependant, des erreurs de manipulation peuvent provoquer des dépassements de mémoire ou des fuites.

Exemple : Mauvaise manipulation d’un tableau dynamique

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

int main() {
    int **tableau = malloc(3 * sizeof(int *));
    for (int i = 0; i < 3; i++) {
        tableau[i] = malloc(4 * sizeof(int)); // Alloue chaque ligne
    }

    tableau[3][0] = 10; // Erreur : dépassement, tableau a 3 lignes (indices 0 à 2)

    // Libération partielle
    free(tableau[0]);
    free(tableau); // Fuite de mémoire pour tableau[1] et tableau[2]

    return 0;
}

Solution correcte :

Vérifiez toujours les limites et libérez toute la mémoire.

for (int i = 0; i < 3; i++) {
    free(tableau[i]);
}
free(tableau);

4. Pointeurs de Pointeurs dans les Fonctions

Passer un pointeur de pointeur à une fonction pour modifier un pointeur peut entraîner des erreurs si les pointeurs ne sont pas correctement initialisés.

Exemple de mauvaise utilisation :

#include <stdio.h>

void modifierPointeur(int **pp) {
    *pp = malloc(sizeof(int)); // Si pp est NULL, comportement indéfini
    **pp = 42;
}

int main() {
    int **pp = NULL; // Non initialisé
    modifierPointeur(pp); // Erreur : accès à une adresse invalide
    return 0;
}

Solution :

Assurez-vous que le pointeur est initialisé avant de le manipuler.

int main() {
    int *p = NULL;
    int **pp = &p; // pp pointe vers p
    modifierPointeur(pp);
    printf("Valeur : %d\n", *p); // Affiche 42
    free(p); // Libère la mémoire
    return 0;
}

5. Tableaux de Chaînes de Caractères

Les tableaux de chaînes de caractères impliquent des pointeurs de pointeurs, et leur gestion peut être complexe.

Exemple avec erreur :

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

int main() {
    char **noms = malloc(3 * sizeof(char *));
    noms[0] = "Alice"; // Correct, mais non alloué dynamiquement
    noms[1] = "Bob";
    noms[2] = "Charlie";

    free(noms[0]); // Erreur : "Alice" n'a pas été alloué avec malloc
    free(noms);

    return 0;
}

Solution correcte :

Allouez chaque chaîne dynamiquement.

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

int main() {
    char **noms = malloc(3 * sizeof(char *));
    noms[0] = strdup("Alice");
    noms[1] = strdup("Bob");
    noms[2] = strdup("Charlie");

    for (int i = 0; i < 3; i++) {
        printf("%s\n", noms[i]); // Affiche chaque nom
        free(noms[i]); // Libère chaque chaîne
    }
    free(noms); // Libère le tableau de pointeurs

    return 0;
}

6. Conversion entre Pointeurs et Tableaux

Un tableau 2D est souvent utilisé comme une matrice en C, mais il peut être manipulé avec des pointeurs de pointeurs, ce qui peut créer de la confusion.

Exemple : Erreur dans la conversion tableau-pointeur

#include <stdio.h>

void afficherMatrice(int **mat, int rows, int cols) {
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            printf("%d ", mat[i][j]); // Comportement indéfini si la mémoire n'est pas contiguë
        }
        printf("\n");
    }
}

int main() {
    int mat[2][2] = {{1, 2}, {3, 4}};
    afficherMatrice((int **)mat, 2, 2); // Conversion incorrecte, les pointeurs ne sont pas compatibles

    return 0;
}

Solution :

Si vous utilisez un tableau statique, transmettez son pointeur.

void afficherMatrice(int mat[2][2], int rows, int cols) {
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            printf("%d ", mat[i][j]);
        }
        printf("\n");
    }
}

Si la matrice est allouée dynamiquement, utilisez un double pointeur correctement configuré.


7. Détection des Fuites de Mémoire

Les erreurs fréquentes avec les pointeurs de pointeurs incluent les fuites de mémoire. Utilisez des outils comme Valgrind pour vérifier.

Exemple d’analyse avec Valgrind :

valgrind --leak-check=full ./programme

Résumé des Cas Particuliers

CasPrécaution / Solution
Pointeur NULLToujours vérifier avant d’utiliser un double pointeur.
Dangling PointerRéinitialisez les pointeurs à NULL après libération.
Tableaux dynamiquesVérifiez les limites et libérez chaque niveau de mémoire.
Passer un double pointeurAssurez-vous que les pointeurs sont initialisés correctement.
Tableaux de chaînesAllouez dynamiquement les chaînes si vous utilisez free.
Conversion tableau/pointeurFaites attention à la compatibilité des pointeurs et tableaux.

Ces cas particuliers montrent l’importance de gérer les pointeurs de pointeurs avec soin pour éviter les erreurs courantes en C.

Recommandés

Guide : Utilisation des Pointeurs sur Fonctions...
Un pointeur sur fonction en C...
En savoir plus
Malloc contre Calloc en C : Guide...
Les fonctions malloc et calloc sont...
En savoir plus
Tableaux en Langage C : Exercices Corrigés
Voici une série d'exercices corrigés sur...
En savoir plus
Multiplication de Deux Matrices en Langage C...
Dans cet article, nous...
En savoir plus
Code C++ pour faire un mot de...
Objectif de l'article Dans un monde numérique...
En savoir plus
La Magie des Majuscules en C :...
Lorsqu'il s'agit d'écrire du code en...
En savoir plus
AZ

Recent Posts

Classification des Documents : Organiser et Automatiser la Gestion Documentaire

Dans toute organisation moderne — entreprise, association, service administratif ou bureau de projet — la…

2 jours ago

Modèle de Bilan Actif Passif sur Excel : Concevoir un tableau comptable clair et automatisé

Dans la pratique comptable, le bilan constitue l’un des documents les plus fondamentaux pour comprendre…

2 jours ago

Fiche Méthode analyse linéaire + guide complet pour la réussir

L’analyse linéaire impressionne souvent plus qu’elle ne le devrait. Au moment d’aborder l’oral du bac…

2 jours ago

Analyse linéaire au bac français : méthode complète, exemples et conseils pour réussir l’oral

L’analyse linéaire occupe une place centrale à l’oral du bac français. C’est l’exercice qui permet…

3 jours ago

Créer une fiche de suivi en ligne : générateur personnalisable à imprimer

Créer une fiche de suivi claire et adaptée à son activité prend souvent plus de…

3 jours ago

Préparation physique football avec ballon : Fiche Word utile

Comment améliorer sa condition physique tout en travaillant la technique Quand on parle de préparation…

3 jours ago

This website uses cookies.