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

Definition d'un "nom de tableau"

81 réponses
Avatar
candide
Ce que je fais là est un peu cavalier mais j'ouvre un fil pour répondre
au message de François, l'autre fil devenant vraiment trop anarchique.

Francois a écrit :

>
> « Le nom t d'un tableau est une *constante* de type pointeur dont la
> valeur est fixée une bonne fois pour toute au début de l'exécution du
> programme et n'est pas stockée en mémoire. »
>
> est-ce correct ? J'ai l'impression que oui bien sûr, mais je me trompe
> peut-être (sûrement).


Je ne comprends pas ton obstination à vouloir trouver une définition
parfaite de "nom d'un tableau", ce qui compte aussi c'est de savoir les
utiliser ces tableaux mais peut-être que tu ne pourras faire cela
qu'après avoir défini, ce en quoi on reconnait le matheux ;) .

Et maintenant tu compliques les choses en parlant non plus de tableau
mais de nom d'un tableau.


À mon avis, ta définition est totalement incorrecte même si je
vois bien l'idée.

"Un nom de variable est une constante" ? Bof, dans la
norme (que je la connais très mal, OK), les constantes et les noms de
variables (les identificateurs) ne sont pas dans la même catégorie de
"token" donc on ne peut pas dire formellement qu'un identificateur est
une constante.

"Un tableau est de type pointeur", c'est une horreur, un tableau est de
type tableau, c'est tout même si une expression de type tableau est,
dans certaines circonstances, convertie en pointeur.

La valeur de t est fixée une bonne fois pour toutes au début ? oui, me
semble-t-il et encore, je sens que certains vont chipoter.


"La valeur n'est pas stockée en mémoire" : à mon avis, non, un tableau
est un objet, comment on y accèderait sinon ?

Comme dit la faq de clc à propos de l'expression "un tableau est un
pointeur constant" (question 6.9) : un nom de tableau est constant dans
la mesure où on ne peut l'assigner mais une fois de plus, un tableau
n'est pas un pointeur.

10 réponses

Avatar
Marc Boyer
On 2008-03-17, Antoine Leca wrote:
En news:frh9si$5gu$, Marc Boyer va escriure:
On 2008-03-15, candide wrote:
Non. Un compilateur normal va générer l'equivalent de


double foo(void){
return 8.0;
}

éventuellement précédé du mot-clé inline.

Voire même, va supprimer totalement le code généré, s'il s'aperçoit qu'il
n'y a aucun appel à la fonction foo dans tout le programme.


Toute la difficulté de donner un exemple en fonction du niveau
de l'interlocuteur.
Alors, spécialement pour Antoine:

void other(double *p);

double foo(void){
double p[10];
double q[10];
double r[10];

other(p+6);
other(q+3);
other(r+3);
return p[6]+q[3]+r[4];
}

En supposant que other() est défini dans une autre unité de
compilation. Comme le compilo ne sait pas ce que va faire other,
il ne peut pas optimiser grand chose, et même pas supprimer
des bouts de p, q et r, puisque other pourrait aller taper
dedans.

Par contre, dans le code exécutable de foo, on peut complètement
(enfin presque) oublier les tailles et adresse des début de p, q et r.


Là encore, il est possible que tout ceci soit remplacé par

void bar(double d) {}

(voire supprimé complètement). Même si les compilateurs courants ne vont pas
jusque là.


Je te le refais avec pleins d'effets de bord pour
que le compilo puisse pas trop deviner ce qui se passe.

double bar(double d){
double x;
double t[5];
scanf("%d",&x);
for(int i=0;i<5;++i){
scanf("%d", &t[i] );
d+= t[i]+i>0?t[i-1]:0;
}
return d;
}

vs

double bar(double d){
double t[6];
scanf("%d",t);
for(int i=1;i<6;++i){
scanf("%d", &t[i] );
t[0]+= t[i]+i>1?t[i-1]:0;
}
return t[0];
}

Marc
--
Si tu peux supporter d'entendre tes paroles
Travesties par des gueux pour exciter des sots
IF -- Rudyard Kipling (Trad. André Maurois)


Avatar
Antoine Leca
En news:47dd34a0$0$28548$, Francois va escriure:
"int tab2[10][12];",
L'occurrence, il faudrait dire comment exactement ? "tab2" est une
variable de type tableau de *10x12 int* ?


Non : les tableaux multi-dimensionnels n'existent pas en C. tab2 est le nom
d'une variable de type tableau de 12 tableaux de 10 entiers.

2) Vous dites : « Ce pointeur est le premier argument de [], le
deuxième argument étant 2 ».


[] est un opérateur binaire (comme *, sauf qu'il a une forme moins
classique), donc il a besoin de deux opérandes (argument dans la prose de
JM) pour opérer. Le premier est un pointeur, le second un entier.
Le résultat est du type vers lequel pointe le pointeur (ici int).

Mais ce qui me gêne, c'est qu'au final, tab[2] est une
expression de type int, et donc il doit bien avoir une opération
d'indirection (avec *) qui apparaît. Or dans ce que vous avez écrit,
j'ai l'impression que vous ne parlez pas de l'opérateur *.


Tout se passe comme si au lieu de E1[E2], on écrivait (*((E1)+(E2))). C'est
juste un raccourci bien utile (et qui démontre qu'on est pas en Lisp).


Ces trois choses à ne pas confondre, si j'ai bien compris, c'est
lorsqu'on parle de type (int par exemple), il faut distinguer si l'on
parle :

1) du type d'une manière générale.
2) du type d'un objet (zone de la mémoire qui stocke des valeurs)
3) du type d'une valeur (qui peut parfois être le contenu d'un objet
et dans ce cas, le type de l'objet et le type de sa valeur sont
identiques)


Je verrais plutôt
2) du type d'un nom (identifiant)
3) du type d'une expression

Le troisième est une clause de style ; par contre, au niveau du deuxième il
y a plus de différence : un nom désigne un objet, mais les objets ne sont
pas tous identifiables par des noms.


Antoine

Avatar
Antoine Leca
En news:, Marc Boyer va escriure:
Alors, spécialement pour Antoine:

void other(double *p);

double foo(void){
double p[10], q[10], r[10];

other(p+6); other(q+3); other(r+3);
return p[6]+q[3]+r[4];
}

En supposant que other() est défini dans une autre unité de
compilation. Comme le compilo ne sait pas ce que va faire other,


En fait le C n'oblige pas à ce que la compilation soit complètement
indépendante : donc il est _possible_ que le compilateur puisse avoir des
informations sur other() même s'il est compilé à part : par exemple, il
pourrait faire la supposition que other() modifie, ou pas, le tableau
sous-jaçant à son argument ; et générer deux versions de foo(), l'une qui
appelerait other$1 (où les autres adresses ne seraient pas modifiées, et
dont le protype serait réécrit double other$1(void)) ; et une autre moins
agressive, où p est supposé modifié y compris ailleurs.

Et de générer

inline double foo$1(void) {
return other$1() .+ other$1() .+ other$1(); }

(où .+ représente l'opérateur non commutatif, qui évalue d'abord à gauche
puis à droite) ; et

double foo$2(void){
double p[10], q[10], r[10];

other$2(p+6, p, <double[10]>);
other$2(q+3, q, <double[10]>);
other$2(r+3, r, <double[10]>);
return p[6]+q[3]+r[4];
}

(j'ai explicité les arguments pour pouvoir réaliser la vérification complète
de la conformité des opérations dans other$2.) Il ne restera plus à
l'éditeur de liens qu'à choisir entre foo$1 et foo$2 en fonction de la
version réellement fournie pour other(), et peut-être du niveau de debug
désiré.


il ne peut pas optimiser grand chose, et même pas supprimer
des bouts de p, q et r, puisque other pourrait aller taper
dedans.

Par contre, dans le code exécutable de foo, on peut complètement
(enfin presque) oublier les tailles et adresse des début de p, q et r.


Euh, dans le cas other$2 où te sembles te placer, il faut quand même les
allouer et les supprimer...


double x;
scanf("%d",&x);


u.b.

vs

double t[6];
scanf("%d",t);


u.b. aussi, donc il n'y a pas de différence observable. Mais était-ce que tu
voulais montrer ? ;-)



OK, je sais que c'est beaucoup plus facile de pinailler que de fournir des
solutions, donc je vais arrêter de faire l'idiot. D'ailleurs je pense que ce
que tu voulais démontrer initialement (que le compilateur fait une
différence très grande entre le tableau d'une part, et son adresse d'autre
part) devrait être maintenant assez clair.


Antoine

Avatar
Francois
"int tab2[10][12];",
L'occurrence, il faudrait dire comment exactement ? "tab2" est une
variable de type tableau de *10x12 int* ?


Non : les tableaux multi-dimensionnels n'existent pas en C. tab2 est le nom
d'une variable de type tableau de 12 tableaux de 10 entiers.


Alors, est-ce que par hasard ce ne serait pas plutôt « une variable de
type tableau de 10 tableaux de 12 entiers » ?


2) Vous dites : « Ce pointeur est le premier argument de [], le
deuxième argument étant 2 ».


[] est un opérateur binaire (comme *, sauf qu'il a une forme moins
classique), donc il a besoin de deux opérandes (argument dans la prose de
JM) pour opérer. Le premier est un pointeur, le second un entier.
Le résultat est du type vers lequel pointe le pointeur (ici int).


D'accord ! je crois que vous venez de porter l'estocade. :-)

[] est un opérateur binaire, mais * est plutôt un opérateur unaire, non ?

Petite question : en interne (1) c'est les "[]" qui sont convertis avec
des "*" ou (2) c'est l'inverse. J'ai l'impression que c'est (1). J'ai
cru comprendre que "[]" est une commodité pour la lisibilité, qui en
réalité est automatiquement converti avec des "*" via :

E1[E2] qui devient *(E1 + E2)

comme vous le signalez plus loin. Est-ce correct ?


Mais ce qui me gêne, c'est qu'au final, tab[2] est une
expression de type int, et donc il doit bien avoir une opération
d'indirection (avec *) qui apparaît. Or dans ce que vous avez écrit,
j'ai l'impression que vous ne parlez pas de l'opérateur *.


Tout se passe comme si au lieu de E1[E2], on écrivait (*((E1)+(E2))). C'est
juste un raccourci bien utile (et qui démontre qu'on est pas en Lisp).


:-))


Ces trois choses à ne pas confondre, si j'ai bien compris, c'est
lorsqu'on parle de type (int par exemple), il faut distinguer si l'on
parle :

1) du type d'une manière générale.
2) du type d'un objet (zone de la mémoire qui stocke des valeurs)
3) du type d'une valeur (qui peut parfois être le contenu d'un objet
et dans ce cas, le type de l'objet et le type de sa valeur sont
identiques)


Je verrais plutôt
2) du type d'un nom (identifiant)
3) du type d'une expression

Le troisième est une clause de style ; par contre, au niveau du deuxième il
y a plus de différence : un nom désigne un objet, mais les objets ne sont
pas tous identifiables par des noms.


Ah. C'est étrange cette dichotomie, car pour moi un nom est simple cas
particulier d'expression. (?)


Merci pour la réponse en tout cas, vous m'avez expliqué un point
crucial. Je trouve que c'est dur les tableaux, plus dur que les
pointeurs je trouve. C'est curieux que dans les livres il parle souvent
des tableaux avant les pointeurs. Je trouve que ça devrait être l'inverse.


François


Avatar
Marc Boyer
On 2008-03-17, Antoine Leca wrote:
En news:, Marc Boyer va escriure:
Alors, spécialement pour Antoine:

void other(double *p);

double foo(void){
double p[10], q[10], r[10];

other(p+6); other(q+3); other(r+3);
return p[6]+q[3]+r[4];
}

En supposant que other() est défini dans une autre unité de
compilation. Comme le compilo ne sait pas ce que va faire other,


En fait le C n'oblige pas à ce que la compilation soit complètement
indépendante : donc il est _possible_ que le compilateur puisse avoir des
informations sur other() même s'il est compilé à part : par exemple, il
pourrait faire la supposition que other() modifie, ou pas, le tableau
sous-jaçant à son argument ; et générer deux versions de foo(), l'une qui
appelerait other$1 (où les autres adresses ne seraient pas modifiées, et
dont le protype serait réécrit double other$1(void)) ; et une autre moins
agressive, où p est supposé modifié y compris ailleurs.

Et de générer

inline double foo$1(void) {
return other$1() .+ other$1() .+ other$1(); }

(où .+ représente l'opérateur non commutatif, qui évalue d'abord à gauche
puis à droite) ; et

double foo$2(void){
double p[10], q[10], r[10];

other$2(p+6, p, <double[10]>);
other$2(q+3, q, <double[10]>);
other$2(r+3, r, <double[10]>);
return p[6]+q[3]+r[4];
}

(j'ai explicité les arguments pour pouvoir réaliser la vérification complète
de la conformité des opérations dans other$2.) Il ne restera plus à
l'éditeur de liens qu'à choisir entre foo$1 et foo$2 en fonction de la
version réellement fournie pour other(), et peut-être du niveau de debug
désiré.


<air-mechant>
Tu veux que j'ajoute du volatile pour voir ?
</air-mechant>

il ne peut pas optimiser grand chose, et même pas supprimer
des bouts de p, q et r, puisque other pourrait aller taper
dedans.

Par contre, dans le code exécutable de foo, on peut complètement
(enfin presque) oublier les tailles et adresse des début de p, q et r.


Euh, dans le cas other$2 où te sembles te placer, il faut quand même les
allouer et les supprimer...


Oui, mais il peut allouer le global p+q+r de taille 30,
et donc perdre les détails. Il y aura donc les adresses
de début de deux tableaux qui ne seront plus nulle part,
le but étant de faire un contre-exemple à l'affirmation
selaon laquelle l'adresse du début d'un tableau devait bien
être stoquée quelque part.

double x;
scanf("%d",&x);


u.b.


Et oui... Poster du code sans compiler, c'est toujours risqué.

OK, je sais que c'est beaucoup plus facile de pinailler que de fournir des
solutions, donc je vais arrêter de faire l'idiot.


Je ne t'en veux pas. Disons que j'ai l'impression de te connaitre
assez pour deviner tes intentions, et je ne les sens pas vraiment
méchantes...

D'ailleurs je pense que ce
que tu voulais démontrer initialement (que le compilateur fait une
différence très grande entre le tableau d'une part, et son adresse d'autre
part) devrait être maintenant assez clair.


N'ais-je pas convaincu que les gens qui savaient déjà ?


Marc Boyer
--
Si tu peux supporter d'entendre tes paroles
Travesties par des gueux pour exciter des sots
IF -- Rudyard Kipling (Trad. André Maurois)


Avatar
Francois
"int tab2[10][12];",
L'occurrence, il faudrait dire comment exactement ? "tab2" est une
variable de type tableau de *10x12 int* ?


Non : les tableaux multi-dimensionnels n'existent pas en C. tab2 est
le nom
d'une variable de type tableau de 12 tableaux de 10 entiers.


Alors, est-ce que par hasard ce ne serait pas plutôt « une variable de
type tableau de 10 tableaux de 12 entiers » ?


Pardon d'insister, mais pourrais-je avoir un confirmation/infirmation de
ceci ?

Je récapitule.

Lorsqu'on déclare un " int tab2[10][12]; " alors tab2 est bien une
variable de type tableau de 10 éléments de type tableau de 12 entiers,
et non l'inverse (à savoir variable de type tableau de 12 éléments de
type tableau de 10 entiers). Est-ce correct ?


Au passage, j'ai trouvé le "Introduction au langage C" de Bernard
Cassagne (document pdf consultable sur internet sans problème) pas mal
du tout s'initier. Il m'a bien aidé, en plus des précieuses explications
que j'ai glané sur le fclc, à comprendre correctement (j'espère) les
tableaux.

Merci d'avance.


François



Avatar
candide

N'ais-je pas convaincu que les gens qui savaient déjà ?

Rassure-moi, on peut quand même faire honnêtement du C sans savoir

comment marche un compilateur, y compris l'ignorer dans les grandes
lignes ?

Avatar
candide


Non : les tableaux multi-dimensionnels n'existent pas en C. tab2 est
le nom
d'une variable de type tableau de 12 tableaux de 10 entiers.


Alors, est-ce que par hasard ce ne serait pas plutôt « une variable de
type tableau de 10 tableaux de 12 entiers » ?


Pardon d'insister, mais pourrais-je avoir un confirmation/infirmation de
ceci ?



Tes questions sont intéressantes pour juger de la façon dont les
ouvrages de C sont écrits. Alors, je regarde dans le saint des saints,
et il n'y a pas de réponse à ta question (ou en tout cas là où ça
devrait être, au §5.12). Je regarde dans Harbison & Steele, et là
encore, pas de réponse directe à ta question, d'ailleurs, on a une
formulation typique de ce bouquin :


-------------------------
§5.4.2
Multidimensional arrays are declared "arrays of arrays", such as in the
declaration

int matrix[12][10];

wich declares to be a 12-by-10 element array of int.
-------------------------

Je trouve quand même ça assez fort : ils commencent par te dire que les
tableaux multidimensionnels en C, c'est pas ce à quoi on s'attend, c'est
des tableaux de tableaux et au lieu de t'expliquer comment ça marche (et
en particulier si matrix est un tableau de 12 ou 10 tableaux), ils
utilisent la formulation qui ne convient pas (et c'est la 5ème édition
du bouquin !).


Une fois de plus, tu trouveras des explications BEAUCOUP plus claire
dans la norme. Extrait de la norme C99 :


--------------------8<----------------------------------------
6.5.2.1 Array subscripting
4 EXAMPLE Consider the array object defined by the declaration
int x[3][5];
Here x is a 3 ´ 5 array of ints; more precisely, x is an array of three
element objects, each of which is an array of five ints.
-------------------->8----------------------------------------


Oui, la norme est capable de donner des définitions et des exemples
clairs. Et ce n'est pas rare.



Avatar
YBM
-------------------------
§5.4.2
Multidimensional arrays are declared "arrays of arrays", such as in the
declaration

int matrix[12][10];

wich declares to be a 12-by-10 element array of int.
-------------------------


concorde parfaitement avec :

--------------------8<----------------------------------------
6.5.2.1 Array subscripting
4 EXAMPLE Consider the array object defined by the declaration
int x[3][5];
Here x is a 3 ´ 5 array of ints; more precisely, x is an array of three
element objects, each of which is an array of five ints.
-------------------->8----------------------------------------


Toujours en train de vous crisper comme des dingues ? Vous n'avez
pas lu la faq dont je donnais le lien tantôt alors...

Avatar
Francois
--------------------8<----------------------------------------
6.5.2.1 Array subscripting
4 EXAMPLE Consider the array object defined by the declaration
int x[3][5];
Here x is a 3 ´ 5 array of ints; more precisely, x is an array of three
element objects, each of which is an array of five ints.
-------------------->8----------------------------------------


Oui, la norme est capable de donner des définitions et des exemples
clairs. Et ce n'est pas rare.


Merci beaucoup ! C'est donc bien ce que je pensais. Cela peut paraître
anodin comme question, mais en fait je trouve que ça ne l'est pas. Par
exemple, après

int tab[3][4] ;

les occurrences futures de tab seront en général (d'après la *règle* qui
admet 3 exceptions) converties en la valeur d'un pointeur sur un tableau
de 4 entiers et tab+1 aussi (il donnera l'adresse du deuxième élément du
tableau tab [qui est un tableau à 4 quatre éléments de type entier]).
Donc, comprendre le type permet de bien comprendre le "+1" dans "tab+1".

Effectivement, on comprend bien alors qu'un tableau dit
"multidimensionnel" n'est en réalité qu'un tableau à une seule dimension
dont le nombre d'éléments est donné par le premier "chiffre" entre
crochet et dont chaque élément sera un tableau (dont les éléments
pourront être éventuellement des tableaux eux aussi, ainsi de suite ...).

Je crois que je commence à comprendre alors les explications de
Jean-Marc Bourget et sa vision systématique des tableaux. Je tente une
explication. Surtout, rectifiez moi si je dis des bêtises !


Supposons que l'on ait int tab[3][4] ;

L'objet tab est un tableau à 3 éléments que je vais énumérer
symboliquement par tab0, tab1 et tab2 qui sont chacun des tableaux de
quatre entiers.

Supposons qu'on ait à un moment donné tab[1][3] ;

Effectivement, tab est alors converti en une valeur de type pointeur sur
son premier élément. Donc tab est converti en une valeur égale à
l'adresse de tab0 (l'adresse du premier byte de tab0) vue avec le type
de tab0, c'est-à-dire le type tableau de quatre entiers.

tab[1][3] est alors pour moi converti en *(*(tab+1)+3)

*(tab+1) est le deuxième élément de tab, c'est-à-dire tab1. Donc en fait
on a :

tab[1][3] converti en *(*(tab+1)+3) équivalent à *(tab1+3)

Or, tab1 est un tableau de 4 entiers. Dans ce contexte, il est lui aussi
converti à son tour en une valeur de type pointeur vers son premier
élément. Une fois tab1 converti, *(tab1+3) donne enfin le quatrième
élément (un entier) de tab1. Donc, tout ça pour dire que tab[1][3] est
le quatrième élément de tab1 !

Conclusion, si j'ai bien compris : l'opération i[j] en C est converti en
des *(i+j) et c'est une simple commodité d'écriture. Mais vu mon
raisonnement précédent, cette commodité est une chose bien pratique pour
la lisibilité.

Est-ce que c'est correct ou je délire ?


Merci


François