|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Note : traduction par Serge Hammouche. 0.1 Introduction Dans ce chapitre, nous allons voir comment les compilateurs C fonctionnent, comment utiliser le compilateur Lattice C, rappeler des points importants en C, finalement expliquer comment utiliser les bibliothèques de l'Amiga et parler des trois différents types de mémoire. Comme vous l'avez remarqué, ce chapitre vous donne une petite introduction au C, aux compilateurs et à l'Amiga. 0.2 Comment compiler et éditer des liens Le principe des compilateurs C est que vous écrivez un document (habituellement appelé "code source"), qui est lu en deux phases par le compilateur C. La première fois, le compilateur C crée un fichier de travail, appelé "quad file". Le quad file est alors compilé une deuxième fois en un "object file". Le fichier objet contient essentiellement du code machine exécutable mais aussi des références à des fonctions/variables externes dans des fichiers objet et des bibliothèques. Ainsi, avant de pouvoir lancer le programme, nous devons y lier tous les fichiers objet et bibliothèques en un seul programme. C'est ce que fait l'éditeur de liens.
0.2.1 Le code source Quand vous voulez écrire des programmes en C, il vous faut un éditeur/ traitement de texte pour créer le "code source". Le code source ressemble à un document normal sauf qu'il est écrit en suivant des règles particulières. Presque tous les éditeurs/traitements de texte sont utilisables. Vous pouvez même utiliser le MEMACS qui vous est fourni à l'achat de votre Amiga (Memacs est sur la disquette "Extras".) Voici un exemple de code source :
Quand vous avez écrit le code source, vous devez le sauvegarder. Le standard est de terminer les noms de fichiers par l'extension ".c". Ainsi, si vous voulez appeler votre programme "hello", le code source doit être appelé "hello.c". 0.2.2 La première phase de compilation Une fois le code source sauvegardé, vous pouvez lancer la première phase de compilation. Si vous utiliser le compilateur Lattice C, utilisez simplement la commande "lc1". Pour compiler notre exemple, il suffit de taper "lc1 hello.c", et le compilateur va alors créer un fichier quad appelé "hello.q". 0.2.3 La deuxième phase de compilation Pour lancer la deuxième phase de compilation, vous utilisez la commande "lc2". Pour compiler notre exemple, vous n'avez qu'à taper "lc2 hello.q", et le compilateur va alors créer un fichier objet appelé "hello.o". Si vous voulez lancer la première et la deuxième passe du compilateur en une seule fois, vous pouvez utiliser la commande "lc". Ainsi, si vous tapez "lc hello.c", la première et la deuxième phase de compilation seront toutes deux exécutées (lc lance lc1 et lc2 automatiquement). 0.2.4 L'édition de liens Comme nous l'avons déjà vu, le fichier quad ne contient pas que des instructions en langage machine, il comporte aussi des appels à des fonctions externes dans des bibliothèques et d'autres fichiers objet. Avant de pouvoir exécuter le programme, vous devez donc lier le tout grâce à l'éditeur de liens. Lattice C utilise un éditeur de liens appelé "blink". Tous les programmes que vous écrivez doivent normalement être liés avec la routine de démarrage "c.o", et les deux bibliothèques : "lc.lib" et "amiga.lib". Le fichier objet et les deux bibliothèques sont dans le répertoire "lib" de la deuxième disquette du Lattice C. Ce répertoire est normalement déjà assigné à "LIB:", donc si vous voulez accéder aux fichiers, tapez simplement "LIB:c.o" par exemple. Pour la majorité des programmes, appelez l'éditeur de liens suivant cette syntaxe (ceci montre comment lier notre exemple "hello") :
[A] Le fichier objet "c.o" doit toujours être placé avant tous les autres fichiers objet. Ce fichier se trouve dans l'unité logique "LIB:". [B] Après le fichier de démarrage "c.o", vous pouvez placer tous les fichiers objet que vous voulez lier ensemble (pensez à mettre une virgule entre chacun d'eux). Dans cet exemple, nous n'avons qu'un fichier objet, "hello.o". [C] Après la liste des fichiers objet, tapez "TO" suivi du nom qui sera celui du programme exécutable. Dans cet example, nous voulons que notre programme s'appelle "hello". [D] Presque tous les programmes doivent être liés à des bibliothèques. Le mot-clef "LIB" indique à blink que les noms de fichier suivants sont des noms de bibliothèques (pensez à mettre une virgule entre chaque nom de bibliothèque). Ne confondez pas le mot-clef "LIB" avec l'unité logique "LIB:". "LIB" indique à l'éditeur de liens qu'une liste des noms de bibliothèques va suivre, tandis que l'unité logique "LIB:" indique à AmigaDOS sur quelle unité et dans quel répertoire trouver certains fichiers. [E] La bibliothèque "lc.lib" doit être l'avant-dernière bibliothèque de la liste (la bibliothèque se trouve dans l'unité logique "LIB:"). [F] La bibliothèque "amiga.lib" doit être la dernière bibliothèque de la liste (la bibliothèque se trouve dans l'unité logique "LIB:"). Si vous voulez appeler à la fois la première et la deuxième passe du compilateur et lier le fichier objet avec les bibliothèques standard, vous pouvez utiliser la commande "lc" avec le paramètre "-L". Ainsi, si vous voulez compiler et lier le programme avec une seule commande, vous n'avez qu'à taper :
La commande "lc" appelle la première et la deuxième passe du compilateur, et le paramètre "-L" signifie que le code objet final doit être lié avec les bibliothèques standard. Si vous voulez aussi que l'éditeur de liens cherche la bibliothèque mathématique standard, ajoutez simplement la lettre "m" après le "-L" :
Notre exemple "hello" ne nécessite pas la bibliothèque mathématique mais si vous faites un quelconque calcul, vous devez normalement l'inclure. Tous les exemples de ce manuel seront compilés et liés sans problème avec la commande :
0.3 Les programmes C Ce manuel s'adresse à tous ceux ayant quelques notions de C et désirant programmer l'Amiga. Si vous savez écrire un programme qui affiche "hello!" à l'écran, et que vous n'êtes pas complètement embrouillé par les pointeurs et les structures, je ne pense pas que vous ayez de problèmes en lisant ce manuel. Tous les exemples ont étés écrits avec autant de clarté possible et j'ai évité plusieurs raccourcis afin de les rendre plus facilement compréhensibles. Néanmoins, avant de commencer le vrai manuel, je pense qu'il est préférable de rappeler d'importantes particularités du C. 0.3.1 Fichier d'en-tête #Include Les premières choses que vous trouverez dans un programme C sont les commandes "#include". Elles indiquent au compilateur qu'il faut lire certains fichiers (fichiers d'en-tête) avant de compiler le code et peuvent aussi contenir des définitions de constantes/structures/macros, etc. Les fichiers sont normalement référencés comme "fichier d'en-tête" ("header files") et ont pour cette raison l'extension ".h". Il y a deux types de fichiers d'en-tête ; les vôtres et les définitions standard du système. Si vous incluez vos propres fichiers d'en-tête, mettez le nom du fichier entre guillemets :
Le compilateur va alors chercher votre fichier dans le "répertoire courant". Si au contraire vous incluez des définitions standard du système, mettez le nom du fichier entre des signes de comparaison, le compilateur les cherchera alors dans l'unité logique "INCLUDE:" :
Notez qu'il n'y a pas de point-virgule après les #include ! C'est parce que les fichiers d'en-tête sont analysés par un macro préprocesseur (compris dans la première phase de compilation, lc1) et n'ont en fait rien à voir avec le compilateur C. Plus loin dans le manuel nous inclurons souvent le fichier intuition.h qui contient les définitions de plusieurs structures (telles Window, Border, Gadget, etc.) et quelques constantes et macros importantes. Celui-ci et tous les autres fichiers en-têtes standard du système sont compressés sur la deuxième disquette du Lattice C. Si vous voulez voir ce qu'ils définissent vraiment, vous pourrez trouver les fichiers non compressés sur la quatrième disquette du Lattice C. 0.3.2 #Define Après les #include viennent les #define. La commande #define vous permet non seulement déclarer les constantes et les chaînes de remplacement, mais aussi de déclarer vos propres macros. Il faut se souvenir que la première chaîne définie est remplacée par la deuxième par le macro préprocesseur. Le premier espace sépare la première chaîne de la deuxième. Notez le sous-tiret entre MAX et WIDTH, et les guillemets à Anders Bjerin :
Le macro préprocesseur remplacera toutes les chaînes MAX_WITH par 150, et toutes les chaînes AUTHOR par "Anders Bjerin". S'il y a des parenthèses dans la première chaîne, alors vous déclarez une macro :
La première chaîne est remplacée normalement par la deuxième mais tous les éléments (séparés par des virgules s'il y en a plusieurs) entre parenthèses sont recopiés dans la nouvelle formule. Une telle ligne :
...sera remplacé comme suit :
Il est donc important de se rappeler que le préprocesseur considère tout comme des chaînes et une telle ligne :
...sera remplacé comme suit :
Le carré (square) de 4+1 est 25 (5*5) mais nr est égal à 9. Ce qui prouve que vous devez être très prudent quand vous définissez vos macros. Pour résoudre ce problème, vous n'avez qu'à rajouter des parenthèses autour des x :
...sera alors remplacé comme suit :
Même ainsi, la macro n'est pas encore parfaitement définie. Considérez cette expression :
Elle sera alors remplacée comme suit :
16 divisé par 2 au carré donne 16/4, qui est égal à 4 mais nr est égal à 16 (16/2*2). La solution à tous ces problèmes est de mettre des parenthèses autour de chaque élément, plus des parenthèses autour de l'expression elle-même :
...sera alors remplacé comme suit :
Dans les macros, vous devez penser à :
0.3.3 Les autres commandes du préprocesseur #include et #define sont les deux commandes du préprocesseur les plus couramment utilisées, mais il en existe cinq autres qui peuvent être utilisées conjointement à la commande #define :
Ces commandes sont très pratiques quand vous #incluez plusieurs fichiers, et que vous voulez vérifier que quelque chose a été défini. Si ça n'est pas le cas, vous pouvez inclure d'autres fichiers et ainsi de suite...
La dernière commande du préprocesseur, #undef, annule la dernière définition :
0.3.4 Les fonctions Les compilateurs C s'attendent généralement à trouver les définitions des fonctions (quel que soit le type de donnée que la fonction retourne) avant qu'il n'y soit fait référence. Comme la fonction main() est la première et qu'elle contient probablement des références à d'autres fonctions situées plus loin, nous devons déclarer les fonctions avant la fonction main(). Quand une fonction est définie avant d'être déclarée, il faut mettre un point-virgule après. Le compilateur comprend alors que nous avons seulement défini une fonction et que nous la déclarerons par la suite :
0.3.5 Les variables Voici les types de données standard du Lattice C :
Attention, ces types de données peuvent être différents sur d'autres C, méfiez-vous quand vous les utilisez. Pour être sûr que la taille des variables soit la même sur différents systèmes, il y a une liste spéciale de définitions de variable. Vous n'avez qu'à inclure le fichier "exec/types.h" et vous avez une liste fiable de types de variables. Le fichier définit, entre autres choses, ces types de données couramment utilisés :
Voici une liste de types de données qui ne doivent plus être utilisés :
Voyez le fichier "exec/types.h" de la quatrième disquette Lattice C pour plus d'informations. Ce fichier est automatiquement inclus quand vous incluez le fichier d'en-tête "intuition.h". 0.3.6 Les classes d'allocation des variables Il y a six types principaux de classes d'allocation des variables :
Les variables Automatic sont déclarées dans les fonctions, précédées du mot-clef "auto". Les variables qui sont déclarées dans une fonction sans être précédées d'un mot-clef sont considérées comme des variables Automatic. De la place sur la pile leur est réservée et elles ne peuvent être utilisées que par la fonction dans laquelle elles ont été déclarées. Quand la fonction prend fin, la mémoire est automatiquement restituée à la pile. Voici un petit programme récursif. L'utilisateur peut entrer autant de valeurs qu'il veut jusqu'à ce qu'il entre 0 et alors tous les nombres sont affichés dans l'ordre inverse.
0.3.6.2 Formal Très ressemblant aux variables Automatic. De la mémoire est réservée dans la pile, elles ne peuvent être utilisées que par la fonction dans laquelle elles ont été déclarées, la mémoire est automatiquement restituée à la pile quand la fonction prend fin. La seule différence est qu'elles sont les "paramètres" de la fonction et sont déclarées après les parenthèses mais avant les accolades.
0.3.6.3 Global Les variables Global sont déclarées en dehors des fonctions et sont donc accessibles de n'importe quel endroit du programme. De la mémoire est réservée dans les segments ("hunks") BSS ou DATA et ne peut pas être désallouée. Toutes les données non initialisées sont mises dans les segments BSS, tandis que les données initialisées sont mises dans les segments DATA.
Les informations sur les variables Global sont mises dans le fichier objet afin que les autres modules puissent accéder aux variables. Si une variable est déclarée comme variable Global (déclarée en dehors des fonctions) dans le module A, la variable doit être déclarée comme une variable External Global (déclarée en dehors des fonctions et précédée du mot-clef "extern") dans le module B. Module A
Module B
Pour compiler ces deux modules et les lier, vous n'avez qu'à :
0.3.6.4 External Static Comme les variables Global, mais ne peuvent être utilisées qu'à l'intérieur du même module. Toutes les fonctions de ce module auront accès à ces variables mais aucune fonction d'un autre module ne peut y accéder. La mémoire est réservée dans les segments BSS ou DATA mais aucune information sur ces variables n'est placée dans le fichier objet. Pour déclarer une variable External Static vous n'avez qu'à ajouter le mot-clef "static" avant un nom de variable Global.
0.3.6.5 Internal Static Comme les variables External Static sauf qu'elles sont déclarées dans les fonctions et ne peuvent être utilisées que par ces fonctions. La particularité des variables Internal Static est que la mémoire n'est pas désallouée quand la fonction prend fin et les valeurs mémorisées ne sont pas modifiées. La mémoire est réservée dans les segments BSS ou DATA. Une variable Internal Static n'est initialisée qu'une fois et peut par la suite être modifiée mais pas désallouée.
0.3.6.6 Register Les variables Register sont comme les variables Automatic sauf que vous demandez au compilateur si la variable peut être autorisée à utiliser un des registres du processeur. C'est très pratique si vous utilisez beaucoup une variable, dans une boucle par exemple. Néanmoins, il n'est pas certain que vous pourrez réserver un registre. Si vous avez de la chance le compilateur arrivera à réserver un registre pour la variable mais ça n'est pas toujours possible. La variable Register sera alors transformée en une variable Automatic normale.
0.3.7 Les pointeurs Les pointeurs sont déclarés comme les variables normales avec cependant une différence importante. Nous ajoutons un signe "*" devant le nom :
Si nous voulons obtenir l'adresse d'une variable nous ajoutons un "&" devant le nom de la variable. "a" contient une valeur Integer tandis que "&a" est l'adresse mémoire de cette variable. Si nous voulons que le pointeur "b" pointe sur la variable "a", il suffit d'écrire :
Il faut se rappeler que "b" est un pointer de variable ("b" peut pointer sur n'importe quelle variable Integer), tandis que "&a" est le pointeur constant ("&a" sera toujours l'adresse de "a"). 0.3.8 Les structures Je n'essayerai pas de tout expliquer sur les structures, mais je vais quand même vous donner un bref aperçu de la façon de les utiliser. 0.3.8.1 Comment déclarer une structure Vous déclarez un type structure comme suit :
Vous pouvez mettre vos déclarations de structures d'usage courant dans un fichier d'en-tête et l'inclure quand vous en aurez besoin. Quand nous programmerons l'Amiga nous utiliserons de nombreuses structures déclarées qui sont principalement dans le fichier d'en-tête "intuition.h". 0.3.8.2 Comment modifier les champs d'une structure Quand vous avez déclaré un type structure, vous pouvez déclarer des variables et des pointeurs structure. Pour déclarer une variable structure appelée "anders" et un pointeur structure appelé "programmeur" vous écrivez :
Vous pouvez maintenant initialiser la variable structure "anders". Pour ce faire, écrivez le nom de la variable structure, suivi d'un point, et enfin écrivez à quel champ vous voulez accéder. Ainsi, si vous voulez mettre l'âge dans la variable structure "anders" à 20, la taille à 1.92, et le poids à 74.5, écrivez :
0.3.8.3 Pointeurs et structures Si vous voulez initialiser le pointeur "programmeur" pour qu'il pointe sur la structure "anders", écrivez :
Attention : "anders" est la variable structure. "&anders" est l'adresse de la variable structure. Quand le pointeur pointe sur la structure, vous pouvez commencer à contrôler et modifier les paramètres de cette structure. Pensez à ne pas mettre un point entre le nom du pointeur et celui du champ mais un signe "->", ceci afin que le pointeur "programmeur" pointe sur le champ "taille": programmeur -> taille, etc.
0.3.9 Les conversions Si vous voulez convertir un type de donnée en un autre, il faut effectuer une conversion ("casting"). Pour convertir une variable WORD en une variable LONG vous devez écrire :
La commande (LONG) indique au compilateur C que le prochain type de donnée doit être converti en une variable de type LONG. Quand vous convertissez différents types de variable, vous n'avez normalement pas besoin de faire une conversion. Dans le cas précédent, vous auriez pu supprimer la commande (LONG) mais comme ça confirme à tout lecteur qu'il n'y à pas d'erreur dans le code, il est préférable de la laisser. Cependant, quand vous convertissez différents types de pointeurs, vous devez utiliser la méthode de conversion. Comme les erreurs les plus courantes en C viennent des pointeurs, les compilateurs C sont souvent très rigoureux quant à savoir quel pointeur peut pointer sur quelles variables/structures, etc. Si vous avez déclaré un pointeur comme un pointeur mémoire normal CPTR (programmeur) et vous voulez qu'il pointe sur une structure (personne), le compilateur va être embrouillé.
Un pointeur mémoire et un pointeur sur une structure sont en fait identiques mais ce paranoïaque de compilateur refuse de l'admettre et nous envoie un avertissement du genre :
Pour indiquer au compilateur que les deux différents types d'adresses mémoire sont en fait les mêmes, vous devez faire une conversion. Vous devez indiquer au compilateur que le pointeur sur la structure anders (&anders) est en fait un pointeur mémoire (CPTR).
0.4 Les bibliothèques Plusieurs fonctions spéciales ont été déjà écrites pour vous et placées dans différentes "bibliothèques". Si vous voulez accéder à l'une de ces fonctions, vous n'avez qu'à "ouvrir" la bonne bibliothèque. Vous ouvrez une bibliothèque en appelant la fonction OpenLibrary() et elle retourne un pointeur sur la structure bibliothèque. Il y a deux types de bibliothèques, les bibliothèques ROM qui sont placées en ROM (étonnant, non ?) et les bibliothèques sur disquette qui sont placées sur la disquette de démarrage ("System Disk"). 0.4.1 Les bibliothèques ROM Les bibliothèques ROM sont toujours accessibles (elles sont dans la ROM) mais doivent quand même être ouvertes avant que vous n'utilisiez leurs fonctions. Voici la liste des bibliothèques ROM :
Si un programme ouvre une bibliothèque sur disquette, AmigaDOS va la chercher dans l'unité logique "LIBS:", qui a été assignée au répertoire "libs" de la disquette système et va alors charger toute la bibliothèque en RAM. Si la bibliothèque a déjà été chargée en RAM par une autre tâche, Exec utilisera alors la bibliothèque existante. Cela prouve l'efficacité des bibliothèques en mémoire puisque plusieurs programmes peuvent utiliser le même code. Voici la liste des bibliothèques sur disquette :
Comme vu précédemment, vous ouvrez une bibliothèque en appelant la fonction OpenLibrary(). Elle retourne un pointeur sur une structure bibliothèque et ce pointeur doit être stocké dans une variable pointeur avec un nom spécial. Si vous ouvrez par exemple la bibliothèque Intuition, le pointeur doit être appelé "IntuitionBase" et si vous ouvrez la bibliothèque Graphics, le pointeur doit être appelé "GfxBase".
Voici une autre partie de programme qui cette fois ouvre et ferme la bibliothèque Graphics :
0.5 La mémoire L'Amiga 1000 peut être étendu jusqu'à un total de 9 Mo de mémoire. Cette mémoire peut être divisée en trois types différents : Mémoire Chip : les 512 premiers ko sont normalement appelés "mémoire Chip", et le processeur, les coprocesseurs et les autres puces de l'Amiga peuvent y accéder. Tous les graphismes/sons, etc. qui sont utilisés par les puces (Blitter, etc.) doivent s'y trouver ! Mémoire Slow : les 512 ko suivants (la mémoire supplémentaire qui se trouve dans l'Amiga) auxquels seul le processeur peut avoir accès et qui ne peuvent donc pas être utilisés pour stocker des données graphiques et sonores. Ils sont appelés "Slow" car bien que les puces n'y aient pas accès, ils peuvent y faire des interruptions. Si vous avez un écran en haute résolution et/ou avec beaucoup de couleurs, cette mémoire sera ralentie car les puces lui voleront des cycles mémoire. Il y a un nouveau jeu de puces qui permet aux coprocesseurs et aux puces d'utiliser aussi cette mémoire, ce qui signifie qu'elle peut alors contenir des données graphiques/sonores, etc. Néanmoins, les Amiga OCS ont encore les anciennes puces, et cette mémoire ne peut pas être utilisée pour les données graphiques/sonores. Mémoire Fast : le troisième type de mémoire est constitué des 8 Mo suivants, qui ne sont accessibles qu'au processeur principal et ne sont pas ralentis par les puces. Cette mémoire s'appelle donc "mémoire Fast". Il faut se rappeler que toutes les données graphiques et sonores (toutes les données utilisées par les puces) doivent être stockées en mémoire Chip ! Il y a trois façons de le faire :
|