Obligement - L'Amiga au maximum

Samedi 27 mai 2017 - 06:20  

Translate

En De Nl Nl
Es Pt It Nl


Rubriques

 · Accueil
 · A Propos
 · Articles
 · Galeries
 · Glossaire
 · Hit Parade
 · Liens
 · Liste jeux Amiga
 · Quizz
 · Téléchargements
 · Trucs et astuces


Articles

 · Actualité (récente)
 · Actualité (archive)
 · Comparatifs
 · Dossiers
 · Entrevues
 · Matériel (tests)
 · Matériel (bidouilles)
 · Points de vue
 · En pratique
 · Programmation
 · Reportages
 · Tests de jeux
 · Tests de logiciels
 · Tests de compilations
 · Articles divers

 · Articles in english
 · Articles in other languages


Twitter

Suivez-nous sur Twitter




Liens

 · Sites de téléchargements
 · Associations
 · Pages Personnelles
 · Moteurs de recherche
 · Pages de liens
 · Constructeurs matériels
 · Matériel
 · Autres sites de matériel
 · Réparateurs
 · Revendeurs
 · Presse et médias
 · Programmation
 · Développeurs logiciels
 · Logiciels
 · Développeurs de jeux
 · Jeux
 · Autres sites de jeux
 · Scène démo
 · Divers
 · Informatique générale


Jeux Amiga

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


Trucs et astuces

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


Glossaire

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


Partenaires

Annuaire Amiga

Amedia Computer

Relec

Hit Parade


Soutien

N'hésitez pas à soutenir le projet Obligement



Contact

David Brunet

Courriel

 


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 :

ptr = malloc(28);

...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 :

y (long) x;

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 :

truc((long)toto);

"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 :

ptr = (char *) toto;

"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 :

toto = "A";

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 :

toto = 'A';

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.


[Retour en haut] / [Retour aux articles]