web-dev-qa-db-fra.com

Pourquoi utiliser le double pointeur? ou Pourquoi utiliser des pointeurs sur des pointeurs?

Quand faut-il utiliser un double pointeur en C? Quelqu'un peut-il expliquer avec un exemple?

Ce que je sais, c'est qu'un double pointeur est un pointeur vers un pointeur. Pourquoi aurais-je besoin d'un pointeur sur un pointeur?

203
manju

Si vous voulez avoir une liste de caractères (un mot), vous pouvez utiliser char *Word

Si vous voulez une liste de mots (une phrase), vous pouvez utiliser char **sentence

Si vous voulez une liste de phrases (un monologue), vous pouvez utiliser char ***monologue

Si vous voulez une liste de monologues (une biographie), vous pouvez utiliser char ****biography

Si vous voulez une liste de biographies (une biothèque), vous pouvez utiliser char *****biolibrary

Si vous voulez une liste de bio-bibliothèques (a ?? lol), vous pouvez utiliser char ******lol

... ...

oui, je sais que celles-ci pourraient ne pas être les meilleures structures de données


Exemple d'utilisation avec un très très très ennuyeux lol

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

int wordsinsentence(char **x) {
    int w = 0;
    while (*x) {
        w += 1;
        x++;
    }
    return w;
}

int wordsinmono(char ***x) {
    int w = 0;
    while (*x) {
        w += wordsinsentence(*x);
        x++;
    }
    return w;
}

int wordsinbio(char ****x) {
    int w = 0;
    while (*x) {
        w += wordsinmono(*x);
        x++;
    }
    return w;
}

int wordsinlib(char *****x) {
    int w = 0;
    while (*x) {
        w += wordsinbio(*x);
        x++;
    }
    return w;
}

int wordsinlol(char ******x) {
    int w = 0;
    while (*x) {
        w += wordsinlib(*x);
        x++;
    }
    return w;
}

int main(void) {
    char *Word;
    char **sentence;
    char ***monologue;
    char ****biography;
    char *****biolibrary;
    char ******lol;

    //fill data structure
    Word = malloc(4 * sizeof *Word); // assume it worked
    strcpy(Word, "foo");

    sentence = malloc(4 * sizeof *sentence); // assume it worked
    sentence[0] = Word;
    sentence[1] = Word;
    sentence[2] = Word;
    sentence[3] = NULL;

    monologue = malloc(4 * sizeof *monologue); // assume it worked
    monologue[0] = sentence;
    monologue[1] = sentence;
    monologue[2] = sentence;
    monologue[3] = NULL;

    biography = malloc(4 * sizeof *biography); // assume it worked
    biography[0] = monologue;
    biography[1] = monologue;
    biography[2] = monologue;
    biography[3] = NULL;

    biolibrary = malloc(4 * sizeof *biolibrary); // assume it worked
    biolibrary[0] = biography;
    biolibrary[1] = biography;
    biolibrary[2] = biography;
    biolibrary[3] = NULL;

    lol = malloc(4 * sizeof *lol); // assume it worked
    lol[0] = biolibrary;
    lol[1] = biolibrary;
    lol[2] = biolibrary;
    lol[3] = NULL;

    printf("total words in my lol: %d\n", wordsinlol(lol));

    free(lol);
    free(biolibrary);
    free(biography);
    free(monologue);
    free(sentence);
    free(Word);
}

Sortie:

nombre total de mots dans mon lol: 243
414
pmg

Une des raisons est que vous souhaitez modifier la valeur du pointeur transmis à une fonction en tant qu'argument de la fonction. Pour ce faire, vous avez besoin d'un pointeur sur un pointeur.

En termes simples, Utilisez ** lorsque vous souhaitez conserver (OU conserver les modifications dans) l’affectation ou l’affectation de mémoire, même en dehors d’un appel de fonction. (Donc, transmettez cette fonction avec l'argument à double pointeur.)

Ce n'est peut-être pas un très bon exemple, mais vous montrera l'utilisation de base:

void allocate(int** p)
{
  *p = (int*)malloc(sizeof(int));
}

int main()
{
  int* p = NULL;
  allocate(&p);
  *p = 42;
  free(p);
}
155
Asha

Voici une réponse simple !!!!

  • disons que vous avez un pointeur que sa valeur est une adresse.
  • mais maintenant vous voulez changer cette adresse.
  • vous pourriez, en faisant pointer1 = pointer2, et pointer1 aurait maintenant l'adresse de pointer2.
  • MAIS! si vous souhaitez qu'une fonction le fasse pour vous et que le résultat soit conservé après son exécution, vous devez effectuer un travail supplémentaire, vous avez besoin d'un nouveau pointeur3 juste pour pointer sur le pointeur1 et pour lui transmettre le pointeur3.

  • voici un exemple amusant (regardez d'abord le résultat ci-dessous pour le comprendre!):

#include <stdio.h>

int main()
{

    int c = 1;
    int d = 2;
    int e = 3;
    int * a = &c;
    int * b = &d;
    int * f = &e;
    int ** pp = &a;  // pointer to pointer 'a'

    printf("\n a's value: %x \n", a);
    printf("\n b's value: %x \n", b);
    printf("\n f's value: %x \n", f);
    printf("\n can we change a?, lets see \n");
    printf("\n a = b \n");
    a = b;
    printf("\n a's value is now: %x, same as 'b'... it seems we can, but can we do it in a function? lets see... \n", a);
    printf("\n cant_change(a, f); \n");
    cant_change(a, f);
    printf("\n a's value is now: %x, Doh! same as 'b'...  that function tricked us. \n", a);

    printf("\n NOW! lets see if a pointer to a pointer solution can help us... remember that 'pp' point to 'a' \n");
     printf("\n change(pp, f); \n");
    change(pp, f);
    printf("\n a's value is now: %x, YEAH! same as 'f'...  that function ROCKS!!!. \n", a);
    return 0;
}

void cant_change(int * x, int * z){
    x = z;
    printf("\n ----> value of 'a' is: %x inside function, same as 'f', BUT will it be the same outside of this function? lets see\n", x);
}

void change(int ** x, int * z){
    *x = z;
    printf("\n ----> value of 'a' is: %x inside function, same as 'f', BUT will it be the same outside of this function? lets see\n", *x);
}
  • et voici la sortie:
 a's value: bf94c204

 b's value: bf94c208 

 f's value: bf94c20c 

 can we change a?, lets see 

 a = b 

 a's value is now: bf94c208, same as 'b'... it seems we can, but can we do it in a function? lets see... 

 cant_change(a, f); 

 ----> value of 'a' is: bf94c20c inside function, same as 'f', BUT will it be the same outside of this function? lets see

 a's value is now: bf94c208, Doh! same as 'b'...  that function tricked us. 

 NOW! lets see if a pointer to a pointer solution can help us... remember that 'pp' point to 'a' 

 change(pp, f); 

 ----> value of 'a' is: bf94c20c inside function, same as 'f', BUT will it be the same outside of this function? lets see

 a's value is now: bf94c20c, YEAH! same as 'f'...  that function ROCKS!!!. 
67

En ajoutant à la réponse d'Asha, si vous utilisez un seul pointeur sur l'exemple ci-dessous (par exemple, alloc1 ()), vous perdrez la référence à la mémoire allouée à l'intérieur de la fonction.

void alloc2(int** p) {
   *p = (int*)malloc(sizeof(int));
   **p = 10;
}

void alloc1(int* p) {
   p = (int*)malloc(sizeof(int));
   *p = 10;
}

int main(){
   int *p;
   alloc1(p);
   //printf("%d ",*p);//value is undefined
   alloc2(&p);
   printf("%d ",*p);//will print 10
   free(p);
   return 0;
}

La raison pour laquelle cela se produit est que, dans alloc1, le pointeur est transmis par valeur. Ainsi, lorsqu'il est réaffecté au résultat de l'appel malloc à l'intérieur de alloc1, la modification ne concerne pas le code d'une autre portée.

39
Silviu

1. Concept de base -

Lorsque vous déclarez ce qui suit: -

1. char * ch - (appelé pointeur de caractère)
- ch contient l'adresse d'un seul caractère.
- (* ch) va déréférencer la valeur du caractère. 

2. caractère ** ch -
'ch' contient l'adresse d'un tableau de pointeurs de caractères. (comme dans 1)
'* ch' contient l'adresse d'un seul caractère. (Notez que c'est différent de 1, en raison de la différence de déclaration).
(** ch) va déréférencer la valeur exacte du caractère.

L'ajout de pointeurs étend la dimension d'un type de données, d'un caractère à une chaîne, d'un tableau de chaînes, etc.). Vous pouvez l'associer à une matrice 1d, 2d, 3d ..

Ainsi, l'utilisation du pointeur dépend de la façon dont vous le déclarez.

Voici un code simple ..

int main()
{
    char **p;
    p = (char **)malloc(100);
    p[0] = (char *)"Apple";      // or write *p, points to location of 'A'
    p[1] = (char *)"Banana";     // or write *(p+1), points to location of 'B'

    cout << *p << endl;          //Prints the first pointer location until it finds '\0'
    cout << **p << endl;         //Prints the exact character which is being pointed
    *p++;                        //Increments for the next string
    cout << *p;
}

2. Une autre application des pointeurs doubles -
(cela couvrirait aussi les passages par référence)

Supposons que vous souhaitiez mettre à jour un caractère d'une fonction. Si vous essayez ce qui suit: -

void func(char ch)
{
    ch = 'B';
}

int main()
{
    char ptr;
    ptr = 'A';
    printf("%c", ptr);

    func(ptr);
    printf("%c\n", ptr);
}

La sortie sera AA. Cela ne fonctionne pas, car vous avez "passé par valeur" à la fonction.

La bonne façon de faire serait -

void func( char *ptr)        //Passed by Reference
{
    *ptr = 'B';
}

int main()
{
    char *ptr;
    ptr = (char *)malloc(sizeof(char) * 1);
    *ptr = 'A';
    printf("%c\n", *ptr);

    func(ptr);
    printf("%c\n", *ptr);
}

Maintenant, étendez cette exigence pour mettre à jour une chaîne au lieu d'un caractère.
Pour cela, vous devez recevoir le paramètre dans la fonction sous la forme d'un double pointeur.

void func(char **str)
{
    strcpy(str, "Second");
}

int main()
{
    char **str;
    // printf("%d\n", sizeof(char));
    *str = (char **)malloc(sizeof(char) * 10);          //Can hold 10 character pointers
    int i = 0;
    for(i=0;i<10;i++)
    {
        str = (char *)malloc(sizeof(char) * 1);         //Each pointer can point to a memory of 1 character.
    }

    strcpy(str, "First");
    printf("%s\n", str);
    func(str);
    printf("%s\n", str);
}

Dans cet exemple, la méthode s'attend à ce qu'un pointeur double en tant que paramètre mette à jour la valeur d'une chaîne.

22
Bhavuk Mathur

J'ai vu un très bon exemple aujourd'hui, de cet article de blog , comme je le résume ci-dessous.

Imaginez que vous ayez une structure pour les nœuds dans une liste chaînée, ce qui est probablement

typedef struct node
{
    struct node * next;
    ....
} node;

Maintenant, vous voulez implémenter une fonction remove_if, qui accepte un critère de suppression rm comme l'un des arguments et parcourt la liste liée: si une entrée satisfait le critère (quelque chose comme rm(entry)==true), son nœud sera supprimé de la liste. À la fin, remove_if renvoie la tête (qui peut être différente de la tête d'origine) de la liste liée.

Vous pouvez écrire

for (node * prev = NULL, * curr = head; curr != NULL; )
{
    node * const next = curr->next;
    if (rm(curr))
    {
        if (prev)  // the node to be removed is not the head
            prev->next = next;
        else       // remove the head
            head = next;
        free(curr);
    }
    else
        prev = curr;
    curr = next;
}

comme votre boucle for. Le message est: sans les doubles pointeurs, vous devez conserver une variable prev pour réorganiser les pointeurs et gérer les deux cas différents.

Mais avec les doubles pointeurs, vous pouvez réellement écrire

// now head is a double pointer
for (node** curr = head; *curr; )
{
    node * entry = *curr;
    if (rm(entry))
    {
        *curr = entry->next;
        free(entry);
    }
    else
        curr = &entry->next;
}

Vous n'avez pas besoin d'une prev maintenant car vous pouvez modifier directement ce que prev->next a pointé sur.

Pour clarifier les choses, suivons un peu le code. Pendant le déménagement:

  1. si entry == *head: ce sera *head (==*curr) = *head->next - head pointe maintenant vers le pointeur du nouveau noeud d'en-tête. Pour ce faire, vous remplacez directement le contenu de head par un nouveau pointeur.
  2. si entry != *head: De même, *curr est ce que prev->next a indiqué, et pointe maintenant sur entry->next.

Peu importe dans quel cas, vous pouvez réorganiser les pointeurs de manière unifiée avec des pointeurs doubles.

19
ziyuang

Les pointeurs vers des pointeurs s'avèrent également utiles en tant que "poignées" vers la mémoire, dans lesquelles vous souhaitez faire passer une "poignée" entre des fonctions afin de repositionner la mémoire. Cela signifie fondamentalement que la fonction peut changer la mémoire pointée par le pointeur à l'intérieur de la variable de descripteur et que chaque fonction ou objet utilisant le descripteur pointe correctement vers la mémoire nouvellement déplacée (ou allouée). Les bibliothèques aiment faire cela avec des types de données "opaques", c'est-à-dire des types de données où vous n'avez pas à vous soucier de ce qu'elles font avec la mémoire pointée, vous faites simplement passer le "handle" entre les fonctions de la bibliothèque pour effectuer certaines opérations sur cette mémoire ... les fonctions de la bibliothèque peuvent être allouer et désallouer de la mémoire sous le capot sans que vous ayez à vous soucier explicitement du processus de gestion de la mémoire ou du pointeur.

Par exemple:

#include <stdlib.h>

typedef unsigned char** handle_type;

//some data_structure that the library functions would work with
typedef struct 
{
    int data_a;
    int data_b;
    int data_c;
} LIB_OBJECT;

handle_type lib_create_handle()
{
    //initialize the handle with some memory that points to and array of 10 LIB_OBJECTs
    handle_type handle = malloc(sizeof(handle_type));
    *handle = malloc(sizeof(LIB_OBJECT) * 10);

    return handle;
}

void lib_func_a(handle_type handle) { /*does something with array of LIB_OBJECTs*/ }

void lib_func_b(handle_type handle)
{
    //does something that takes input LIB_OBJECTs and makes more of them, so has to
    //reallocate memory for the new objects that will be created

    //first re-allocate the memory somewhere else with more slots, but don't destroy the
    //currently allocated slots
    *handle = realloc(*handle, sizeof(LIB_OBJECT) * 20);

    //...do some operation on the new memory and return
}

void lib_func_c(handle_type handle) { /*does something else to array of LIB_OBJECTs*/ }

void lib_free_handle(handle_type handle) 
{
    free(*handle);
    free(handle); 
}


int main()
{
    //create a "handle" to some memory that the library functions can use
    handle_type my_handle = lib_create_handle();

    //do something with that memory
    lib_func_a(my_handle);

    //do something else with the handle that will make it point somewhere else
    //but that's invisible to us from the standpoint of the calling the function and
    //working with the handle
    lib_func_b(my_handle); 

    //do something with new memory chunk, but you don't have to think about the fact
    //that the memory has moved under the hood ... it's still pointed to by the "handle"
    lib_func_c(my_handle);

    //deallocate the handle
    lib_free_handle(my_handle);

    return 0;
}

J'espère que cela t'aides,

Jason

15
Jason

Les chaînes sont un excellent exemple d'utilisation de doubles pointeurs. La chaîne elle-même est un pointeur. Ainsi, chaque fois que vous devez pointer sur une chaîne, vous aurez besoin d'un double pointeur.

6
drysdam

Exemple simple que vous avez probablement déjà vu plusieurs fois

int main(int argc, char **argv)

Dans le deuxième paramètre, vous l'avez: pointeur à pointeur à caractère.

Notez que la notation du pointeur (char* c) et la notation du tableau (char c[]) sont interchangeables dans les arguments de fonction. Donc, vous pouvez aussi écrire char *argv[]. En d'autres termes, char *argv[] et char **argv sont interchangeables. 

Ce qui précède représente en fait un tableau de séquences de caractères (les arguments de ligne de commande attribués à un programme au démarrage).

Voir aussi cette réponse pour plus de détails sur la signature de fonction ci-dessus.

5
plats1

Voici un exemple très simple de C++ qui montre que si vous souhaitez utiliser une fonction pour définir un pointeur de manière à ce qu’il pointe sur un objet, vous avez besoin d’un pointeur sur un pointeur. Sinon, le pointeur continuera à revenir à null} _.

(Une réponse en C++, mais je crois que c'est la même chose en C.)

(En outre, pour référence: Google ("passer par valeur c ++") = "Par défaut, les arguments en C++ sont passés par valeur. Lorsqu'un argument est passé par valeur, la valeur de l'argument est copiée dans le paramètre de la fonction.")

Nous voulons donc que le pointeur b soit égal à la chaîne a.

#include <iostream>
#include <string>

void Function_1(std::string* a, std::string* b) {
  b = a;
  std::cout << (b == nullptr);  // False
}

void Function_2(std::string* a, std::string** b) {
  *b = a;
  std::cout << (b == nullptr);  // False
}

int main() {
  std::string a("Hello!");
  std::string* b(nullptr);
  std::cout << (b == nullptr);  // True

  Function_1(&a, b);
  std::cout << (b == nullptr);  // True

  Function_2(&a, &b);
  std::cout << (b == nullptr);  // False
}

// Output: 10100

Que se passe-t-il à la ligne Function_1(&a, b);?

  • La "valeur" de &main::a (une adresse) est copiée dans le paramètre std::string* Function_1::a. Par conséquent, Function_1::a est un pointeur sur (c'est-à-dire l'adresse mémoire de) la chaîne main::a.

  • La "valeur" de main::b (une adresse en mémoire) est copiée dans le paramètre std::string* Function_1::b. Par conséquent, il y a maintenant 2 de ces adresses en mémoire, les deux pointeurs nuls. Sur la ligne b = a;, la variable locale Function_1::b est ensuite remplacée par Function_1::a (= &main::a), mais la variable main::b reste inchangée. Après l'appel à Function_1, main::b est toujours un pointeur null.

Que se passe-t-il à la ligne Function_2(&a, &b);?

  • Le traitement de la variable a est le même: dans la fonction, Function_2::a est l'adresse de la chaîne main::a.

  • Mais la variable b est maintenant passée en tant que pointeur sur un pointeur. La "valeur" de &main::b (le adresse du pointeurmain::b) est copiée dans std::string** Function_2::b. Par conséquent, dans Function_2, le fait de déréférencer ceci en tant que *Function_2::b va accéder à main::b et le modifier. Donc, la ligne *b = a; définit réellement main::b (une adresse) égale à Function_2::a (= adresse de main::a), ce que nous voulons.

Si vous voulez utiliser une fonction pour modifier une chose, que ce soit un objet ou une adresse (pointeur), vous devez passer un pointeur sur cette chose. La chose que vous réellement passez in ne peut pas être modifié (dans la portée de l'appel) car une copie locale est faite.

(Une exception est si le paramètre est une référence, telle que std::string& a. Mais généralement, il s'agit de const. Généralement, si vous appelez f(x), si x est un objet, vous devriez pouvoir supposer que fn'est pas modifier x Mais si x est un pointeur, alors vous devriez supposer que fpourrait modifier l'objet pointé par x.)

4
jt117

Par exemple, vous pouvez vouloir vous assurer que lorsque vous libérez la mémoire de quelque chose, vous définissez le pointeur sur null par la suite.

void safeFree(void** memory) {
    if (*memory) {
        free(*memory);
        *memory = NULL;
    }
}

Lorsque vous appelez cette fonction, vous l’appelez avec l’adresse d’un pointeur

void* myMemory = someCrazyFunctionThatAllocatesMemory();
safeFree(&myMemory);

Maintenant, myMemory est défini sur NULL et toute tentative de le réutiliser sera manifestement une erreur.

4
Jeff Foster

Par exemple, si vous souhaitez un accès aléatoire à des données non contiguës.

p -> [p0, p1, p2, ...]  
p0 -> data1
p1 -> data2

- en C

T ** p = (T **) malloc(sizeof(T*) * n);
p[0] = (T*) malloc(sizeof(T));
p[1] = (T*) malloc(sizeof(T));

Vous stockez un pointeur p qui pointe vers un tableau de pointeurs. Chaque pointeur pointe vers une donnée.

Si sizeof(T) est grand, il ne sera peut-être pas possible d'allouer un bloc contigu (par exemple, malloc) de sizeof(T) * n octets.

2
log0

Une chose que je les utilise constamment est quand j'ai un tableau d'objets et que je dois effectuer des recherches (recherche binaire) sur eux par différents champs.
Je garde le tableau d'origine ...

int num_objects;
OBJECT *original_array = malloc(sizeof(OBJECT)*num_objects);

Ensuite, faites un tableau de pointeurs triés sur les objets.

int compare_object_by_name( const void *v1, const void *v2 ) {
  OBJECT *o1 = *(OBJECT **)v1;
  OBJECT *o2 = *(OBJECT **)v2;
  return (strcmp(o1->name, o2->name);
}

OBJECT **object_ptrs_by_name = malloc(sizeof(OBJECT *)*num_objects);
  int i = 0;
  for( ; i<num_objects; i++)
    object_ptrs_by_name[i] = original_array+i;
  qsort(object_ptrs_by_name, num_objects, sizeof(OBJECT *), compare_object_by_name);

Vous pouvez créer autant de tableaux de pointeurs triés que nécessaire, puis utiliser une recherche binaire sur le tableau de pointeurs triés pour accéder à l'objet dont vous avez besoin à l'aide de vos données. Le tableau d'objets d'origine peut rester non trié, mais chaque tableau de pointeurs sera trié selon le champ spécifié.

2
DavidMFrey

Comme cela a été dit, l’application du double pointeur consiste à mettre à jour la chaîne afin que les modifications apportées soient reflétées.

#include <iostream>
#include <cstring>  // for using strcpy
using namespace std;

void change(char **temp)
{
   strcpy(temp[0],"new");
   strcpy(temp[1],"value");
}

int main()
{
   char **str;
   str = (char **)malloc(sizeof(char *)*3);
   str[0]=(char *)malloc(10);
   str[1]=(char *)malloc(10);
   strcpy(str[0],"old");
   strcpy(str[1],"name");
   char **temp = str;  // always use the temporary variable
   while(*temp!=NULL)
{
    cout<<*temp<<endl;
    temp++;
}
temp = str;   // making it point it to the original head because we have changed the address in while loop above
change(str);
while(*temp!=NULL)
{
   cout<<*temp<<endl;
   temp++;
}

free(temp);
free(str[0]);
free(str[1]);
free(str);
2
Abhishek D K

J'ai utilisé les doubles pointeurs aujourd'hui alors que je programmais quelque chose pour le travail, donc je peux dire pourquoi nous avons dû les utiliser (c'est la première fois que je dois utiliser des doubles pointeurs). Nous avons eu à traiter avec l'encodage en temps réel des images contenues dans des tampons qui sont membres de certaines structures. Dans le codeur, nous devions utiliser un pointeur sur l'une de ces structures. Le problème était que notre pointeur était modifié pour indiquer d'autres structures à partir d'un autre thread. Pour utiliser la structure actuelle dans le codeur, je devais utiliser un double pointeur afin de pointer vers le pointeur en cours de modification dans un autre thread. Au début, du moins pour nous, il n'était pas évident que nous devions adopter cette approche. Beaucoup d'adresses ont été imprimées dans le processus :)).

Vous DEVEZ utiliser des pointeurs doubles lorsque vous travaillez sur des pointeurs modifiés à d'autres endroits de votre application. Vous pouvez également trouver que les doubles pointeurs sont indispensables lorsque vous traitez avec du matériel renvoyé et adressé à vous.

1
Axenie Ionut

Pourquoi double pointeurs?

L’objectif est de changer ce que l’élève indique, à l’aide d’une fonction.

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


typedef struct Person{
    char * name;
} Person; 

/**
 * we need a ponter to a pointer, example: &studentA
 */
void change(Person ** x, Person * y){
    *x = y; // since x is a pointer to a pointer, we access its value: a pointer to a Person struct.
}

void dontChange(Person * x, Person * y){
    x = y;
}

int main()
{

    Person * studentA = (Person *)malloc(sizeof(Person));
    studentA->name = "brian";

    Person * studentB = (Person *)malloc(sizeof(Person));
    studentB->name = "erich";

    /**
     * we could have done the job as simple as this!
     * but we need more work if we want to use a function to do the job!
     */
    // studentA = studentB;

    printf("1. studentA = %s (not changed)\n", studentA->name);

    dontChange(studentA, studentB);
    printf("2. studentA = %s (not changed)\n", studentA->name);

    change(&studentA, studentB);
    printf("3. studentA = %s (changed!)\n", studentA->name);

    return 0;
}

/**
 * OUTPUT:
 * 1. studentA = brian (not changed)
 * 2. studentA = brian (not changed)
 * 3. studentA = erich (changed!)
 */
1

Un peu tard pour la fête, mais j'espère que cela aidera quelqu'un.

Dans les tableaux C, allouez toujours de la mémoire sur la pile. Par conséquent, une fonction ne peut pas renvoyer de tableau (non statique) car la mémoire allouée sur la pile est libérée automatiquement lorsque l'exécution atteint la fin du bloc en cours. C’est vraiment gênant lorsque vous souhaitez gérer des tableaux à deux dimensions (matrices) et implémenter quelques fonctions qui peuvent modifier et renvoyer des matrices. Pour ce faire, vous pouvez utiliser un pointeur à pointeur pour implémenter une matrice avec de la mémoire allouée dynamiquement:

/* Initializes a matrix */
float** init_matrix(int num_rows, int num_cols){
    // Allocate memory for num_rows float-pointers
    float** A = calloc(num_rows, sizeof(double*));
    // return NULL if the memory couldn't allocated
    if(A == NULL) return NULL;
    // For each float-pointer (row) allocate memory for num_cols floats
    for(int i = 0; i < num_rows; i++){
        A[i] = calloc(num_cols, sizeof(double));
        // return NULL if the memory couldn't allocated
        // and free the already allocated memory
        if(A[i] == NULL){
            for(int j = 0; j < i; j++){
                free(A[j]);
            }
            free(A);
            return NULL;
        }
    }
    return A;
} 

Voici une illustration:

 float**        float*            float
             -------------       ---------------------------------------------------------
   A ------> |   A[0]    | ----> | A[0][0] | A[0][1] | A[0][2] | ........ | A[0][cols-1] |
             | --------- |       ---------------------------------------------------------
             |   A[1]    | ----> | A[1][0] | A[1][1] | A[1][2] | ........ | A[1][cols-1] |
             | --------- |       ---------------------------------------------------------
             |     .     |                                    .
             |     .     |                                    .
             |     .     |                                    .
             | --------- |       ---------------------------------------------------------
             |   A[i]    | ----> | A[i][0] | A[i][1] | A[i][2] | ........ | A[i][cols-1] |
             | --------- |       ---------------------------------------------------------
             |     .     |                                    .
             |     .     |                                    .
             |     .     |                                    .
             | --------- |       ---------------------------------------------------------
             | A[rows-1] | ----> | A[rows-1][0] | A[rows-1][1] | ... | A[rows-1][cols-1] |
             -------------       ---------------------------------------------------------

Le pointeur float à float A pointe sur le premier élément A [0] d'un bloc de mémoire dont les éléments sont eux-mêmes des pointeurs flottants. Vous pouvez imaginer ces pointeurs flottants comme les lignes de la matrice. C'est la raison pour laquelle chaque float-pointeur alloue de la mémoire pour num_cols float-elements. De plus, A [i] pointe vers la i-ème ligne, c.-à-d. A [i] pointe vers A [i] [0] et il ne s’agit que du premier élément flottant du bloc de mémoire pour la i-ème ligne. Enfin, vous pouvez accéder facilement à l'élément de la i-ème ligne et de la j-ème colonne avec A [i] [j].

Voici un exemple complet qui illustre l'utilisation:

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

/* Initializes a matrix */
float** init_matrix(int num_rows, int num_cols){
    // Allocate memory for num_rows float-pointers
    float** matrix = calloc(num_rows, sizeof(double*));
    // return NULL if the memory couldn't allocated
    if(matrix == NULL) return NULL;
    // For each float-pointer (row) allocate memory for num_cols
    // floats
    for(int i = 0; i < num_rows; i++){
        matrix[i] = calloc(num_cols, sizeof(double));
        // return NULL if the memory couldn't allocated
        // and free the already allocated memory
        if(matrix[i] == NULL){
            for(int j = 0; j < i; j++){
                free(matrix[j]);
            }
            free(matrix);
            return NULL;
        }
    }
    return matrix;
}

/* Fills the matrix with random float-numbers between -1 and 1 */
void randn_fill_matrix(float** matrix, int rows, int cols){
    for (int i = 0; i < rows; ++i){
        for (int j = 0; j < cols; ++j){
            matrix[i][j] = (float) Rand()/Rand_MAX*2.0f-1.0f;
        }
    }
}


/* Frees the memory allocated by the matrix */
void free_matrix(float** matrix, int rows, int cols){
    for(int i = 0; i < rows; i++){
        free(matrix[i]);
    }
    free(matrix);
}

/* Outputs the matrix to the console */
void print_matrix(float** matrix, int rows, int cols){
    for(int i = 0; i < rows; i++){
        for(int j = 0; j < cols; j++){
            printf(" %- f ", matrix[i][j]);
        }
        printf("\n");
    }
}


int main(){
    srand(time(NULL));
    int m = 3, n = 3;
    float** A = init_matrix(m, n);
    randn_fill_matrix(A, m, n);
    print_matrix(A, m, n);
    free_matrix(A, m, n);
    return 0;
}
1
joni

Comparez en modifiant la valeur de la variable versus en modifiant la valeur du pointeur :

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

void changeA(int (*a))
{
  (*a) = 10;
}

void changeP(int *(*P))
{
  (*P) = malloc(sizeof((*P)));
}

int main(void)
{
  int A = 0;

  printf("orig. A = %d\n", A);
  changeA(&A);
  printf("modi. A = %d\n", A);

  /*************************/

  int *P = NULL;

  printf("orig. P = %p\n", P);
  changeP(&P);
  printf("modi. P = %p\n", P);

  free(P);

  return EXIT_SUCCESS;
}

Cela m'a aidé à éviter de renvoyer la valeur du pointeur lorsque celui-ci était modifié par la fonction appelée (utilisée dans la liste à liens simples).

OLD (mauvais):

int *func(int *P)
{
  ...
  return P;
}

int main(void)
{
  int *pointer;
  pointer = func(pointer);
  ...
}    

NOUVEAU (meilleur):

void func(int **pointer)
{
  ...
}

int main(void)
{
  int *pointer;
  func(&pointer);
  ...
}    
0
Sany

l'exemple suivant, que je vais donner, donnera un aperçu ou une intuition du fonctionnement des doubles pointeurs, je vais passer par les étapes.

1) try to understand the following statements
   char **str ;

   a) str is of type char ** whose value is an address of another pointer.
   b) *str is of type char * whose value is an address of variable or (it is a string itself).
   c) **str is of type char ,gives the value stored, in this case a character.

ce qui suit est le code auquel vous pouvez vous rapporter aux points ci-dessus (a, b, c) pour comprendre

str = (char **)malloc(sizeof(char *) *2); // here i am assigning the mem for two char *
       str[0]=(char *)"abcdefghij"; // assignin the value
       str[1]=(char *)"xyzlmnopqr"; 

maintenant, pour imprimer la valeur i.e des chaînes dans le tableau, il suffit de regarder le point b, En cas de chaîne, la valeur et l'adresse sont identiques, il n'est donc pas nécessaire de les déréférencer de nouveau.

cout<<*str<<endl;   // abcdefghij;

maintenant, pour imprimer la chaîne suivante, sortez d'une déréférence i.e (*) de * str en str puis incrémentez, comme indiqué ci-dessous

str++;

maintenant imprimer la chaîne

cout<<*str<<endl;        //xyzlmnopqr

maintenant pour imprimer uniquement les caractères d'une chaîne, reportez-vous au point c)

cout<<**str<<endl;  // prints the first character i.e "a"

maintenant, pour imprimer le caractère suivant d’une chaîne, c’est "b" sortir de 1 opérateur de déréférencement et l’incrémenter c’est-à-dire passer de ** str à * str et faire * str ++

*str++;

maintenant imprimer le personnage

cout<<**str<<endl;  // prints the second character i.e "b"

puisque les deux tableaux ("abcdefghij", "xylmnopqr") sont stockés dans un bloc de mémoire continu si la même chose est faite pour incrémenter l'adresse, tous les caractères de deux chaînes seront imprimés

0
Abhishek D K

Espérons que l'exemple suivant clarifie certains concepts concernant les pointeurs et les doubles pointeurs, leurs différences et leur utilisation dans des scénarios courants. 

    int* setptr(int *x)
    {
        printf("%u\n",&x);
        x=malloc(sizeof(int));
        *x=1;
        return x;
    }

In the above function setptr we can manipulate x either
1. by taking fn arg as int *x , doing  malloc and setting value of x and return x 
Or
    2. By taking arg as int ** and malloc and then set  **x value to some value.
Note: we cant set any general pointer directly without doing  malloc.Pointer indicates that it is a type of variable which can hold address of any data type.Now either we define a variable and give reference to it or we declare a pointer(int *x=NULL) and allocate some memory to it inside the called function where we pass x or a reference to it .. In either case we need to have address of a memory in the  pointer and in the case pointer initially points  to NULL or it is defined like int *x where it points  to any random address then we need to assign a valid memory address to pointer 

    1. either we need to allocate memory to it by malloc

    int *x=NULL means its address is 0.
    Now we need to either o following
    1.



    void main()
        {
            int *x;
            x=malloc
            *x=some_val;
        }
        Or
        void main()
        {
            int *x
            Fn(x);
        }

        void Fn(int **x)
        {
            *x=malloc;
            **x=5;
        }
        OR
        int * Fn(int *x)
        {
            x=malloc();
            *x=4;
            Return x;
        }


        2. Or we need to point it to a valid memory like a defined variable inside the function where pointer is defined.


        OR
        int main()
        {
            int a;
            int *x=&a;
            Fn(x);
            printf("%d",*x);
        }
        void Fn(int *x)
        {
            *x=2;
        }


     in both cases value pointed by x is changed inside fn

    But suppose if we do like


    int main()
    {
        int *x=NULL;
        printf("%u\n",sizeof(x));
        printf("%u\n",&x);
        x=setptr(x);
        //*x=2;
        printf("%d\n",*x);
        return 0;
    }

/* output
4
1
*/

#include<stdio.h>
void setptr(int *x)
{
    printf("inside setptr\n");
    printf("x=%u\n",x);
    printf("&x=%u\n",&x);
    //x=malloc(sizeof(int));
    *x=1;
    //return x;
}
int main()
{
    int *x=NULL;
    printf("x=%u\n",x);
    printf("&x=%u\n",&x);
    int a;
    x=&a;
    printf("x=%u\n",x);
    printf("&a=%u\n",&a);
    printf("&x=%u\n",&x);
    setptr(x);
    printf("inside main again\n");

    //*x=2;
    printf("x=%u\n",x);
    printf("&x=%u\n",&x);
    printf("*x=%d\n",*x);
    printf("a=%d\n",a);
    return 0;
}
0
Peter_pk

l’application du double pointeur, comme l’a montré Bhavuk Mathur, semble être fausse . Ici, l’exemple suivant est celui qui est valide

void func(char **str)
{
     strcpy(str[0],"second");
}

int main(){

    char **str;
    str = (char **)malloc(sizeof(char*)*1); // allocate 1 char* or string
    str[0] = (char *)malloc(sizeof(char)*10);      // allocate 10 character
    strcpy(str[0],"first");            // assign the string
    printf("%s\n",*str);
    func(str);            
    printf("%s\n",*str);           
    free(str[0]); 
    free(str);
}
0
Abhishek D K