web-dev-qa-db-fra.com

Comment fonctionnent les pointeurs sur les pointeurs en C?

Comment les pointeurs vers les pointeurs fonctionnent-ils en C? Quand les utiliseriez-vous?

156
debugger

Supposons un ordinateur 8 bits avec des adresses 8 bits (et donc seulement 256 octets de mémoire). Cela fait partie de cette mémoire (les nombres en haut sont les adresses):

  54   55   56   57   58   59   60   61   62   63   64   65   66   67   68   69
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
|    | 58 |    |    | 63 |    | 55 |    |    | h  | e  | l  | l  | o  | \0 |    |
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+

Ce que vous pouvez voir ici, c'est qu'à l'adresse 63, la chaîne "hello" commence. Donc, dans ce cas, s'il s'agit de la seule occurrence de "hello" en mémoire,

const char *c = "hello";

... définit c comme étant un pointeur sur la chaîne "hello" (en lecture seule) et contient donc la valeur 63. c doit lui-même être stocké quelque part: dans l'exemple ci-dessus à l'emplacement 58. Bien sûr, nous ne pouvons pas uniquement pointer aux personnages, mais aussi à d'autres pointeurs. Par exemple.:

const char **cp = &c;

Maintenant, cp pointe sur c, c'est-à-dire qu'il contient l'adresse de c (qui est 58). Nous pouvons aller encore plus loin. Considérer:

const char ***cpp = &cp;

Maintenant, cpp stocke l'adresse de cp. Il a donc la valeur 55 (d'après l'exemple ci-dessus) et vous l'avez deviné: il est lui-même stocké à l'adresse 60.


Quant à pourquoi on utilise des pointeurs à pointeurs:

  • Le nom d'un tableau donne généralement l'adresse de son premier élément. Ainsi, si le tableau contient des éléments de type t, une référence au tableau est de type t *. Considérons maintenant un tableau de tableaux de type t: naturellement, une référence à ce tableau 2D aura le type (t *)* = t ** et constitue donc un pointeur sur un pointeur.
  • Même si un tableau de chaînes sonne unidimensionnel, il est en fait bidimensionnel, car les chaînes sont des tableaux de caractères. D'où: char **.
  • Une fonction f devra accepter un argument de type t ** si elle doit modifier une variable de type t *.
  • Beaucoup d'autres raisons qui sont trop nombreuses pour être énumérées ici.
335
Stephan202

Comment les pointeurs vers les pointeurs fonctionnent-ils en C?

Tout d'abord, un pointeur est une variable, comme toute autre variable, mais qui contient l'adresse d'une variable.

Un pointeur sur un pointeur est une variable, comme toute autre variable, mais qui contient l'adresse d'une variable. Il se trouve que cette variable est simplement un pointeur.

Quand les utiliseriez-vous?

Vous pouvez les utiliser lorsque vous devez renvoyer un pointeur sur une mémoire du tas, sans utiliser la valeur de retour. 

Exemple:

int getValueOf5(int *p)
{
  *p = 5;
  return 1;//success
}

int get1024HeapMemory(int **p)
{
  *p = malloc(1024);
  if(*p == 0)
    return -1;//error
  else 
    return 0;//success
}

Et vous l'appelez comme ça:

int x;
getValueOf5(&x);//I want to fill the int varaible, so I pass it's address in
//At this point x holds 5

int *p;    
get1024HeapMemory(&p);//I want to fill the int* variable, so I pass it's address in
//At this point p holds a memory address where 1024 bytes of memory is allocated on the heap

Il y a aussi d'autres utilisations, comme l'argument main () de chaque programme C a un pointeur sur un pointeur pour argv, où chaque élément contient un tableau de caractères qui sont les options de ligne de commande. Vous devez cependant faire attention lorsque vous utilisez des pointeurs de pointeurs pour pointer sur des tableaux à 2 dimensions, il est préférable d'utiliser un pointeur sur un tableau à 2 dimensions. 

Pourquoi c'est dangereux?

void test()
{
  double **a;
  int i1 = sizeof(a[0]);//i1 == 4 == sizeof(double*)

  double matrix[ROWS][COLUMNS];
  int i2 = sizeof(matrix[0]);//i2 == 240 == COLUMNS * sizeof(double)
}

Voici un exemple de pointeur sur un tableau à 2 dimensions correctement:

int (*myPointerTo2DimArray)[ROWS][COLUMNS]

Toutefois, vous ne pouvez pas utiliser de pointeur sur un tableau à 2 dimensions si vous souhaitez prendre en charge un nombre variable d'éléments pour ROWS et COLUMNS. Mais quand vous savez à l'avance, vous utiliseriez un tableau à 2 dimensions.

41
Brian R. Bondy

J'aime cet exemple de code "real world" de pointeur sur usage de pointeur, dans Git 2.0, commit 7b1004b :

Linus a dit une fois:

En fait, je souhaite que plus de gens comprennent le type de codage de bas niveau vraiment fondamental. Pas de gros trucs complexes comme la recherche de nom sans verrou, mais simplement une bonne utilisation des pointeurs sur pointeurs, etc.
Par exemple, j'ai vu trop de personnes supprimer une entrée de liste à lien unique en gardant une trace de l'entrée "prev", puis supprimer l'entrée, en effectuant une opération comme

if (prev)
  prev->next = entry->next;
else
  list_head = entry->next;

et chaque fois que je vois un code comme celui-ci, je dis simplement "Cette personne ne comprend pas les indicateurs" Et c'est malheureusement assez commun.

Les personnes qui comprennent les pointeurs utilisent simplement un "pointeur vers le pointeur d'entrée} _" et l'initialisent avec l'adresse de list_head. Ensuite, lorsqu'ils parcourent la liste, ils peuvent supprimer l'entrée sans utiliser de condition, simplement en effectuant une opération. 

*pp =  entry->next

http://i.stack.imgur.com/bpfxT.gif

L'application de cette simplification nous permet de perdre 7 lignes de cette fonction même en ajoutant 2 lignes de commentaire.

-   struct combine_diff_path *p, *pprev, *ptmp;
+   struct combine_diff_path *p, **tail = &curr;

Chris souligne dans les commentaires à la vidéo de 2016 " Problème du double pointeur de Linus Torvalds " de Philip Buuck.


kumar souligne dans les commentaires l'article de blog " Linus sur la compréhension des pointeurs ", où Grisha Trubetskoy explique:

Imaginez que vous ayez une liste chaînée définie comme:

typedef struct list_entry {
    int val;
    struct list_entry *next;
} list_entry;

Vous devez itérer dessus du début à la fin et supprimer un élément spécifique dont la valeur est égale à la valeur de to_remove.
La manière la plus évidente de le faire serait:

list_entry *entry = head; /* assuming head exists and is the first entry of the list */
list_entry *prev = NULL;

while (entry) { /* line 4 */
    if (entry->val == to_remove)     /* this is the one to remove ; line 5 */
        if (prev)
           prev->next = entry->next; /* remove the entry ; line 7 */
        else
            head = entry->next;      /* special case - first entry ; line 9 */

    /* move on to the next entry */
    prev = entry;
    entry = entry->next;
}

Ce que nous faisons ci-dessus est:

  • itérer sur la liste jusqu'à ce que l'entrée soit NULL, ce qui signifie que nous avons atteint la fin de la liste (ligne 4). 
  • Lorsque nous rencontrons une entrée que nous voulons supprimer (ligne 5),
    • on assigne la valeur du pointeur suivant au précédent, 
    • éliminant ainsi l'élément actuel (ligne 7).

Il existe un cas particulier ci-dessus - au début de l'itération, il n'y a pas d'entrée précédente (prev est NULL), et donc pour supprimer la première entrée de la liste, vous devez modifier la tête elle-même (ligne 9).

Ce que Linus disait, c’est que le code ci-dessus pourrait être simplifié en faisant de l’élément précédent un pointeur sur un pointeur plutôt que simplement un pointeur.
Le code ressemble alors à ceci:

list_entry **pp = &head; /* pointer to a pointer */
list_entry *entry = head;

while (entry) {
    if (entry->val == to_remove)
        *pp = entry->next;

    pp = &entry->next;
    entry = entry->next;
}

Le code ci-dessus est très similaire à la variante précédente, mais notez que nous n'avons plus besoin de surveiller le cas particulier du premier élément de la liste, puisque pp n'est pas NULL au début. Simple et intelligent.

En outre, quelqu'un dans ce fil a fait remarquer que c'est mieux parce que *pp = entry->next est atomique. Ce n'est certainement pas atomique.
L'expression ci-dessus contient deux opérateurs de déréférence (* et ->) et une affectation. Aucune de ces trois choses n'est atomique.
_ {C'est une idée fausse courante, mais hélas, pratiquement rien dans C ne devrait être supposé être atomique} (y ​​compris les opérateurs ++ et --)!

26
VonC

Lorsque nous couvrons des indicateurs d’un cours de programmation à l’université, nous avons reçu deux indications sur la manière de commencer à les apprendre. La première était de voir Pointer Fun With Binky . La seconde était de penser aux Les yeux de Haddocks passage de Lewis Carroll's À travers le miroir

"Tu es triste", dit le Chevalier d'un ton inquiet: "Laisse-moi te chanter une chanson pour te réconforter."

"Est-ce que c'est très long?" Demanda Alice, car elle avait entendu beaucoup de poésie ce jour-là.

«C'est long, dit le chevalier, mais c'est vraiment très beau. Tous ceux qui m'entendent le chanter - soit ça leur met les larmes aux yeux, soit sinon… »

"Ou bien quoi?" Dit Alice, car le chevalier avait fait une pause soudaine.

«Sinon, tu sais pas. Le nom de la chanson s’appelle ‘Haddocks 'Eyes’. ”

"Oh, c'est le nom de la chanson, n'est-ce pas?" Dit Alice, essayant de se sentir intéressée.

"Non, vous ne comprenez pas," dit le chevalier, l'air un peu vexé. «C'est comme ça que le nom s'appelle. Le nom est vraiment "Le vieil homme âgé". "

"Alors j’aurais dû dire" c’est comme ça que la chanson s’appelle "?" Se corrigea Alice.

«Non, tu ne devrais pas: c'est tout autre chose! La chanson s’appelle ‘Ways And Means’: mais c’est comme ça qu’elle s’appelle, vous savez! ”

"Alors, quelle est la chanson, alors?" Dit Alice, qui était à ce moment-là complètement désemparée.

«J'y venais», dit le chevalier. "La chanson est vraiment" A-sitting On A Gate ": et la mélodie est ma propre invention."

13
Edd

Vous voudrez peut-être lire ceci: Pointeurs en pointeurs

J'espère que cela aide à clarifier certains doutes fondamentaux.

11
aJ.

Lorsqu'une référence à un pointeur est requise. Par exemple, lorsque vous souhaitez modifier la valeur (adresse pointée vers) d'une variable de pointeur déclarée dans l'étendue d'une fonction appelante à l'intérieur d'une fonction appelée. 

Si vous transmettez un seul pointeur en tant qu'argument, vous modifierez les copies locales du pointeur, et non le pointeur d'origine dans l'étendue de l'appel. Avec un pointeur sur un pointeur, vous modifiez ce dernier.

7
Alex Balashov

Un pointeur sur un pointeur est également appelé un handle. Une utilisation est souvent lorsqu'un objet peut être déplacé en mémoire ou supprimé. On est souvent responsable de verrouiller et déverrouiller l'utilisation de objet afin qu'il ne soit pas déplacé lors de son accès.

Il est souvent utilisé dans des environnements à mémoire restreinte, tels que Palm OS.

computer.howstuffworks.com Lien >>

www.flippinbits.com Lien >>

7
epatel

Vous avez une variable qui contient l'adresse de quelque chose. C'est un pointeur.

Ensuite, vous avez une autre variable qui contient l'adresse de la première variable. C'est un pointeur à pointeur.

5
Igor Oks

Considérez la figure et le programme ci-dessous pour mieux comprendre ce concept.

 Double pointer diagram

Selon la figure, ptr1 est un pointeur unique ayant l'adresse de la variable num.

ptr1 = #

De même, ptr2 est un pointeur sur pointeur (double pointeur) qui a l'adresse du pointeur ptr1

ptr2 = &ptr1;

Un pointeur qui pointe vers un autre pointeur est appelé double pointeur. Dans cet exemple, ptr2 est un double pointeur.

Valeurs du diagramme ci-dessus:

Address of variable num has : 1000
Address of Pointer ptr1 is: 2000
Address of Pointer ptr2 is: 3000

Exemple:

#include <stdio.h>

int main ()
{
   int  num = 10;
   int  *ptr1;
   int  **ptr2;

   // Take the address of var 
   ptr1 = &num;

   // Take the address of ptr1 using address of operator &
   ptr2 = &ptr1;

   // Print the value
   printf("Value of num = %d\n", num );
   printf("Value available at *ptr1 = %d\n", *ptr1 );
   printf("Value available at **ptr2 = %d\n", **ptr2);
}

Sortie:

Value of num = 10
Value available at *ptr1 = 10
Value available at **ptr2 = 10
5
M.S Chaudhari

c'est un pointeur sur la valeur d'adresse du pointeur. (c'est terrible je sais)

en gros, il vous permet de passer un pointeur sur la valeur de l'adresse d'un autre pointeur, afin que vous puissiez modifier l'emplacement d'un autre pointeur à partir d'une sous-fonction, comme:

void changeptr(int** pp)
{
  *pp=&someval;
}
4
Luke Schafer

Un pointeur vers un pointeur est, bien, un pointeur vers un pointeur.

Un exemple significatif de someType ** est un tableau bidimensionnel: vous avez un tableau, rempli de pointeurs sur d'autres tableaux, donc lorsque vous écrivez

dpointer [5] [6]

vous accédez au tableau qui contient des pointeurs vers d'autres tableaux en 5ème position, obtenez le pointeur (laissez fpointer son nom) puis accédez au 6ème élément du tableau référencé à ce tableau (donc, fpointer [6]).

3
akappa

J'ai créé une vidéo de 5 minutes qui explique le fonctionnement des pointeurs:

https://www.youtube.com/watch?v=3X-ray3tDjQ

 pointer buckets

1
Johannes Fahrenkrug

Comment ça marche: C'est une variable qui peut stocker un autre pointeur.

Quand les utiliseriez-vous: Plusieurs en utilisent un si votre fonction veut construire un tableau et le retourner à l'appelant.

//returns the array of roll nos {11, 12} through paramater
// return value is total number of  students
int fun( int **i )
{
    int *j;
    *i = (int*)malloc ( 2*sizeof(int) );
    **i = 11;  // e.g., newly allocated memory 0x2000 store 11
    j = *i;
    j++;
    *j = 12; ;  // e.g., newly allocated memory 0x2004 store 12

    return 2;
}

int main()
{
    int *i;
    int n = fun( &i ); // hey I don't know how many students are in your class please send all of their roll numbers.
    for ( int j=0; j<n; j++ )
        printf( "roll no = %d \n", i[j] );

    return 0;
}
1
resultsway

Il y a tellement d'explications utiles, mais je n'ai pas trouvé une description courte, donc ..

Fondamentalement, le pointeur est l'adresse de la variable. Code de résumé court:

     int a, *p_a;//declaration of normal variable and int pointer variable
     a = 56;     //simply assign value
     p_a = &a;   //save address of "a" to pointer variable
     *p_a = 15;  //override the value of the variable

//print 0xfoo and 15 
//- first is address, 2nd is value stored at this address (that is called dereference)
     printf("pointer p_a is having value %d and targeting at variable value %d", p_a, *p_a); 

Des informations utiles peuvent également être trouvées dans le sujet Que signifie référence et déréférence

Et je ne suis pas sûr de savoir quand les pointeurs peuvent être utiles, mais en commun, il est nécessaire de les utiliser lorsque vous effectuez des allocation de mémoire manuelle/dynamique - malloc, calloc, etc.

J'espère donc que cela aidera également à clarifier la problématique :)

0
xxxvodnikxxx