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 - principes de base et philosophie
(Article écrit par Diallo Barrou et extrait d'Amiga News - juillet 1993)
|
|
Parmi tous les langages existants sur Amiga, il en est un qui de par sa structure et sa puissance
de programmation fait quasi l'unanimité parmi les utilisateurs professionnels ou non.
Vous l'avez reconnu, c'est le langage C.
Suite à la difficulté d'un grand nombre d'amigaïstes à se mettre à la programmation de ce langage
assez rebutant au début, voici une petite introduction aux principes de base et à la philosophie
du C ANSI. Pourquoi ANSI ? Tout simplement parce que c'est une norme qui assure la portabilité
des programmes écrits en C sur toutes les machines munies d'un compilateur ANSI. Que les programmeurs
avertis se rassurent, des trucs et astuces ne manqueront pas de vous étonner en temps voulu !
Nous avons de très bons compilateurs C sur Amiga et même des gratuits (comme
DICE ou ZC) dans la
collection Fred Fish qui permettent à n'importe quel débutant ou programmeur confirmé de s'initier
au C. Ceci dit, à vos clavier, et tout de suite quelques commandes pour compiler un programme C
sur votre compilateur une fois installé :
Avec DICE :
dh0:1> dcc -f mon_programme.c -o mon_programme
|
Avec Aztec C 5.x :
dh0:1> cc mon_programme.c
|
Puis pour le "linkage" :
dh0:1> ln mon_programme -lc
|
Avec Lattice C :
dh0:1> lc mon_programme.c -L
|
Bien sûr, chaque compilateur possède ses options qui sont d'ailleurs souvent en très grand nombre.
Nous ne rentrerons pas des les détails, les manuels sont là pour ça !
Prémices...
Le langage C est très pauvre en instructions (une trentaine), mais ce qui fait sa puissance,
ce sont ses opérateurs et bien sûr ses bibliothèques de fonctions riches et variées. J'ai bien
dit de fonctions, car contrairement au langage Pascal, le C n'a pas la notion de procédure,
nous verrons cela plus tard.
/* Voici le plus petit programme C possible */
main()
{
}
|
Entre les accolades viennent se loger les instructions du programme et les appels aux fonctions des
bibliothèques. Le passage d'un programme source C à l'exécutable nécessite au compilateur plusieurs
passes, nous allons nous pencher sur la première qui est l'étape du préprocesseur.
Le préprocesseur
Souvent peu ou mal utilisé, cette étape de compilation est pourtant essentielle et apparaît
comme l'un des atouts du C. Elle permet de charger des fichiers d'en-têtes standard (les .h)
qui contiennent, entre autres, les noms des fonctions que l'on veut utiliser, mais aussi de
définir des constantes et des macros. Les directives du préprocesseur ont la particularité
de commencer par un ou deux "#".
#include <stdio.h>
#define PI 3.14159265
main()
{
printf("Hello world!\n");
printf("PI = %f\n", PI);
}
|
Ce petit programme charge à la compilation le fichier d'en-tête "stdio.h" grâce à la directive "#include"
qui contient la définition de la fonction "printf()", qui va être utilisée pour afficher les messages
à l'écran. La seconde ligne définit la constante "PI", qui sera réutilisée pour être affichée.
Le préprocesseur peut aller plus loin et tester si la constante PI existe et auquel cas ne pas la
redéfinir. La syntaxe est la suivante :
#ifdef CONSTANTE
#else /* optionnel */
#endif
|
Ou :
#ifndef CONSTANTE
#else /* optionnel */
#endif
#ifndef PI /* SI PI n'existe pas alors */
#define PI 3.14159265 /* la définir */
#endif /* FIN-SI */
|
Ces directives de compilations peuvent bien sûr être imbriquées à souhait :
#ifndef VRAI /* SI VRAI n'est pas défini */
#ifndef FAUX /* SI FAUX n'est pas défini */
#define VRAI 1
#define FAUX 0
#else /* SINON */
#define VRAI 1
#endif /* FIN-SI */
#endif /* FIN-SI */
|
Les macro-constantes (car ce sont leur véritable nom) peuvent être elles aussi
imbriquées :
#define PI 3.14159265 /* définition de PI */
#define PI2 PI/2.0 /* définition de PI/2 grâce à PI */
#define PI4 PI2/2.0 /* définition de PI/4 grâce à PI/2 */
|
Le préprocesseur connaît bien d'autres directives telles que
"#elif" pour SINON-SI, ou "#undef" pour retirer la définition d'une constante.
La syntaxe de base de "#define" est la suivante :
#define Identificateur Chaîne
|
A chaque fois que le compilateur trouvera l'Identificateur dans le programme,
il le remplacera par "Chaîne". Exemple :
#define MESSAGE "Hello World"
#define ENTIER 2
...
printf(MESSAGE); /* affiche Hello World */
resultat = 1 + ENTIER; /* Calcul le résultat = 3 */
...
|
Attention ! Cela met en évidence certains pièges comme le fait que le préprocesseur
n'évalue pas le contenu des macro-constantes, mais procède à un remplacement
bête et méchant de la chaîne :
#define ENTIER7 2+5
...
resultat = 6 * ENTIER7 ;
|
Le préprocesseur fournira :
Ce qui donne 17 (12+5) et non 42 (6*7) comme on aurait pu l'imaginer...
Les #define peuvent servir aussi pour la compilation conditionnelle d'une partie de
programme, en effet comment faire en sorte qu'un programme se compile et
s'exécute aussi bien sous 68000 que sous 68040 en tenant compte de l'évolution entre
ces deux processeurs ? Eh bien, tout simplement en déclarant une constante qui
contient le nom du processeur et en définissant ce que le compilateur doit faire
lorsqu'un cas critique se présente :
#define MC68030 /* on définit qu'on travail sur un A3000 */
...
#ifdef MC68040 /* programme pour 68040 */
#else
#ifdef MC68030 /* programme exécuté ici car on a définit MC68030 */
#endif
#endif
|
Bien sûr, on peut tester ce genre de chose invariante lors de l'exécution
du programme de façon différente avec la directive :
Exemple pour ne compiler qu'à partir du 68020 :
#define PROCESSEUR 68010 /* J'ai un 68010 */
#if PROCESSEUR < 68020
printf("Il vous faut absolument un A1200 pour ce programme \n");
#else
printf(" Ok, vous avez le matériel requis...\n");
#endif
|
On peut ainsi dire au compilateur de ne compiler une partie de programme plutôt
qu'une autre si une condition est vérifiée.
Les macro-fonctions préprocesseur
Maintenant que nous savons comment déclarer des constantes, poursuivons
avec les macro-fonctions, qui ne sont, en fait qu'une extension de la
directive #define :
#define Identificateur(parametres [, ...] instructions
|
Tout de suite, un petit exemple qui définit une macro qui renvoit le maximum
de deux nombres entiers. On utilisera l'opérateur ternaire de comparaison qui
s'écrit comme ceci : "Condition ? Vérifiée : Non Vérifiée" et qui corresponds à :
if (condition) Vérifiée
else
Non Vérifiée
|
Cet opérateur permet de faire un "IF THEN ELSE" sur une ligne !
Remarquons au passage que le C n'a pas d'instruction "THEN".
Voici donc la fameuse macro "MAX" :
#define MAX(nbre1, nbre2) (((nbre1) > (nbre2)) ? (nbre 1) : (nbre2))
...
resultat = MAX(9,3);
...
|
Le préprocesseur remplacera la dernière ligne par son équivalent avec les
bons paramètres, ce qui donnera :
resultat = 9 > 3 ? 9 : 3 ;
|
9 est-il supérieur à 3 ? Oui alors resultat=9 ; Vous remarquerez que dans la
macro les paramètres ont été soigneusement parenthésés, voici un exemple
qui illustre que l'oublie des parenthèses peut provoquer des surprises :
#define TRIPLE(nbre) nbre+nbre+nbre
...
resultat = TRIPLE(5) * TRIPLE(2);
...
|
Le préprocesseur remplacera la ligne des résultats par :
resultat = 5+5+5 * 2+2+2 ;
|
La multiplication étant prioritaire sur l'addition, la variable "resultat"
sera égale à 24 (10+10+4) au lieu de 90 (15*6).
Remarque : un #define doit tenir sur une ligne sans retour chariot,
il faut donc, au besoin protéger la ligne du retour chariot avec un antislash
comme ceci :
#define MOYENNES (a,b,c,d,e) ((a)+(b)+(c)+\(d)+(e))/5
|
Voilà pour ce qui est des macros, il faut y réfléchir à deux fois avant
l'élaboration d'une macro un peu complexe, mais c'est tellement pratique !
Préprocesseur : encore plus !
Nous avons vu comment créer nos propres constantes et macros, mais qu'en est-il
du standard ANSI ? Celui-ci propose des constantes et des directives (un peu spéciales)
qui peuvent faciliter la vie d'un programmeur en C, c'est ce que nous allons aborder
maintenant.
Tout compilateur C ANSI doit définir la constante __STDC__ qui garantit sa conformité à
la norme. C'est, je le répète, très important pour la portabilité et la pérénité
de votre programme ! Nous allons le tester avec une nouvelle directive : #error <Message
d'erreur>
/* arrête la compilation à cet endroit et affiche le message */
|
Exemple :
#if __STDC__ == 1 /* Test si ANSI */
#define ON_EST_EN_ANSI
#else
#error Le compilateur n'est pas un compilateur ANSI
#endif
|
Il existe évidemment d'autres constantes standard comme :
- __TIME__ qui contient l'heure système (hh:mm:ss).
- __DATE__ qui contient la date système (Mmm jj aaaa).
- __LINE__ qui contient le numéro de ligne actuel.
- __FILE__ qui contient le nom du fichier en compilation.
Exemple :
...
printf("On est le %s, et il est %s\n", __DATE__, __TIME__);
...
|
Allez, une petite dernière directive et nous allons aborder les
maigres opérateurs du préprocesseur, il s'agit de "#line" qui, comme son nom
l'indique, permet de modifier le numéro de ligne courant stocké dans __LINE__
mais aussi éventuellement (et on ne pouvait pas le deviner) la valeur de __FILE__.
Sa syntaxe est :
#line numéro_de_ligne ["nom_du_programme"]
|
Tout de suite un exemple :
#line 2
printf("Ligne %d\n", __LINE__); /* on est à la ligne 2 */
#line 3 "essai.c"
printf("Ligne %d et fichier est %s \n", __LINE__, __FILE__); /* compilation en cours sur le fichier essai.c */
|
Les opérateurs
Comme promis, nous allons terminer sur les trois opérateurs du préprocesseur, qui
malheureusement sont mal ou peu connus, ce sont "#", "##" et "defined".
Ce dernier renvoit "1" (VRAI) si sont argument est défini et "0"
dans le cas contraire :
#if defined(MC68040) /* Si MC68040 est défini alors */
#define PROCESSEUR_OK
#else
#error Ce programme ne marche que sur A4000 !
#endif
|
L'opérateur #parametre et qui s'utilise dans des #define permet de passer des
paramètres littéraux aux macros, le paramètre sera automatiquement mis entre
guillemets avant que la macro soit transformée dans le programme :
#define DEBUG_PRG(msg) printf("MESSAGE: Fichier %s ligne %d\n\t" #msg\ "\n" ,__FILE__, __LINE__)
|
Cette macro (qui peut être utile pour le déverminage) prend comme paramètre
"msg", et celui-ci sera remplacé par "valeur de msg", ce qui nous permet
d'afficher un message avec le nom du programme et le numéro de la ligne courante facilement :
...
DEBUG_PRG(Ici tout va bien!);
|
Encore plus fort, l'opérateur "##" permet la concaténation de valeurs. Exemple :
#define VALEUR(var) printf("La variable essai_" #var \ " est égale à %d...\n", essai_##var)
|
Ici l'argument "var" est utilisé une première fois pour afficher le nom de la
variable, et une deuxième pour afficher sa valeur, en sachant que mes variables commencent
par "essai_". Si on appelle cette macro comme ceci :
...
int essai_indice = 10; /* déclaration d'un entier égal à 10 */
VALEUR(indice); /* Appel de la macro pour savoir sa valeur */
|
...on obtiendra le résultat suivant : la variable essai_indice est égale à 10.
Puissant le préprocesseur, non ? Sur ce, nous voilà arrivé à la fin de cet article
en guise d'introduction au C ANSI. Maintenant, à vous de jouer, la prochaine fois
nous verrons les types complexes et les pointeurs. Ciao !
|