Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

tableau et pointeur : question très simple

21 réponses
Avatar
Lip
bonsoir,

dans le code ci-dessous, je ne vois pas pourquoi
je suis obligé de déréférencer 2 fois &tab pour
avoir la valeur pointée (lignes 'ICI') étant donné
que tab et &tab ont les mêmes valeurs.

Mon raisonnement est le suivant, corrigez-moi si j'ai faux :
- tab est un tableau de 3 entiers, le type de tab est donc int[3],
- &tab est l'adresse du tableau de 3 entiers, c'est donc un pointeur
constant(?) vers un tableau de 3 entiers, le type de &tab est donc
int (*p)[3].
Mais &tab est équivalent à tab, à &tab[0] et à tab+0. Après je ne sais
plus...

Merci de bien vouloir me donner des explications simples et "décortiquées".
Lip


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

int main(void) {

int tab[3] = {1,2,3};

/* les formes de l'adresse du tableau */
printf("Valeur de tab : %p\n", tab);
printf("Valeur de &tab[0] : %p\n", &tab[0]);
printf("Valeur de tab + 0 : %p\n", tab + 0);
printf("Valeur de &tab : %p\n", &tab);

printf("Valeur pointee : %d\n", **&tab); /* ICI */

printf("\n");

/* les formes des valeurs du dernier élément du tableau */
printf("Forme 1 : %d\n", tab[2]);
printf("Forme 2 : %d\n", *tab + 2);
printf("Forme 3 : %d\n", **&tab + 2); /* ICI */

printf("\n");

printf("Adresse du dernier element de tab : %p\n", &tab[2]);


return EXIT_SUCCESS;
}

10 réponses

1 2 3
Avatar
drkm
Lip <pasdespam> writes:

Mais &tab


Adresse de la variable tab. Donc un pointeur vers un tableau
d'entiers. Et non un pointeur vers un entier.

est équivalent à tab,


Variable tab. Il existe une conversion implicite vers &tab[0].

à &tab[0]


Adresse du premier élément de tab.

et à tab+0.


tab est converti en l'adresse de son premier élément, à laquelle on
ajoute 0.

Je dirais donc que les trois dernières expressions sont
équivalentes : l'adresse du premier élément de tab. La première est
l'adresse du tableau lui-même.

--drkm

Avatar
Marc Boyer
In article <417c3b11$0$855$, Lip wrote:
dans le code ci-dessous, je ne vois pas pourquoi
je suis obligé de déréférencer 2 fois &tab pour
avoir la valeur pointée (lignes 'ICI') étant donné
que tab et &tab ont les mêmes valeurs.


Même valeur ne signifie pas équivalents.

sizeof(tab) != sizeof( &tab[0] )

Mon raisonnement est le suivant, corrigez-moi si j'ai faux :
- tab est un tableau de 3 entiers, le type de tab est donc int[3],
- &tab est l'adresse du tableau de 3 entiers, c'est donc un pointeur
constant(?) vers un tableau de 3 entiers, le type de &tab est donc
int (*p)[3].
Mais &tab est équivalent à tab, à &tab[0] et à tab+0. Après je ne sais
plus...


Non, tab est automatiquement convertit en &tab[0], sauf dans
le cas où on lui applique les opérateurs & et sizeof.

Pour l'exemple:
(char*) ((&tab)+1) == ( (char*) &tab[0] ) + 3 * sizeof(tab[0])
== ( (char*) &tab[0] ) + sizeof(tab)
== (char*) (&tab[0] + 3)

Merci de bien vouloir me donner des explications simples et "décortiquées".
#include <stdio.h>
#include <stdlib.h>

int main(void) {

int tab[3] = {1,2,3};

/* les formes de l'adresse du tableau */
printf("Valeur de tab : %pn", tab);
printf("Valeur de &tab[0] : %pn", &tab[0]);
printf("Valeur de tab + 0 : %pn", tab + 0);
printf("Valeur de &tab : %pn", &tab);


Ici, on a bien (void*) tab == (void*) &tab, donc le %p
s'en sort.

printf("Valeur pointee : %dn", **&tab); /* ICI */


Normal: &tab est l'adresse d'un tableau, *&tab est le
tableau, **&tab == *tab == tab[0]

Marc Boyer
--
Je ne respecte plus le code de la route à vélo depuis une double fracture
due au fait que j'étais le seul à le respecter.

Avatar
Horst Kraemer
Lip <pasdespam> wrote:

bonsoir,

dans le code ci-dessous, je ne vois pas pourquoi
je suis obligé de déréférencer 2 fois &tab pour
avoir la valeur pointée (lignes 'ICI') étant donné
que tab et &tab ont les mêmes valeurs.

Mon raisonnement est le suivant, corrigez-moi si j'ai faux :
- tab est un tableau de 3 entiers, le type de tab est donc int[3],


Oui.

- &tab est l'adresse du tableau de 3 entiers, c'est donc un pointeur
constant(?) vers un tableau de 3 entiers, le type de &tab est donc
int (*p)[3].


Oui.


Mais &tab est équivalent à tab, à &tab[0] et à tab+0. Après je ne sais
plus...


Par "équivalent" tu veut dire que les valeurs de ces expressions
*converties en hex* sont identiques - mais cela ne veut pas dire que
les expressions sont identiques. Leur type est différent bien qu'ils
pointent vers le même endroit dans la mémoire. La conversion en hex
par printf retire l'information 'type' et ne te donne que
l'information 'adresse'.

Par définition du langage C un tableau (ici 'tab') est converti
automatiquement en un pointeur (constant) vers le premier élément du
tableau - à l'exception des expressions '&tab' ou 'sizeof tab' où
'tab' est toujours *le tableau*.

Donc

printf("%p", (void*)tab);

donne l'adresse du premier élément de tab

printf("%p", (void*)&tab);

donne l'adresse du tableau 'tab'. C'est adresses sont identiques parce
que par définition l'adresse d'un objet est l'adresse du premier byte
de l'objet mais les types des expressions '&tab' et 'tab' sont
différents.

Cependant les expressions '*&tab' est 'tab' sont égales par 'valeur'
et par 'type'. &tab a le type pointeur vers int[3], alors *&tab a le
type de l'objet vers lequel &tab pointe, donc *&tab a le type int[3].

Pour la valeur du premier élement de tab on peut donc écrire


tab[2] : veut dire par définition *(tab+2)
*(tab+2)

*(*&tab+2) : *&tab a le mème type et la mème valeur que tab



Merci de bien vouloir me donner des explications simples et "décortiquées".
Lip


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

int main(void) {

int tab[3] = {1,2,3};

/* les formes de l'adresse du tableau */
printf("Valeur de tab : %pn", tab);
printf("Valeur de &tab[0] : %pn", &tab[0]);
printf("Valeur de tab + 0 : %pn", tab + 0);
printf("Valeur de &tab : %pn", &tab);

printf("Valeur pointee : %dn", **&tab); /* ICI */

printf("n");

/* les formes des valeurs du dernier élément du tableau */
printf("Forme 1 : %dn", tab[2]);
printf("Forme 2 : %dn", *tab + 2);


printf("Forme 2 : %dn", *(tab+2));

*tab+2 est la valeur de l'élément *tab (==tab[0]) plus 2. l'opérateur
* est prioritaire par rapport à l'opérateur +.



printf("Forme 3 : %dn", **&tab + 2); /* ICI */


printf("Forme 3 : %dn", *(*&tab + 2)); /* ICI */

**&tab + 2 est la mème chose que *tab+2 == tab[0]+2


printf("n");

printf("Adresse du dernier element de tab : %pn", &tab[2]);


return EXIT_SUCCESS;
}



(Selon la norme du langage C il faut convertir un pointeur en (void*)
pour la sortie par %p)


--
Horst

--
Lâche pas la patate!

Avatar
Anthony Fleury
Lip <pasdespam> wrote:

dans le code ci-dessous, je ne vois pas pourquoi
je suis obligé de déréférencer 2 fois &tab pour
avoir la valeur pointée (lignes 'ICI') étant donné
que tab et &tab ont les mêmes valeurs.


La même «valeur» oui peut être, le même «type» non...

Mon raisonnement est le suivant, corrigez-moi si j'ai faux :
- tab est un tableau de 3 entiers, le type de tab est donc int[3],


Corrrecccte (© Laurence Boccolini)

- &tab est l'adresse du tableau de 3 entiers, c'est donc un pointeur
constant(?) vers un tableau de 3 entiers, le type de &tab est donc
int (*p)[3].


Correct. C'est pour initialiser int (*p)[3] sans que gcc me dise que j'ai
pas les bons types que je vois l'utilité de ce &tab, sinon je ne vois pas
où on pourrait l'utiliser autre part...

Mais &tab est équivalent à tab, à &tab[0] et à tab+0. Après je ne sais
plus...


Bah on ne peut pas vraiment dire équivalent. Ils ont la même valeur pour que
la construction des tableaux en C soit cohérente.
Par construction, la variable de type tableau contient un pointeur sur son
premier élement. Par construction &tab aura aussi la même valeur. Mais ce
ne sont que des valeurs, les types manipulés sont bien différent, et il ne
faudra pas dire qu'un tableau et un pointeur sont la même chose comme
disent beaucoup de personnes !

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

int main(void) {

int tab[3] = {1,2,3};

/* les formes de l'adresse du tableau */
printf("Valeur de tab : %pn", tab);
printf("Valeur de &tab[0] : %pn", &tab[0]);


par définition du tableau, la variable contient l'adresse sur son premier
élement, c'est normal donc que ce soit la même chose qu'au dessus.

printf("Valeur de tab + 0 : %pn", tab + 0);


Ici c'est de l'arithmétique sur les pointeurs, comme 0 ne déplace rien, on
réaffiche tab, logique que ce soit la même chose.

printf("Valeur de &tab : %pn", &tab);


Et ici par construction ils ont dit que &tab aurait la même «valeur» que tab
et que &tab[0]. En gros le & est aussi utile ici que dans le cas de
pointeurs de fonctions.

printf("Valeur pointee : %dn", **&tab); /* ICI */


Je ne vois pas ce qu'il y a de choquant à mettre deux *, si on reste sur le
système de _type_ et non de valeurs.

&tab a quel type ? tab a pour type int(*)[3]. Pour &tab on en rajoute une
indirection de plus, int (**)[3]. si on veut compenser autant
d'indirection, faut bien mettre plus d'une * pour l'afficher.

En fait, dans le même genre, mais avec une indirection de moins,

printf("Valeur pointée : %d", *tab) affichera tab[0]. En effet, si on
déréférence tab on obtient la valeur de l'objet pointé, ici un int. Donc si
tu veux faire le malin à rajouté un & devant ton tab, faudra rajouter une
autre * :-)

/* les formes des valeurs du dernier élément du tableau */
printf("Forme 1 : %dn", tab[2]);
printf("Forme 2 : %dn", *tab + 2);


Attention ca ca ne fonctionne PAS. (sauf dans ce cas avec les valeurs 1, 2
et 3. Pour des tests prendre 42, 424, 4242) !
Par priorité sur les opérateurs, ceci déréférence *tab donc obtient tab[0]
et lui ajoute 2. Ca n'ajoute pas 2 à tab avant de le déréférencé !

printf("Forme 2 : %dn", *(tab+2));

Le fait par contre !

printf("Forme 3 : %dn", **&tab + 2); /* ICI */


Même punition ici :
printf("Forme 3 : %dn", **&(tab+2));

Ce qui fait d'ailleurs bien apparaitre l'inutilité du couple *& qui se
compense :-)

Ah le C ca peut être «chiant» quand on essaye de bien le comprendre !

Anthony - qui espère ne pas avoir dit trop de bêtises sur ce sujet épineux
--
Alan Turing thought about criteria to settle the question of whether
machines can think, a question of which we now know that it is about as
relevant as the question of whether submarines can swim.
-- Dijkstra

Avatar
Jean-Marc Bourguet
Lip <pasdespam> writes:

bonsoir,

dans le code ci-dessous, je ne vois pas pourquoi
je suis obligé de déréférencer 2 fois &tab pour
avoir la valeur pointée (lignes 'ICI') étant donné
que tab et &tab ont les mêmes valeurs.



Mon raisonnement est le suivant, corrigez-moi si j'ai faux :
- tab est un tableau de 3 entiers, le type de tab est donc int[3],


OK.

- &tab est l'adresse du tableau de 3 entiers, c'est donc un pointeur
constant(?) vers un tableau de 3 entiers,


Oui. Au sujet du constant, &tab est une valeur (comme 12 ou 15) et
pas une variable.

le type de &tab est donc
int (*p)[3].
Ok.

Mais &tab est équivalent à tab,


Non. Il est des contextes ou tab est convertit en &tab[0], mais c'est
deux choses differentes.

à &tab[0] et à tab+0.


Les valeurs sont les memes, les types pas.
Après je ne sais
plus...

Merci de bien vouloir me donner des explications simples et "décortiquées".
Lip


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

int main(void) {

int tab[3] = {1,2,3};

/* les formes de l'adresse du tableau */
printf("Valeur de tab : %pn", tab);


Le type de tab est int[3] mais il est employe dans un contexte ou il
est converti implicitement en &tab[0] (donc de type int*).

printf("Valeur de &tab[0] : %pn", &tab[0]);
tab est de type int[3]

tab[0] est de type int
&tab[0] est de type int*

C'est la meme valeur que ci-dessus.

printf("Valeur de tab + 0 : %pn", tab + 0);


tab est de type int[3], employe dans un contexte ou il est converti
implicitement en &tab[0] (type int*)
la valeur ci-dessus +0, ne change rien.

printf("Valeur de &tab : %pn", &tab);
tab est de type int[3],

&tab est de type int (*)[3]
Meme adresse mais type different que les 3 expressions ci-dessus.

printf("Valeur pointee : %dn", **&tab); /* ICI */
On a &tab de type int(*)[3]

*&tab de type int[3]
employe dans un contexte ou il est converti implicitement en
un pointeur de type int* &(*&tab)[0]
Donc **&tab est de type int.

printf("n");

/* les formes des valeurs du dernier élément du tableau */
printf("Forme 1 : %dn", tab[2]);
Ok.

printf("Forme 2 : %dn", *tab + 2);
Non, *(tab+2)

*tab+2 est a interpreter comme (*tab) + 2 (tu ne vois pas la
difference a cause du choix malheureux des valeurs dans ton exemple).
A nouveau il y a conversion implicite de tableau vers pointeur vers le
premier element.

printf("Forme 3 : %dn", **&tab + 2); /* ICI */
Non. *(*&tab +2). Meme probleme d'associativite. Meme jeux de

conversion que ci-dessus.

printf("n");

printf("Adresse du dernier element de tab : %pn", &tab[2]);


OK.

A+

--
Jean-Marc
FAQ de fclc: http://www.isty-info.uvsq.fr/~rumeau/fclc
Site de usenet-fr: http://www.usenet-fr.news.eu.org

Avatar
Charlie Gordon
La question a trouvé réponse, mais que dire de ces quelques lignes :

#include <stdio.h>

int main(void) {
printf("main = %pn", (void*)(main));
printf("&main = %pn", (void*)(&main));
printf("*main = %pn", (void*)(*main));
printf("&*main = %pn", (void*)(&*main));
printf("**main = %pn", (void*)(**main));
printf("***main = %pn", (void*)(***main));
printf("****main = %pn", (void*)(****main));
return 0;
}

Chqrlie.
Avatar
Jean-Marc Bourguet
"Charlie Gordon" writes:

La question a trouvé réponse, mais que dire de ces quelques lignes :

#include <stdio.h>

int main(void) {
printf("main = %pn", (void*)(main));
printf("&main = %pn", (void*)(&main));
printf("*main = %pn", (void*)(*main));
printf("&*main = %pn", (void*)(&*main));
printf("**main = %pn", (void*)(**main));
printf("***main = %pn", (void*)(***main));
printf("****main = %pn", (void*)(****main));
return 0;
}


Les pointeurs vers fonctions sont amusants n'est-ce pas?

(Des conversions implicites dans les deux sens)

A+

--
Jean-Marc
FAQ de fclc: http://www.isty-info.uvsq.fr/~rumeau/fclc
Site de usenet-fr: http://www.usenet-fr.news.eu.org

Avatar
Pierre Maurette
Jean-Marc Bourguet a écrit:

"Charlie Gordon" writes:

La question a trouvé réponse, mais que dire de ces quelques lignes :

#include <stdio.h>

int main(void) {
printf("main = %pn", (void*)(main));
printf("&main = %pn", (void*)(&main));
printf("*main = %pn", (void*)(*main));
printf("&*main = %pn", (void*)(&*main));
printf("**main = %pn", (void*)(**main));
printf("***main = %pn", (void*)(***main));
printf("****main = %pn", (void*)(****main));
return 0;
}


Les pointeurs vers fonctions sont amusants n'est-ce pas?

(Des conversions implicites dans les deux sens)
Justement, la question que je me pose: ne serait-il pas préférable de

répondre à l'OP que
- C est un langage où tab et &tab d'un coté, foo et &foo d'un autre
peuvent être indifféremment utilisés dans quantité d'expression.
- Que ce n'est absolument pas le cas général, par exemple entier et
&entier.
- Et que partant de là, il est certain qu'il y aura des dommages
collatéraux et que toutes les expressions dérivées de tab ou de foo
par les opérateurs de déréférencement et d'adresse n'auront pas
obligatoirement une explication cohérente et limpide ?
--
Pierre


Avatar
Jean-Marc Bourguet
Pierre Maurette writes:

- C est un langage où tab et &tab d'un coté, [...] peuvent être
indifféremment utilisés dans quantité d'expression.


Tu n'en donnes une qui ne resume pas a conversion a un (void*)?

Pour tab et &tab[0] je suis d'accord. Mais c'est ce que j'ai ecrit
avant que Charlie ne vienne compliquer les choses avec les fonctions
(sujet qu'on a deja aborde par ailleurs -- ou etait-ce sur fclc++?)

A+

--
Jean-Marc
FAQ de fclc: http://www.isty-info.uvsq.fr/~rumeau/fclc
Site de usenet-fr: http://www.usenet-fr.news.eu.org

Avatar
Lip
Merci bien à tous pour toutes les infos.
Je vais digérer tout cela...
Lip :-)
1 2 3