est-ce que le code que j'ai proposé le post avant est valable tout de même (même si...) ?
Version imprimable
est-ce que le code que j'ai proposé le post avant est valable tout de même (même si...) ?
Tu veux dire le code du message #18? Ben non, il ne marche pas: Les a et b de Echanger() sont des paramètres, des variables locales, donc des copies des a et b de main().
Seules les copies sont échangées, donc les a et b de main() conservent leurs valeurs respectives.
pourtant j'aurais juré qu'avec des variables normales ça marcherait
jamais je n'aurais pensé que ça faisait une sorte de copie si vous n'étiez pas là
ça me perturbe vachement cette histoire : je n'ai pas compris lors du passage des paramètres : quand vous dites "copie" ça veut dire quoi au juste ?
Ça veut dire que:
- Avant l'appel, tu as une variable n'importe où en mémoire: Cela peut être une variable globale, une variable allouée quelque part dans le tas (malloc() etc.), ou une variable locale.
- Lors de l'appel, la variable est copiée quelque part d'autre, dépendant de l'implémentation: Sur un processeur x86, c'est sur la pile: On décale le pointeur de pile avec la taille de la variable, et on écrit à cet endroit, tous les octets de la variable (dans des langages comme C++, c'est un poil plus compliqué).
- La fonction appelée travaille sur cette copie comme si c'était une de ses variables locales.
- Lorsque la fonction retourne, la copie est détruite. Elle n'est pas recopiée dans l'autre sens vers l'original, elle est simplement oubliée.
Un exemple pour montrer que c'est une copie, c'est que les deux variables possèdent des addresses différentes:
Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 #include <stdio.h> void UneFonction(int param) { int* pParam = ¶m; printf("Adresse de param: %p\n", (void*)pParam); } int main(void) { int varLocale; int *pVarLocale = &varLocale; printf("Adresse de varLocale: %p\n", (void*)pVarLocale); UneFonction(varLocale); return 0; }
En fait, sur une machine x86, tu peux considérer la mémoire comme un tableau, et les pointeurs comme des indexes dans ce tableau. Voici un équivalent simple:
Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 #include <stdio.h> #include <stddef.h> int g_memoire[10]; void Echange(size_t iGauche, size_t iDroite) { /*Note que le temporaire lui-même n'est pas un pointeur. C'est un int, vu qu'on échange les int pointés.*/ int temporaire = g_memoire[iGauche]; g_memoire[iGauche] = g_memoire[iDroite]; g_memoire[iDroite] = temporaire; } int main(void) { size_t ia, ib; g_memoire[0] = 1; g_memoire[1] = 42; ia = 0; ib = 1; printf("Avant : a=%d, b=%d, ia=%ld, ib=%ld\n", g_memoire[0], g_memoire[1], (long)ia, (long)ib); Echange(ia, ib); printf("Apres : a=%d, b=%d, ia=%ld, ib=%ld\n", g_memoire[0], g_memoire[1], (long)ia, (long)ib); return 0; }
bon déjà j'ai compris qu'un pointeur est une variable vide lorsqu'elle est déclarée et qui pointe sur une autre variable lors qu'on l'initialise
en vérité on assigne jamais un nombre à un point mais on lui indique une autre variable de même nature (ex ici un int)
ex : on n'écrit pasmaisCode:pa = 1
alors qu'avec une variable normale on peut écrire :Code:pa=&a
c'est ça en gros non ?Code:a=1
Oui, à cela près qu'une variable n'est jamais "vide".
Par contre, elle peut être non-initialisée (la lire entraine un comportement indéfini) ou nulle.
Un pointeur nul est un pointeur initialisé à zéro. En C, on utilise la macro NULL, qui est traditionnellement définie en tant que ((void*)0) (mais ça peut être, à la place, un truc plus spécifique au compilateur).
Donc, typiquement, à part quand on fait de la programmation à très bas niveau (pour de l'Embarqué par exemple), on ne met que 4 choses différentes dans un pointeur:
- NULL pour dire qu'il ne pointe sur rien du tout.
- L'adresse d'une variable existante, ou d'un champ de structure, ou même d'une fonction.
- La valeur retournée par une fonction qui retourne un pointeur (peut être une fonction à toi, ou une fonction comme malloc()).
- Le résultat d'un calcul sur des pointeurs: si p pointe sur le premier élément d'un tableau, alors p+1 pointe sur le second élément.
Il est important de ne jamais laisser un pointeur non-initialisé! S'il ne pointe sur rien du tout, l'initialiser à NULL. Pourquoi? Parce que le code peut tester si un pointeur est nul, alors qu'on ne peut pas tester si un pointeur est initialisé ou non.
ayé je pense avoir compris le truc (je pense) dites-moi si je me trompe
après avoir lu le tuto de cgi sur les pointeurs je pense avoir compris que :
=> un pointeur pointe vers 1 seule et unique adresse mémoire lorsqu'on l'initialise
avec &x
=> de ce pointeur on peut en tirer la valeur pointée à l'adresse mémoire
correspondante avec *px
=> pour reprendre l'exemple que vous m'avez donné avec la fonction
echange() : comme vous m'avez dit lors du passage des paramètres ça fait
une copie : ça copie la valeur à une autre adresse mémoire : n'importe
laquelle : on se retrouve avec la meme valeur mais à une autre adresse mémoire
=> la valeur copiée n'est active qu'à l'intérieur de la fonction et meurt à la fin
=> alors que les pointeurs ils disent tu vas à cette unique adresse mémoire
et c'est pour ça que ça marche
en fait je ne m'y connais pas en progra mais ça a l'air d'être un truc super puissant
C'est exactement ça!
C'est en effet très puissant et indispensable à bas niveau.
Dans un processeur x86, plusieurs registres sont tout le temps des pointeurs!
- IP - Instruction Pointer (EIP en 32 bits): Pointeur d'instruction: Il dit quelle partie du programme le processeur exécute
- SP - Stack Pointer (ESP en 32 bits): Pointeur de pile: Il indique le sommet de la pile; il est utilisé et modifié à chaque fois qu'on empile ou dépile un truc (comme justement, un paramètre de fonction).
- BP - Base Pointer (EBP en 32 bits): pointe à dans la pile, à l'endroit des variables locales et des paramètres: En fait, quand tu accèdes à une variable locale, vu du proc elle se trouve à EBP+(quelque chose).
Bonjour tout le monde,
Je sais que je dis merci 10 ans après mais merci :
=> pour vos précisions apportées sur les pointeurs qui m'ont permis de mieux comprendre à quoi ils servent.
=> pour la mise à disposition des tutos qui m'ont été utiles également
Tout cela me sera d'ailleurs utiles par la suite aussi.
Merci d'avoir pris un peu de votre temps pour m'expliquer tout cela.
Non vraiment.
A bientôt.
Cordialement,
Gizmo.
De rien! ;)
Bon courage pour la suite.
Bonsoir à toutes et à tous,
Je reviens avec une question concernant le code de Medinoc sur les pointeurs :
C'est la fonction Echange qui m'interpelle ici : au moment de l'appel de la fonction dans la partie main : il est marquéCode:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 #include <stdio.h> void Echange(int *pGauche, int *pDroite) { /*Note que le temporaire lui-même n'est pas un pointeur. C'est un int, vu qu'on échange les int pointés.*/ int temporaire = *pGauche; *pGauche = *pDroite; *pDroite = temporaire; } int main(void) { int a = 1; int b = 42; int *pa; int *pb; pa = &a; /*On initialise pa avec l'adresse de a. pa pointe maintenant sur a.*/ pb = &b; /*On initialise pb avec l'adresse de b. pb pointe maintenant sur b.*/ /*Note: la norme exige de caster un pointeur en void* avant de le passer à printf. C'est à cause de certaines machines où les pointeurs n'ont pas tous la même taille.*/ printf("Avant : a=%d, b=%d, pa=%p, pb=%p\n", a, b, (void*)pa, (void*)pb); Echange(pa, pb); printf("Après : a=%d, b=%d, pa=%p, pb=%p\n", a, b, (void*)pa, (void*)pb); return 0; }
Comme les pointeurs sont initialisés aux adresses de a et b, je ne comprend pas la chose suivante : lors de la déclaration de la fonction Echange, les paramètres sont des valeurs pointées.Code:Echange(pa, pb);
Par quel miracle des adresses de variable sont-elles transformées en entier ?
Lors de l'appel de la fonction dans le main, pourquoi ne met-on pas :?Code:Echange(*pa, *pb);
Car enfin ce sont les entiers a et b à échanger qui nous intéresse, et non pas d'échanger les adresses des variables ?
Voyez-vous ce que je veux dire, où je veux en venir ?
Non, les paramètres sont des pointeurs.Citation:
Comme les pointeurs sont initialisés aux adresses de a et b, je ne comprend pas la chose suivante : lors de la déclaration de la fonction Echange, les paramètres sont des valeurs pointées.
Elles ne sont pas transformées en entier. A partir des adresses pGauche ou pDroite, la fonction retrouve la valeur des entiers situés à ces adresses : *pGauche et *pDroiteCitation:
Par quel miracle des adresses de variable sont-elles transformées en entier ?
Si on veut échanger le contenu de deux variables, il faut savoir où elles sont. Connaitre leur valeur de suffit pas.
Si on écrit :
on donne en argument la valeur des variables a et b. Comment la fonction va t-elle faire pour prendre par exemple la valeur de a (pas de problème, elle la connait puisque c'est le premier argument) pour la mettre DANS la variable b (mais elle ne sait pas où est b) ?Code:Echange(*pa, *pb);
C'est pourquoi on passe les adresses. La fonction peut prendre la valeur de l'une (elle peut le faire puisqu'elle sait où elle est) pour la mettre dans l'autre variable (elle peut le faire puisqu'elle sait où elle est).
Bonsoir,
J'ai compris le système des pointeurs.
Mon problème vient de cet exemple en particulier.
Ce qui me "taquine" c'est que lors de sa déclaration, la fonction Echange attend des paramètres entiers int avec pour variable des pointeurs *pgauche,...
Donc quand on lui passe les paramètres on devrait mettre les étoiles pour dire que c'est bien les valeurs qu'on veut échanger et pas les adresses... Mais si on le fait alors on n'a pas résolu le problème car on fait une copie et l'on revient à l'exemple avec de simples variables et ça ne marche pas.
Grosso modo je n'ai pas saisi ce qui se passe au moment de l'appel de la fonction après l'avoir déclarée :et lors de l'appel :Code:
1
2
3
4
5
6
7
8 void Echange(int *pGauche, int *pDroite) { /*Note que le temporaire lui-même n'est pas un pointeur. C'est un int, vu qu'on échange les int pointés.*/ int temporaire = *pGauche; *pGauche = *pDroite; *pDroite = temporaire; }
avec pa et pb initialisée respectivement à &a et &b les adresses de a et b.Code:Echange(pa, pb);
Donc on lui fait passer en paramètres : ce que je ne comprend pas en regardant la fonction déclarée c'est : comment la fonction traite-t-elle les adresses pour qu'elle deviennent des pointeurs ?
Pour moi pa est différent de *pGauche : l'un est une adresse mémoire et l'autre est un pointeur : je n'arrive pas à faire le lien entre les deux.
Autrement dit quand on passe à la fonction l'adresse &a, pour moi *pGauche vaut &a... Vous saisissez ?
Ca n'est pas facile de trouver les mots pour bien faire comprendre mon problème, aussi n'hésitez pas à dire si vous ne me comprenez pas.
Merci à vous.
Cordialement,
Gizmo.
Parce qu'une adresse, c'est un pointeur
Démonstration
int toto=123;
La variable "toto" se trouve à l'adresse (par exemple) 0x0010 => &toto=0x0010
Peut-on stocker 0x0010 dans une variable "i" ? Réponse oui
int i=0x0010
Est-ce que cela nous aide pour trouver 123 ?
Réponse non. On pourrait demander "*i" mais le compilo, s'il connait "i", ne connait pas "*i". Il ne sait pas ce qu'il faut récupérer à la zone mémoire d'adresse 0x010 (faut-il récupérer 1, 2, 4, 8 cases ???)
Donc bien qu'on puisse écrire i=0x0010, on peut pas aller plus loin et on est bloqué.
Pour s'en sortir, il faut indiquer non seulement le type qui reçoit 0x0010, mais aussi le type de l'élément situé à l'adresse 0x0010. Or à cet endroit là, il y a un int.
Donc on écrit int *pt=0x0010 ce qui signifie
- pt est de type "int étoile" ou "pointeur sur int"
- *pt est de type "int"
- pt (et non "étoile pt" !!!) reçoit la valeur 0x0010
Comme 0x0010=&toto, on peut écrire plus simplement int *pt=&toto
Ensuite, si on va taper dans "*pt"; le compilo connaissant son type "int" saura récupérer les 4 octets qui s'y trouvent (0x0010; 0x0011; 0x012 et 0x0013) et le transformer en nombre => 123
Idem si on écrit dans "*pt". Le compilo ira écrire dans les cases 0x0010...0x0013
Donc 0x0010 est une adresse. C'est l'adresse de toto. Mais quel est donc son type ???
Ben on peut faire l'analogue suivante
toto est de type "int" et vaut 123
*(&toto) (ce qu'il y a à l'adresse &toto ou 0x0010) est toujours de type int et vaut 123
&toto est de type int... étoile (l'étoile qui a disparu du coté gauche est alors passée du coté droit car elle n'a pas le droit de disparaitre de la phrase)
Donc &toto est bien un pointeur sur int !!!
pa vaut &a. On peut imaginer qu'il vaut 0x0010.
Ensuite on passe pa (0x0010) en paramète à la fonction. Cette valeur 0x0010 sera stockée dans "pGauche" (et non "étoile pGauche" !!! C'est là qu'il faut faire la distinction !!!).
Donc pGauche contiendra 0x0010. En allant taper dans "*pGauche" (qui est de type "int"), ça ira taper dans "ce qu'il y a à l'adresse 0x0010" et les 3 suivantes ; soit la zone mémoire connue aussi sous le nom de "a".
No, elle attend des paramètres pointeurs sur entier de type int*.Citation:
Ce qui me "taquine" c'est que lors de sa déclaration, la fonction Echange attend des paramètres entiers int
C'est juste. En C, les arguments sont toujours passés par copie.Citation:
Mais si on le fait alors on n'a pas résolu le problème car on fait une copie
Pour rappel, le processus est le suivant :
Lors de l'appel à une fonction :
- les expressions utilisées en argument de l'appel sont évaluées (dans un ordre non spécifié). Donc, ici, pa et pb sont évalués, et les valeurs obtenues sont les adresses de a (&a) et de b (&b).
- les variables déclarées en paramètres sont créées et initialisées avec la valeur obtenue pour les arguments. Donc ici, pGauche et pDroite sont créés et initialisés respectivement avec les valeurs &a et &b.
ce qui est bien la conclusion à laquelle tu arrives.
Un pointeur est un objet (une variable) dont le type de la valeur est une adresse. Autrement dit, c'est le type d'objet adapté pour stocker une adresse. Les adresses ne deviennent pas des pointeurs, elles sont stockées dans des pointeurs.Citation:
Donc on lui fait passer en paramètres : ce que je ne comprend pas en regardant la fonction déclarée c'est : comment la fonction traite-t-elle les adresses pour qu'elle deviennent des pointeurs ?
Pour moi aussiCitation:
Pour moi pa est différent de *pGauche :
Non. pa est un pointeur (qui contient l'adresse de a, &a). pGauche est un pointeur (qui contient la valeur de pa soit aussi l'adresse de a). *pGauche est l'objet situé à l'adresse contenue dans pGauche, c'est à dire l'objet entier a.Citation:
l'un est une adresse mémoire et l'autre est un pointeur
Je me demande si ton problème ne vient pas de la confusion entre la syntaxe de déclaration d'un pointeur et celle pour l'utiliser.
Je résume le point de vue du C :
- Si A est une adresse, alors l'opérateur unaire * permet d'obtenir l'objet à cette adresse. Symboliquement :
Si A == &Objet alors *A == Objet.Donc *&Objet == Objet. La combinaison des opérateurs *& est neutre.
- Pour déclarer un pointeur p sur un objet de type T, on va écrire qu'à l'adresse contenue dans p, on a un objet de type T. L'objet à cette adresse est (*p) et on écrira T (*p) (de la même façon que pour déclarer un objet i de type int, on écrit int i). Les parenthèses sont inutiles ici et on simplifie en T *p.
L'étoile dans cette déclaration est une notation, ce n'est plus l'opérateur unaire *. Toutefois, le choix de cette notation n'est pas innocent et est donc directement lié au rôle de l'opérateur unaire *.
En associant l'étoile à p (et en l'assimilant à l'opérateur *), T *p , on a dit : A l'adresse contenue dans p est un T.
Mais on lit la déclaration T * p en associant l'étoile à T, T* p, et on dit : le type de p est T* .
Le type de l'objet est écrit de la même façon que le type de la valeur qu'il stocke (par exemple, int, float,..., pointeurs; mais exception notable : les tableaux). Le type de p est T*. Il stocke une adresse sur T. Le type "adresse de T" s'écrira aussi T*.
Bonjour,
Merci pour vos réponses elle m'ont je pense éclairé.
Je pense avoir compris mais je ne suis pas sûr : dites-moi si je me trompe :
En reprenant l'exemple de la fonction Echange : lorsqu'on déclare un pointeur, on le fait comme ceci , :
: cela veut dire grosso modo : "Réserves un pointeur pGauche en adresse mémoire"Code:int *pGauche
Tout pointeur doit être initialisé ? Alors on lui dit :pa étant initialisé à &a (l'emplacement mémoire de a)Code:pGauche=pa
Mais en fait cela se fait de manière implicite lors de l'appel de la fonction par passage de paramètres.
Voilà : en fait j'avais bien compris l'explication du tuto sous une forme simple mais n'avais pas compris du tout avec la fonction.
Encore merci car vous m'avez appris une autre chose : je ne savais pas qu'une adresse mémoire exprimée en base hexadécimale (0x00000001) différente des entiers exprimés en base 10, pouvait être un entier aussi et surtout qu'il n'y avait pas de conversion à faire.
Je voulais vous demander une dernière chose : est-ce que les références sont beaucoup utilisées dans la progra c++ ?
Cordialement,
Gizmo.
Oui, les références sont très utilisées en C++. La règle pour choisir entre pointeur et référence, c'est "si c'est possible avec une référence, alors on utilise une référence" : On réserve les pointeurs aux circonstances qui en ont besoin (comme la possibilité d'être nul).
Ben une adresse c'est l'endroit où il y a la valeur. Cet endroit est identifié par un nombre donc forcément entier !!! Tu devrais essayer d'afficher tes adresses de temps en temps....
Code:
1
2
3
4
5
6
7
8 int tab[10]; int i; for (i=0; i < 10; i++) { printf("&tab[%d]=%d\n", i, &tab[i]); printf("tab + %d=%d\n", i, tab + i); }
Ben non. Le compilo reconnait la notation utilisée et c'est lui qui fait la conversion (il traduit de toute façon tout en binaire !!!!)