Comment fonctionnent les pointeurs vers les pointeurs en C ? Quand les utiliseriez-vous ?
Oui, bon exemple, je comprends ce qu'ils sont, mais comment et quand les utiliser est plus important maintenant
Comment fonctionnent les pointeurs vers les pointeurs en C ? Quand les utiliseriez-vous ?
Supposons un ordinateur de 8 bits avec des adresses de 8 bits (et donc seulement 256 octets de mémoire). Voici une partie de cette mémoire (les chiffres 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, si c'est la seule occurrence de "hello" en mémoire, alors,
const char *c = "hello";
.... définit c
est un pointeur vers la chaîne de caractères (en lecture seule) "hello", 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 pouvons non seulement pointer vers des caractères, mais aussi vers d'autres pointeurs. Par exemple :
const char **cp = &c;
Maintenant cp
Les points suivants c
c'est-à-dire qu'il contient l'adresse de c
(qui est de 58). Nous pouvons aller encore plus loin. Pensez-y :
const char ***cpp = &cp;
Maintenant cpp
enregistre l'adresse de cp
. Il a donc une valeur de 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 sur des pointeurs :
t
une référence au tableau a le type t *
. Considérons maintenant un tableau de tableaux de type t
: naturellement une référence à ce tableau 2D sera de type (t *)*
= t **
et est donc un pointeur vers un pointeur.char **
.f
devra accepter un argument de type t **
s'il s'agit de modifier une variable de type t *
.
Oui, bon exemple, je comprends ce qu'ils sont, mais comment et quand les utiliser est plus important maintenant
Stephan a fait un bon travail en reproduisant, en gros, le diagramme de l'ouvrage de Kernighan & Richie The C Programming Language. Si vous programmez en C, que vous n'avez pas ce livre et que vous n'avez pas peur de la documentation papier, je vous conseille vivement de vous le procurer, la dépense (assez) modeste sera très vite rentabilisée en termes de productivité. Il tend à être très clair dans ses exemples.
Char* c = "hello" devrait être const char* c = "hello". De même, il est tout au plus trompeur de dire qu'"un tableau est stocké comme l'adresse du premier élément". Un tableau est stocké comme... un tableau. Souvent, son nom donne un pointeur sur son premier élément, mais pas toujours. En ce qui concerne les pointeurs sur les pointeurs, je dirais simplement qu'ils sont utiles lorsqu'une fonction doit modifier un pointeur passé en paramètre (on passe alors un pointeur sur le pointeur à la place).
Comment fonctionnent les pointeurs vers les pointeurs en C ?
Tout d'abord, un pointeur est une variable, comme n'importe quelle autre variable, mais qui contient l'adresse d'une variable.
Un pointeur vers un pointeur est une variable, comme n'importe quelle autre variable, mais qui contient l'adresse d'une variable. Cette variable se trouve être un pointeur.
Quand les utiliseriez-vous ?
Vous pouvez les utiliser lorsque vous avez besoin de renvoyer un pointeur vers une certaine mémoire sur le tas, mais 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 tu l'appelles 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 la ligne de commande. Vous devez cependant faire attention lorsque vous utilisez des pointeurs de pointeurs pour pointer vers des tableaux à 2 dimensions, il est préférable d'utiliser un pointeur vers un tableau à 2 dimensions à la place.
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 d'un pointeur vers un tableau à deux dimensions réalisé correctement :
int (*myPointerTo2DimArray)[ROWS][COLUMNS]
Vous ne pouvez cependant pas utiliser un pointeur vers un tableau à deux dimensions si vous souhaitez prendre en charge un nombre variable d'éléments pour les ROWS et COLUMNS. Mais si vous savez à l'avance que vous utiliserez un tableau à 2 dimensions.
J'aime cet exemple de code "réel" de l'utilisation de pointeur à pointeur, dans Git 2.0, commettre 7b1004b :
Linus a dit une fois :
J'aimerais que plus de gens comprennent le codage de base de bas niveau. Pas les trucs gros et complexes comme la recherche de nom sans verrouillage, mais simplement la bonne utilisation des pointeurs, etc.
Par exemple, j'ai vu trop de personnes qui suppriment une entrée de liste à lien unique en gardant la trace de l'entrée "prev", puis pour supprimer l'entrée, en faisant quelque chose du genre
if (prev)
prev->next = entry->next;
else
list_head = entry->next;
et à chaque fois que je vois du code comme ça, je me dis "Cette personne ne comprend pas les pointeurs". Et c'est malheureusement assez commun.
Les personnes qui comprennent les pointeurs utilisent simplement un " pointeur vers le pointeur d'entrée "et l'initialiser avec l'adresse de la tête de liste. Puis, au fur et à mesure qu'ils parcourent la liste, ils peuvent supprimer l'entrée sans utiliser de conditionnel, en faisant simplement un
*pp = entry->next
L'application de cette simplification nous permet de perdre 7 lignes de cette fonction tout en ajoutant 2 lignes de commentaire.
- struct combine_diff_path *p, *pprev, *ptmp;
+ struct combine_diff_path *p, **tail = &curr;
kumar souligne dans les commentaires l'article de blog " Linus sur la compréhension des pointeurs ", où Grisha Trubetskoy explique :
Imaginez que vous avez une liste liée définie comme :
typedef struct list_entry {
int val;
struct list_entry *next;
} list_entry;
Vous devez le parcourir du début à la fin et supprimer un élément spécifique dont la valeur est égale à la valeur de to_remove.
La façon 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 :
- en itérant 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),
- nous assignons la valeur du pointeur suivant actuel au pointeur précédent,
- éliminant ainsi l'élément courant (ligne 7).
Il y a un cas spécial ci-dessus - au début de l'itération il n'y a pas d'entrée précédente (
prev
esNULL
), et donc pour supprimer la première entrée de la liste, il faut modifier head lui-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 vers un pointeur plutôt qu'un simple 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 remarquez comment nous n'avons plus besoin de surveiller le cas particulier du premier élément de la liste, puisque
pp
n'est pasNULL
au début. Simple et astucieux.En outre, quelqu'un dans ce fil a commenté que la raison pour laquelle c'est mieux est parce que
*pp = entry->next
est atomique. Il n'est certainement PAS atomique .
L'expression ci-dessus contient deux opérateurs de déréférencement (*
y->
) et une affectation, et aucune de ces trois choses n'est atomique.
Il s'agit d'une idée fausse très répandue, mais hélas, presque rien en C ne doit être considéré comme atomique. (y compris le++
y--
opérateurs) !
Cela permettra de mieux comprendre - grisha.org/blog/2013/04/02/linus-on-understanding-pointers
Cette vidéo a été essentielle pour moi dans la compréhension de votre exemple. En particulier, je me sentais confus (et belliqueux) jusqu'à ce que je dessine un diagramme de mémoire et que je retrace la progression du programme. Cela dit, il me semble encore un peu mystérieux.
Lorsque nous avons abordé les pointeurs dans un cours de programmation à l'université, nous avons reçu deux indications sur la manière de commencer à les apprendre. Le premier consistait à regarder Le plaisir du chien d'arrêt avec Binky . La deuxième était de penser à la Les yeux de l'aiglefin un passage de l'œuvre de Lewis Carroll De l'autre côté du miroir
"Vous êtes triste", dit le chevalier d'un ton inquiet : "Laisse-moi te chanter une chanson pour te réconforter."
"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 très, très beau". Tous ceux qui m'entendent la chanter - ou bien ils en ont les larmes aux yeux, ou bien -"
"Ou sinon quoi ?" dit Alice, car le chevalier avait fait une pause soudaine.
"Ou sinon, ça ne le fait pas, tu sais. Le nom de la chanson s'appelle 'Haddocks' Eyes'."
"Oh, c'est le nom de la chanson, c'est ça ?" Alice a dit, en essayant d'être intéressée.
" Non, vous ne comprenez pas ", dit le chevalier, l'air un peu vexé. "C'est le nom qu'on lui donne. Le nom réel est 'L'homme âgé'."
"Alors j'aurais dû dire 'C'est le titre de la chanson' ?" Alice s'est corrigée.
"Non, vous ne devriez pas : c'est tout autre chose ! La chanson s'appelle 'Ways And Means' : mais ce n'est que son nom, vous savez !"
"Alors, quelle est la chanson ?" dit Alice, qui était à ce moment-là complètement déconcertée.
"J'y venais", a déclaré le Chevalier. "La chanson est vraiment 'A-sitting On A Gate' : et l'air est de ma propre invention."
Vous voudrez peut-être lire ceci : De pointeur à pointeur
J'espère que cela aidera à clarifier certains doutes de base.
Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.
45 votes
Non, ce n'est pas du travail à domicile...., je voulais juste savoir... parce que je vois cela souvent quand je lis du code C.
1 votes
Un pointeur sur un pointeur n'est pas un cas particulier de quelque chose, donc je ne comprends pas ce que vous ne comprenez pas à propos de void**.
1 votes
Pour les tableaux 2D, le meilleur exemple est la ligne de commande args "prog arg1 arg2" est stockée char**argv. Et si l'appelant ne veut pas allouer la mémoire ( la fonction appelée allouera la mémoire )
1 votes
Vous avez un bel exemple d'utilisation de "pointeur à pointeur" dans Git 2.0 : voir ma réponse ci-dessous