les pointeurs ou les emp
Le
heron maladroit
/*d'après K&R 2 : "there is one difference between an array name and a
pointer that must be kept in mind. A pointer is a variable , so pa = a
and pa++ are legal. But an array name is not a variable. constructions
like a=pa and a++ are illegal. Je veux juste avoir une idée des
messages d'erreur relatifs aux pointeurs pour l'instant au niveau des
chaines de caractères. */
#include <stdio.h>
int main(void)
{
char Chaine[10] = "abc" ;
char *Chaine2[] = {"abc", "def" }; /* 2 pointeurs sur Char */
printf("%s", Chaine) ; /* A : OK */
printf("%s", *Chaine) ; /* B : warning: format â%sâ exp=
ects type
âchar *â, but argument 2 has type âintâ .=
.. Segmentation fault :
*/
printf("%s", *(Chaine+0) ) ; /* C : warning: format â%sâ=
expects
type âchar *â, but argument 2 has type âintâ=
Segmentation
fault : */
printf("%c", *(Chaine+0) ) ; /* D: OK */
printf("%s", Chaine+0 ) ; /* E : OK */
printf("%s", Chaine+1 ) ; /* F : OK */
printf("%s", &Chaine ) ; /* G : warning: format â%sâ exp=
ects type
âchar *â, but argument 2 has type âchar (*)[10]â=
OK */
printf("%s", &Chaine[0] ) ; /* H: OK */
/*CHAINE 2 */
printf("%s", &Chaine2) ; /* I : warning: format â%sâ exp=
ects type
âchar *â, but argument 2 has type âchar * (*)[2] ..=
. résultat
indéterminé*/
printf("%o", &Chaine2) ; /* J : warning: format â%oâ exp=
ects type
âunsigned intâ, but argument 2 has type âchar * (*)=
[2] résultat :
27776137614 */
printf("%c", *Chaine2[1]) ; /* K : warning: format â%câ=
expects
type âintâ, but argument 2 has type âchar **â=
résultat : vide */
printf("%s", Chaine2[0]) ; /* L : OK */
printf("%s", Chaine2) ; /* M : warning: format â%sâ expe=
cts type
âchar *â, but argument 2 has type âchar **â=
resultat : ����E#abc */
printf("") ;
return 0 ;
}
/*
A :
Resultat : abc
Je comprends ici que Chaine est un tableau (ou une chaine constante
pour être plus précis?).
Le paragraphe suivant l'extrait du K&R précédent dis que lorsqu'u=
n nom
de tableau est envoyé à une fonction, c'est l'emplacement du
1er élément qui est envoyé et à l'intérieur de =
cette fonction, cet
argument est une variable locale
Je ne comprends pas que pour printf avec %s, * ou le contenu de Chaine
ne soit pas appelé mais ce qui me semble être l'adresse en mÃ=
©moire de
Chaine comme lorsqu'on passe char *pointeur ; pointeur = Chaine ou
pointeur = &Chaine[0]. Le point H va dans ce sens
Ce premier cas est celui qui me fait toujours réfléchir et peut m=
e
donner une erreur . Surtout que dans ce cas scanf ajoute de l'insulte
à la blessure car : scanf("%s", Chaine) au lieu de scanf("%s",
&Chaine) comme on peut s'y attendre ;
char Chaine[10] ;
printf("Entrez une chaine : ") ;
scanf("%s", &Chaine[0]) OK MAIS scanf("%s", &Chaine) affiche
warning: format â%sâ expects type âchar *â,=
but argument 2 has type
âchar (*)[10]â mais la saisie se fait quand même
B :
Resultat : Segmentation fault
Le warning dit que le 2ème argument est de type int.
Ce n'est pas la première fois que j'ai ce message. Est-ce que je dois
comprendre que dans printf, %s est le 1er argument, et *Chaine est le
second? Alors comme le pointeur *Chaine n'a pas été déclar=
é, *Chaine
est de type int par défaut ?
Ensuite, le K et R dis pour pa = a un tableau constant a[i] peut
aussi s'écrire *(a+i) pourquoi pas *a (soit le contenu de a) avec %s
affiche une chaine?
C :
Resultat : Segmentation fault
Resultat identique si *(Chaine+1), *(Chaine+2), *(Chaine+3) etc
*(Chaine+9)
L'argument %s semble ici bloquer le déroulement du programme. Ca
paraît cohérent avec %c dans printf :
D :
Resultat : a
E:
Resultat : abc
F:
Resultat : bc
G :
Resultat : abc
1ere fois que je vois ce warning, je dois avouer que je n'avais pas
penser à appeler un tableau d'une chaine constante ou d'entier avec
l'adresse &. Le '&' est à mes yeux associé aux pointeurs. A ma gr=
ande
surprise, le code fonctionne. En tant que débutant avec le langage C,
je trouve intéressant le warning: il montre bien qu'un tableau est un
pointeur vers le type de données dans lequel il a été dÃ=
©finit/déclaré
ici : char (*)[10], cà d 10 pointeurs vers des données de type
caractères?
H:
Resultat : abc
I :
Resultat : des carrés s'affichent : �`� et abc
Je suppose que c'est la représentation d'emplacements mémoire (vo=
ir I)
J:
Resultat : 27776137614
Le message confirme que ce sont bien 2 pointeurs sur char
Je suppose que c'est l'adresse de *Chaine2 représentée sous la fo=
rme
d'un entier?
K :
Resultat : abc
Je ne comprends pas l'erreur "invalid initializer". Pourtant le code
fonctionne même avec : *Chaine[0], *Chaine[20]
L :
Resultat : abc
error: invalid initializer
error: expected expression before â]â token
error: array subscript is not an integer
warning: format â%câ expects type âintâ, bu=
t argument 2 has type âchar
*â
Et pourtant ça fonctionne.
Invalid initializer est dans J alors je suppose que la réponse
précédente satisfait la question présente.
expected expression : je comprends que je dois mettre un indice
entre [ ] . Dans J, les indices ne me semblent pas utiles et ici, ils
seraient utiles?
array subscript is not an integer : je ne comprends pas
warning : format %c expects type int : selon moi, %c attend un
caractère et non pas un int
M:
Resultat inédit : ����E#abc
warning: format â%sâ expects type âchar *â,=
but argument 2 has type
âchar **â
Je ne suis pas sûre de comprendre.
-o-
Je vais arrêter là étant arrivé à la moitié d=
e l'alphabet et les
besoins physiologiques se font pressantsurtout que le tableau
multidimensionnel chaine2 permet encore d'autres combinaisons
*/
pointer that must be kept in mind. A pointer is a variable , so pa = a
and pa++ are legal. But an array name is not a variable. constructions
like a=pa and a++ are illegal. Je veux juste avoir une idée des
messages d'erreur relatifs aux pointeurs pour l'instant au niveau des
chaines de caractères. */
#include <stdio.h>
int main(void)
{
char Chaine[10] = "abc" ;
char *Chaine2[] = {"abc", "def" }; /* 2 pointeurs sur Char */
printf("%s", Chaine) ; /* A : OK */
printf("%s", *Chaine) ; /* B : warning: format â%sâ exp=
ects type
âchar *â, but argument 2 has type âintâ .=
.. Segmentation fault :
*/
printf("%s", *(Chaine+0) ) ; /* C : warning: format â%sâ=
expects
type âchar *â, but argument 2 has type âintâ=
Segmentation
fault : */
printf("%c", *(Chaine+0) ) ; /* D: OK */
printf("%s", Chaine+0 ) ; /* E : OK */
printf("%s", Chaine+1 ) ; /* F : OK */
printf("%s", &Chaine ) ; /* G : warning: format â%sâ exp=
ects type
âchar *â, but argument 2 has type âchar (*)[10]â=
OK */
printf("%s", &Chaine[0] ) ; /* H: OK */
/*CHAINE 2 */
printf("%s", &Chaine2) ; /* I : warning: format â%sâ exp=
ects type
âchar *â, but argument 2 has type âchar * (*)[2] ..=
. résultat
indéterminé*/
printf("%o", &Chaine2) ; /* J : warning: format â%oâ exp=
ects type
âunsigned intâ, but argument 2 has type âchar * (*)=
[2] résultat :
27776137614 */
printf("%c", *Chaine2[1]) ; /* K : warning: format â%câ=
expects
type âintâ, but argument 2 has type âchar **â=
résultat : vide */
printf("%s", Chaine2[0]) ; /* L : OK */
printf("%s", Chaine2) ; /* M : warning: format â%sâ expe=
cts type
âchar *â, but argument 2 has type âchar **â=
resultat : ����E#abc */
printf("") ;
return 0 ;
}
/*
A :
Resultat : abc
Je comprends ici que Chaine est un tableau (ou une chaine constante
pour être plus précis?).
Le paragraphe suivant l'extrait du K&R précédent dis que lorsqu'u=
n nom
de tableau est envoyé à une fonction, c'est l'emplacement du
1er élément qui est envoyé et à l'intérieur de =
cette fonction, cet
argument est une variable locale
Je ne comprends pas que pour printf avec %s, * ou le contenu de Chaine
ne soit pas appelé mais ce qui me semble être l'adresse en mÃ=
©moire de
Chaine comme lorsqu'on passe char *pointeur ; pointeur = Chaine ou
pointeur = &Chaine[0]. Le point H va dans ce sens
Ce premier cas est celui qui me fait toujours réfléchir et peut m=
e
donner une erreur . Surtout que dans ce cas scanf ajoute de l'insulte
à la blessure car : scanf("%s", Chaine) au lieu de scanf("%s",
&Chaine) comme on peut s'y attendre ;
char Chaine[10] ;
printf("Entrez une chaine : ") ;
scanf("%s", &Chaine[0]) OK MAIS scanf("%s", &Chaine) affiche
warning: format â%sâ expects type âchar *â,=
but argument 2 has type
âchar (*)[10]â mais la saisie se fait quand même
B :
Resultat : Segmentation fault
Le warning dit que le 2ème argument est de type int.
Ce n'est pas la première fois que j'ai ce message. Est-ce que je dois
comprendre que dans printf, %s est le 1er argument, et *Chaine est le
second? Alors comme le pointeur *Chaine n'a pas été déclar=
é, *Chaine
est de type int par défaut ?
Ensuite, le K et R dis pour pa = a un tableau constant a[i] peut
aussi s'écrire *(a+i) pourquoi pas *a (soit le contenu de a) avec %s
affiche une chaine?
C :
Resultat : Segmentation fault
Resultat identique si *(Chaine+1), *(Chaine+2), *(Chaine+3) etc
*(Chaine+9)
L'argument %s semble ici bloquer le déroulement du programme. Ca
paraît cohérent avec %c dans printf :
D :
Resultat : a
E:
Resultat : abc
F:
Resultat : bc
G :
Resultat : abc
1ere fois que je vois ce warning, je dois avouer que je n'avais pas
penser à appeler un tableau d'une chaine constante ou d'entier avec
l'adresse &. Le '&' est à mes yeux associé aux pointeurs. A ma gr=
ande
surprise, le code fonctionne. En tant que débutant avec le langage C,
je trouve intéressant le warning: il montre bien qu'un tableau est un
pointeur vers le type de données dans lequel il a été dÃ=
©finit/déclaré
ici : char (*)[10], cà d 10 pointeurs vers des données de type
caractères?
H:
Resultat : abc
I :
Resultat : des carrés s'affichent : �`� et abc
Je suppose que c'est la représentation d'emplacements mémoire (vo=
ir I)
J:
Resultat : 27776137614
Le message confirme que ce sont bien 2 pointeurs sur char
Je suppose que c'est l'adresse de *Chaine2 représentée sous la fo=
rme
d'un entier?
K :
Resultat : abc
Je ne comprends pas l'erreur "invalid initializer". Pourtant le code
fonctionne même avec : *Chaine[0], *Chaine[20]
L :
Resultat : abc
error: invalid initializer
error: expected expression before â]â token
error: array subscript is not an integer
warning: format â%câ expects type âintâ, bu=
t argument 2 has type âchar
*â
Et pourtant ça fonctionne.
Invalid initializer est dans J alors je suppose que la réponse
précédente satisfait la question présente.
expected expression : je comprends que je dois mettre un indice
entre [ ] . Dans J, les indices ne me semblent pas utiles et ici, ils
seraient utiles?
array subscript is not an integer : je ne comprends pas
warning : format %c expects type int : selon moi, %c attend un
caractère et non pas un int
M:
Resultat inédit : ����E#abc
warning: format â%sâ expects type âchar *â,=
but argument 2 has type
âchar **â
Je ne suis pas sûre de comprendre.
-o-
Je vais arrêter là étant arrivé à la moitié d=
e l'alphabet et les
besoins physiologiques se font pressantsurtout que le tableau
multidimensionnel chaine2 permet encore d'autres combinaisons
*/

Poser une question


Les chaines étant par défaut, non modifiables en C, on préfère rest er
cohérent et portable comme ceci :
char const *Chaine2[] = {"abc", "def" }; /* 2 pointeurs sur
char ... */
D'autre part, il n'y a pas de type 'Char' en C standard. C'est 'char'.
Le 'n' indique la fin de ligne, il se place donc ... en fin de
ligne !
printf ("%sn", Chaine); /* A : OK */
en effet, il force l'émission des caractères. Sinon, le caractères
placés après risquent de ne pas être émis ...
Ben oui, normal. *Chaine ou *(Chaine + 0) ou encore Chaine[0], désigne
un caractère (type char), qui est automatiquement converti en int. Cet
int n'a quasiment aucune chance d'être une adresse valide ("%s"
attends l'adresse d'une chaine de caractères). Tu veux peut être "%c"
pour voir le caractère ?
Même problème
Non, pas OK. Le comportement est indéfini. On ne fait pas ça.
Oui, carrément n'importe quoi ...
Pareil. Tu programmes au hasard ? Tu testes le compilateur ? Pourquoi
"%o" ? Tu trouves que l'octal est adapté ?
Oui, portnawak ...
Non, résultat indéfini, c'est tout ...
Il manque :
printf("%sn", Chaine2[1]) ; /* O : OK */
Pas très utile si on prends soin de mettre le 'n' à la fin de l
ligne, comme il se doit ...
C'est un tableau de char initialisé modifiable. Sa valeur est
{'a','b','c',0,0,0,0,0,0,0}
Oui, c'est à dire une adresse. L'adresse du premier élément du tablea u
si c'est le nom seul (<nom> ou <nom> + 0)
Oui, comme tous les arguments. Ici, c'est une variable locale de type
'adresse de char', dont un pointeur sur char (char *s, par exemple).
Chaine
Comme expliqué précédemment, "%s" attend l'adresse d'un tableau de
char initialisé avec une chaine, donc terminée par un 0. Si on lui
passe autre chose (*Chaine, par exemple), le comportement est
indéfini.
Y'a pas trop à réfléchir. Une fois qu'on sait ce qu'attend é%s", to ut
est clair. Et c'est pareil avec scanf() (sauf que c'est la taille qui
importe et non le contenu).
Pourquoi & ? On a expliqué en long en large et travers que quand on
passait le nom d'un tableau à une fonction, celui-ci était convertit
en adresse du premier élément. On peut donc mettre :
scanf("%s", Chaine)
ou
scanf("%s", Chaine + 0)
ou
scanf("%s", &Chaine[0])
si on aime la complication.
Attention, cette saisie est dangereuse et pas paratique, car elle
prend tout jusqu'au premier séparateur (espace, tab ou fin de ligne).
A tes risques et périls. Le comportement est indéterminé. On a déj à
fort affaire avec les comportements normaux. Ne pas perdre de temps
avec les comportements non définis ... Enfin, tu as voulu faire du
C...
Ben oui. C'est pas évident ?
Attention. Le rôle de * change selon le contexte.
Il sert
- soit d'opérateur de multiplication quand il est placé entre 2
opérandes : a * 2, a * b etc.
- soit de déclarateur de pointeur dans une déclaration de variable :
int *p; char const *s="hello"
- soit d'opérateur de déréférencement quand il est appliqué à u n
pointeur : inc c = *s, f (*s) etc.
ici, c'est le 3 ème cas. *Chaine est *(Chaine + 0), c'est à dire
Chaine[0]. C'est donc un caractère qui n'a rien à voir avec une
adresse. Le type est int, parce que l'appel de fonction avec un
paramètre de type char est convertit en int.
Parce que dans ce cas, *a (valeur pointée par a) désigne un caractère
et non une adresse ...
Bah oui, normal. "%c" attend un int qui représente la valeur d'un
caractère. Si on lui donne ce qu'il attend, tout va bien...
Peut importe. La réalité est que & est un opérateur qui retourne une
valeur et un type. La valeur est l'adresse d'un objet. Le type
retourné est 'pointeur sur le type de l'objet'. Appliqué à un tableau ,
ça retourne l'adresse du tableau (comme en C, le premier élément d'un
tableau se trouve en [0], la valeur est la même qu'avec le nom du
tableau), mais le type est différent. Ailiet de retourner le type
'pointeur sur un élément du tableau', on retourne 'pointeur sur le
tableau' ce qui est très différent, et peut avoir des conséquences
terribles si on utilise un offset : Chaine + 1 n'a pas du tout la même
valeur que &Chaine+1.
Oui, la valeur est la même. "Ça tombe en marche" ... Mais c'est faux
pour les raisons déjà expliquées.
Oui, les warnings essayent d'aider à comprendre les subtilités du C...
Mais pourquoi tu ne fais pas du Pascal ...
Des comportements indéfinis ...
Oui, en octal... Quelle idée. Le bon format pour une adresse est "%p"
et il faut forcer le paramètre 'adresse' en (void *)
certainement pas. C'est printf("n%c", *(Chaine2[1])) . Attention à la
préséance des opérateurs ...
Nan. "%c" attend un int représentant la valeur d'un caractère.
Il n'y a rien de prévu nativement pour représenter un tableau de
pointeurs.
Bin il va falloir que tu comprennes cela avant de pousser plus loin,
parce que tout en dépend...
Comme rappelé dans l'extrait du K&R, Chaine est un tableau ; il est donc
jugé trop gros pour être passé au sous-programme : pour passer un
paramètre il faut le copier dans un emplacement prédéfini avant l'appel,
puis au début de la fonction le recopier depuis la zone de passage
prédéfinie : cela s'appelle les conventions d'appel, et c'est très
semblable au circuit que font tes valises lorsque tu prends l'avion :
avant de monter dedans il te faut les enregistrer, puis elles sont mises
dans la soute, et te sont restituées à destination : comme c'est très
lourd, on préfère si possible utiliser les bagages de cabine, pardon les
paramètres passés par registre, qui évite le circuit compliqué.
Seulement les paramètres que l'on peut passer par registre sont limités
en taille ; et un tableau n'est pas acceptable: donc on préfère passer
un registre, en l'occurrence un pointeur vers la dite chaîne.
<LEÇON_SUIVANTE>
SAUF QU'en faisant cela, on ne transporte plus réellement la valise,
pardon, le tableau n'est pas réellement copié dans le sous-programme, on
travaille non pas sur une copie locale mais sur le vrai tableau, seul et
unique : cela est la cause profonde des « problèmes de pointeurs » en C:
il faut constamment veiller à ne pas écraser les tableaux que l'on nous
a passé en paramètres : d'où l'intérêt du mot-clé const, qui permet au
compilateur de renforcer notre attention.
</LEÇON_SUIVANTE>
Donc pour en revenir à printf(), il y a une différence fondamentale
entre %i/%o/%c/%p d'un côté, qui imprime la valeur des paramètres passés
comme variables locales pour printf, et %s de l'autre côté, qui imprime
la valeur du tableau global, externe à printf et donc seulement connu de
cette fonction par l'adresse à laquelle il commence.
Mmmm. Il faut dire que dans la plupart des cas, Chaine est équivalent à
&Chaine[0] (les cas où ce n'est pas équivalent sont: derrière sizeof,
derrière &, à gauche de = ou dans l'initialisation d'un objet).
scanf() est une fonction différente de printf(), et soumise à d'autre
contraintes : en particulier, pour que scanf("%i",???) fonctionne on ne
peut plus se contenter de la valeur d'une variable _avant_ l'appel à
scanf() dont on se moque éperdument : il faut que scanf() puisse
_modifier_ la variable, et pour cela il est nécessaire de passer son
adresse, autrement dit un pointeur vers sa position, à la fonction scanf
scanf("%i", &i);
Seulement, comme expliqué ci-dessus, avec les chaînes on a déjà passé ce
fameux pointeur, donc pour économiser on utilise directement le pointeur
scanf("%s", &Chaine[0]);
ou, ce qui est équivalent
scanf("%s", Chaine);
J'espère que tu vois pourquoi maintenant.
Je ne pense pas. Au mieux, la saisie va s'effectuer en écrasant des
données inutiles sur la pile de main(), et il n'y aura pas de
conséquences graves (mais la chaîne entrée au clavier ne sera pas dans
Chaine) ; au pire, tu vas avoir droit à un SIGSEGV pour avoir écraser
tes propres variables...
Si tu as bien suivi, c'est la même chose dans l'autre sens : cette
fois-ci tu n'essaye pas d'écrire dans ta pile, mais tu fais croire à
printf() que le caractère 'a' est l'adresse où se trouve la chaîne à
écrire...
Oui.
Bin si (sauf que ce n'est pas un pointeur) : l'objet Chaine est déclaré
comme tableau de char, donc lorsqu'on utilise l'opérateur * qui en
extrait le premier élément, on obtient le premier élément du tableau, et
le type du résultat est char.
Au niveau des conventions d'appel, char est trop petit, donc on
l'agrandit en int, ce qui explique le message ci-dessus
Là aussi, on est dans les choses de base concernant les pointeurs (en
l'occurrence, ne pas confondre l'opérateur * avec l'utilisation de *
pour déclarer des pointeurs), il faut absolument que tu maîtrises cela
avant de prétendre aller plus loin.
Antoine