Suivez-nous sur X

|
|
|
0,
A,
B,
C,
D,
E,
F,
G,
H,
I,
J,
K,
L,
M,
N,
O,
P,
Q,
R,
S,
T,
U,
V,
W,
X,
Y,
Z,
ALL
|
|
0,
A,
B,
C,
D,
E,
F,
G,
H,
I,
J,
K,
L,
M,
N,
O,
P,
Q,
R,
S,
T,
U,
V,
W,
X,
Y,
Z
|
|
0,
A,
B,
C,
D,
E,
F,
G,
H,
I,
J,
K,
L,
M,
N,
O,
P,
Q,
R,
S,
T,
U,
V,
W,
X,
Y,
Z
|
|
A propos d'Obligement
|
|
David Brunet
|
|
|
|
Programmation : C - l'opérateur "sizeof", les conversions explicites de type et les constantes caractères
(Article écrit par Batchman et extrait d'A-News (Amiga News) - mai 1990)
|
|
Dernier article sur le langage C proprement dit. Nous allons voir en détail les quelques notions que nous n'avons pas encore
abordées.
L'opérateur sizeof
Cet opérateur permet de connaître la taille en octets d'une variable ou d'un type de variables. Deux écritures sont possibles :
- sizeof truc (pour une variable).
- sizeof(type) (pour un type de données).
A quoi bon connaître la taille des données, on n'en a besoin nulle part. Effectivement, dans ce que nous avons vu
jusqu'ici, ça n'est pas utile mais certaines fonctions des bibliothèques ont besoin de connaître la taille des
objets qu'elles manipulent (exemple : les instructions de lecture/écriture sur fichier, d'allocation de mémoire,
etc.). Pour toutes ces fonctions, il est nécessaire d'exprimer la taille des objets en nombre d'octets. Il est possible
d'écrire ce nombre explicitement. Mais imaginez que vous vouliez adapter votre programme sur une autre machine, dont
le compilateur C ne réserverait éventuellement pas la même place pour les variables (ou même sur Amiga avec un autre
compilateur). Il faudrait alors recalculer tous ces éléments dans tout le programme, tâche fastidieuse, sans compter
les risques d'erreur et d'oubli.
Autre avantage que sizeof vous permet d'éviter : supposons que vous travaillez sur des structures dont vous connaissez
la taille, et que vous avez écrite en chiffres un peu partout. Supposez maintenant que vous rajoutez un champ à la
structure : sa taille change et ici aussi, il faut tout recalculer et modifier.
Bref, l'opérateur sizeof permet d'éviter toute cette gymnastique. C'est le compilateur lui-même qui calcule la
taille en octets de la variable ou du type indiqué. C'est bien pratique si vous utilisez un type construit
comme une structure.
Par exemple, l'allocation dynamique d'une structure (on reviendra là-dessus, ça n'est pas notre sujet) peut se
faire dans le style suivant :
ptr = malloc(sizeof (struct toto));
|
...plutôt que :
...horreur !
Un dernier détail : le calcul de la valeur de sizeof se fait à la compilation, pas à l'exécution.
Par conséquent, si sizeof figure dans un calcul, ce dernier est évalué une fois pour toutes à la compilation.
Donc, le calcul n'est pas fait à l'exécution, comme le texte du programme pourrait le laisser
croire, et ne le ralentit pas. Attention cependant, si le calcul comprend des variables, leur valeur n'est
connue évidemment qu'à l'exécution : dans ce cas, la compilation n'évalue qu'une partie du calcul.
Les conversions explicites de type (ou cast)
Ça, je vous l'avais gardé pour la fin. Je parle de conversions explicites de types car il y a bien entendu
des conversions implicites : quand, dans un calcul, vous utilisez simultanément des caractères, des entiers
signés ou non, longs, courts ou standard, ou encore des réels, le compilateur génère un code que vous ne
voyez pas mais qui effectue toutes les conversions entre ces types : entier court non signé en flottant, entier
long signé en entier court non signé, etc. Ces conversions ont pour but de faire les calculs à partir de
données homogènes : il tend bien sûr que toutes les variables soient de même type pour que le résultat ait une signification.
Mais vous pouvez demander une telle conversion explicitement de la façon suivante :
(nouveau type) nom de variable
|
Une affectation peut ainsi s'écrire, bien que cela ne soit pas nécessaire :
Dans cette affectation, "x" est une variable d'un type numérique quelconque (caractère, entier, réel) qui est
converti en entier long signé par l'opérateur (long). C'est simple comme bonjour. Je vous rappelle qu'on aurait
pu écrire "y = x;" c'était encore plus simple. En fait, le compilateur sait que "x" et "y" ne sont pas du même type
et il génère automatiquement la conversion, qui est donc implicite.
Mais il arrive que le compilateur, aussi dévoué qu'il soit, ne puisse pas faire le boulot à votre place. En effet,
il ne fait pas forcément ce genre de conversion pour les arguments des fonctions. Au mieux, il va faire la conversion
normalement ; au pire, il ne dira rien, ce lâche, et la variable que vous passez en paramètre sera interprétée
comme une variable d'un autre type : bonjour le bogue !
Tout n'est pas clair à ce sujet et cela varie certainement avec chaque compilateur. A titre d'exemple, avec le Lattice,
les conversions entre les types entiers et/ou caractères sont automatiques : si vous passez un caractère ou entier
court à une fonction qui attend comme paramètre un entier long (et vice-versa), la conversion est automatique.
Par contre, les conversions entre types entiers et réels (et vice-versa) semblent ne pas s'effectuer d'office pour
les paramètres de fonction et il faut ici demander une conversion explicite.
Supposons que nous avons une fonction truc qui admet comme argument un entier long signe :
"toto", vous l'aviez reconnu, c'est le paramètre. Ainsi, quel que soit son type numérique, sa valeur est convertie
en entier long signé avant d'être passée en argument de la fameuse fonction truc. De cette façon, truc reçoit toujours
une valeur compatible avec l'utilisation qu'elle en fait. Ici, la conversion de type n'est nécessaire que si toto
est d'un type réel : pour un type entier elle est facultative.
Il existe d'autres cas où elle l'est aussi mais où le compilateur vous oblige pourtant à la faire.
Rappelez-vous l'article sur les pointeurs ; on peut définir différents types
de pointeurs : sur des entiers, des caractères, des structures, etc. Tous ces pointeurs sont de types différents
et pourtant ce sont des pointeurs, donc des variables normalement compatibles entre elles.
Il est normal que le compilateur enrage quand vous lui proposez une affectation entre deux types de pointeurs différents
(en Lattice, ça donne : "Warning 30 Pointers do not point to the same object", je connais mes classiques).
Mais, après tout, vous avez bien le droit d'affecter un pointeur un pointeur de type différent, ça peut être nécessaire.
Dans ce cas, il faut faire une conversion explicite de type pour clouer le bec à ce compilo contestataire. Supposons que
nous voulions donner une valeur à un pointeur de caractère. Cela se fait par :
"toto", dont c'est le grand retour, peut être un pointeur sur n'importe quoi. "ptr", par contre, est un pointeur de caractère. La
conversion de type ci-dessus va éliminer le "warning". Notez bien que pour un pointeur, l'opérateur de conversion est :
(type de l'objet pointé *)
|
...d'où, par exemple :
- (long *) pour un pointeur sur un entier long signé.
- struct toto *) pour un pointeur sur une structure.
- (int (*) ()) pour un pointeur sur une fonction retournant un entier.
Enfin, les conversions de type entre les structures sont strictement impossibles.
Tout ceci n'est pas évident, surtout ce qui concerne les paramètres de fonction. J'espère même que
ça vous a paru obscur, car ça l'est effectivement. Par conséquent, mon message, car il y a un message
derrière tout cela, c'est qu'il faut être très prudent dès lors qu'on passe à une fonction des paramètres
d'un type différent de celui qu'elle attend.
Les constantes caractères
Soit une variable "toto" de type caractère. Vous voulez lui donner une valeur : comment faire ?
Si vous aimez vous faire insulter, écrivez :
En effet, "A" représente une chaîne de caractère(s) et non pas un caractère. Lorsque vous écrivez "A", le compilateur
réserve la place nécessaire au stockage de la chaîne "A", c'est-à-dire, les deux caractères A et \0.
Puis il retourne un pointeur sur le début de la chaîne (donc sur le "A") et tente de réaliser l'affectation demandée.
Or, il ne peut pas affecter un pointeur (32 bits) à un caractère (8 bits) ; d'où le déluge d'injures.
Les constantes caractère sont donc définies, non pas par les guillemets "", mais par les apostrophes '',
appelées aussi "quotes". Il fallait donc écrire :
Mais vous pouvez vouloir affecter à ce caractère autre chose qu'un caractère affichable. Comment mettre, en
effet, une tabulation ou un "new-line" ?
Il est en fait possible d'exprimer tous les caractères par leur code ASCII en octal (base huit), sur le modèle
suivant : '\xxx'. Par exemple, 'A' (code ASCII 65) est équivalent à '\101'. De la même façon, le retour-chariot
(code ASCII 13) peut s'écrire '\015' (je rappelle que c'est de l'octal).
Il y a dans ce cas une meilleure solution. C'est la constante '\r' qui équivaut strictement à '\015'.
Avouez que c'est plus simple.
Autres abréviations du même style :
- \n pour new-line (passage au début de la ligne suivante).
- \t pour une tabulation.
- \b pour le "backspace" (une case à gauche).
- \r pour retour-chariot (retour au début de la même ligne).
- \f pour "form feed" (saut de page pour l'imprimante ou effacement de l'écran sur Amiga).
- \\ pour le caractère \.
- \' pour le caractère '.
- \" pour le caractère ".
- \0 pour fin de chaîne.
Il va de soi que vous pouvez aussi utiliser ces constantes caractères à l'intérieur de chaînes, mais
cette fois-ci, sans les apostrophes. Par exemple :
"\"A-News\"\n le journal qu'il est bon!\n"
|
Les mots réservés du C
Vous les connaissez maintenant tous, sauf trois dont je n'ai pas encore parlé : "entry", "fortran" et "asm".
En fait, ils n'existent pas encore dans les versions actuelles du C, mais ses concepteurs ont voulu
garder des possibilités d'extensions ultérieures. De ce fait, certains compilateurs reconnaissent ces mots-clefs
et interdisent leur emploi comme identificateurs. C'est partiellement le cas du compilateur Lattice qui reconnaît "entry".
C'est sur cette note d'exotisme que se termine notre tour d'horizon du langage C. Je pense avoir abordé, entre
A-News n°14 et ce numéro, toutes les possibilités qu'offre ce langage.
Et pourtant, ce n'est pas la fin de la rubrique C : il nous reste encore à parler de nombreuses choses dont
les bibliothèques, la compilation séparée, le préprocesseur, qui fera l'objet du prochain article, etc.
|