Obligement - L'Amiga au maximum

Jeudi 25 avril 2024 - 10:15  

Translate

En De Nl Nl
Es Pt It Nl


Rubriques

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

Articles in english


Réseaux sociaux

Suivez-nous sur X




Liste des 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,
ALL


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


Galeries

Menu des galeries

BD d'Amiga Spécial
Caricatures Dudai
Caricatures Jet d'ail
Diagrammes de Jay Miner
Images insolites
Fin de jeux (de A à E)
Fin de Jeux (de F à O)
Fin de jeux (de P à Z)
Galerie de Mike Dafunk
Logos d'Obligement
Pubs pour matériels
Systèmes d'exploitation
Trombinoscope Alchimie 7
Vidéos


Téléchargement

Documents
Jeux
Logiciels
Magazines
Divers


Liens

Associations
Jeux
Logiciels
Matériel
Magazines et médias
Pages personnelles
Réparateurs
Revendeurs
Scène démo
Sites de téléchargement
Divers


Partenaires

Annuaire Amiga

Amedia Computer

Relec


A Propos

A propos d'Obligement

A Propos


Contact

David Brunet

Courriel

 


Programmation : C - interfacer le C et l'assembleur (1re partie)
(Article écrit par Cédric Beust et extrait d'A-News (Amiga News) - janvier 1990)


Loin de moi l'idée de raviver l'éternelle querelle qui oppose les inconditionnels de l'un et l'autre. Au contraire, j'espère montrer dans les lignes qui suivent qu'il est non seulement possible de les interfacer simplement sur l'Amiga, mais que cela peut apporter un gain en efficacité non négligeable.

Les protagonistes

Quelques précisions sur les produits dont je parle tout d'abord. J'utilise le Lattice 5.02 et Devpac 2. Ceci dit, n'importe quel compilateur C devrait faire l'affaire car l'appel des fonctions dans ce langage se fait de manière standard. Si votre compilateur ne les respecte pas, c'est que c'est un mauvais compilateur et il est urgent d'en changer ! En ce qui concerne l'assembleur, vous pouvez également utiliser le MetaComCo ou n'importe quel autre qui dispose des directives XREF et XDEF. Une option pour nommer le module courant ne serait pas non plus inutile, mais j'en reparlerai plus tard.

L'intérêt

Quel peut être l'intérêt d'utiliser simultanément les deux langages ? Il y pour cela plusieurs raisons. Chacun a des avantages et des inconvénients.

Pour le C
  • Avantages : rapidité de mise au point, entrées/sorties faciles avec printf, putc, etc.
  • Inconvénients : oblige parfois à des initialisations lourdes pour respecter le multitâche.
Pour l'assembleur
  • Avantages : rapidité d'exécution, accès facile aux registres système.
  • Inconvénients : relecture difficile, bogues nombreux à l'écriture, mise au point fastidieuse.
Ceci est très loin d'être une liste exhaustive et les différences ne sont en fait pas aussi marquées que je le laisse apparaître dans ce tableau, mais cela va me servir à illustrer mon propos. L'intérêt est donc de ne conserver de chaque langage le côté qui lui est agréable, en laissant de côté les inconvénients.

Fixons-nous un but à atteindre afin que tout cela ne soit pas trop théorique. Il vous est sûrement arrivé d'avoir à écrire une petite routine qui a besoin d'afficher du texte dans la fenêtre courante. L'écrire en C semble être une bonne idée mais la seule utilisation de "printf" ou "putc" ajoute facilement 2 ko au code produit. Il est assez irritant d'avoir un exécutable de 5 ko alors que les opérations que vous faites ne devraient en théorie pas dépasser 1 ko (exemple : la routine "avail.c" livrée avec le Lattice).

Il serait donc intéressant de disposer d'une routine d'impression qui serait très simple (impression de chaînes pour commencer) et qui prendrait très peu de place. Je vous propose donc de créer votre propre bibliothèque C (un .lib) qui contiendrait votre routine d'impression sur écran, la routine en question n'excéderait pas 150 octets... J'irai même plus loin en vous proposant une nouvelle routine "sprintf" qui vous permettra de faire des sorties formatées (c'est-à-dire avec %d, %s, etc.) pour une taille du même ordre. Par la même occasion, nous apprendrons à nous passer du startup c.o. Alléchant, n'est-ce pas ?

Assez bavardé, nous avons du pain sur la planche ! Quelques notions sont indispensables pour commencer.

Généralités

Comment le C passe-t-il ses paramètres ? Il utilise pour ce faire une méthode universelle à tous les compilateurs : le passage par la pile. Lorsque le compilateur rencontre une instruction :

fonction(a,b,c,d);

...il pousse sur la pile d, c, b et enfin a, après quoi, il fait un saut à la routine "_fonction" et dès son retour, il rétablit la pile en l'incrémentant de la taille des paramètres passés. Le compilateur pourrait donc produire un code comme celui-ci :

C et assembleur

Pourquoi 16 ? Parce que j'ai supposé que chaque paramètre était représenté sur quatre octets. Ce n'est nullement indispensable (il faut malgré tout que la taille soit un nombre pair d'octets) mais c'est beaucoup plus simple pour les calculs.

Plaçons-nous maintenant à la place de la fonction appelée. Comment peut-elle récupérer ses paramètres ? C'est très simple, il suffit d'aller les chercher au bon endroit sur la pile. Mais n'oubliez pas qu'une nouvelle adresse s'est empilée après l'appel : celle du retour. Le premier paramètre ne se trouve donc pas en 0(a7) mais en 4(a7). Reprenons l'exemple précédent : la routine "_fonction" agirait probablement de la façon suivante :

C et assembleur

En fait, ce modèle n'est pas tout à fait exact. Il faut absolument garder intacts les registres d2-d7 et a2-a6 (d0, d1, a0 et a1 peuvent être utilisés et détruits). En toute rigueur, il faudait sauver les registres que nous utilisons sur la pile, et décaler donc les accès d'autant. Dans cet exemple, nous aurions à sauvegarder d2 et d3, soit une taille de 8 octets :

C et assembleur
C et assembleur

Je laisse de côté l'instruction "link" qui est utilisée pour créer des variables locales, et ajoute un nouveau décalage dans l'adressage.

Comprenez-vous pourquoi les paramètres sont poussés dans l'ordre inverse d'apparition ? L'explication est simple : le C autorise à ses fonctions d'avoir un nombre variable de paramètres. Songez à "printf" : le nombre de paramètres est directement lié à la chaîne de formatage. Par exemple, une chaîne "Résultat: %d = %d" imposera clairement un "printf" à trois paramètres. Si la chaîne était poussée la première, la fonction appelée serait incapable de la retrouver dans la pile.

Exemple : soit l'instruction printf("%d %d",a,b). Voyons comment se présentent les piles en utilisant un mode de passage différent :

C et assembleur

Dans le premier exemple, comment la fonction saura-t-elle que la chaîne de formatage se trouve en 12(a7) ? En utilisant la deuxième méthode, elle sait avec certitude qu'elle doit regarder en premier lieu en 4(a7). Puis elle en déduira que deux paramètres suivent et ira donc les chercher en 8(a7) et 12(a7) (elle connaît naturellement leur taille).

Finissons cette introduction en regardant le problème inverse : comment appeler une fonction C à partir de l'assembleur ? Voici comment procéder :

1. Définir à l'aide de XREF les fonctions C à appeler, et que l'assembleur doit donc ignorer. C'est la tâche de l'éditeur de liens de la trouver.

2. Respecter les conventions d'appel décrites ci-dessus.

3. Ne pas oublier de lier avec la bibliothèque qui contient les fonctions utilisées.

Un petit exemple : utilisation de printf (encore lui !) :

C et assembleur
C et assembleur

Pour faire marcher cet exemple, utiliser "blink" de la façon suivante :

blink lib:c.o,a.o to a lib lib:lc.lib

Pas de problèmes, ça marche mais vous avez vu la taille ? Environ 6000 octets. Ça fait beaucoup pour une routine d'affichage, vous ne trouvez pas ? Ceci est dû au fait que "printf" est une fonction très complexe qui autorise des formatages très divers. De plus, elle fait appel à des symboles définis dans c.o (d'où l'obligation d'utiliser le nom "_main") et des routines autres que "printf" dans lc.lib.


[Retour en haut] / [Retour aux articles] [Article suivant]