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 - Insérer des routines assembleur
(Article écrit par Denis Jarril et extrait d'Amiga News Tech - juin 1991)
|
|
Lorsque l'on a en projet de développer un gros programme en C, il est souvent utile d'y ajouter quelques
routines en assembleur, histoire de gagner du temps et/ou de la place, ou bien tout simplement parce qu'on
ne peut pas faire autrement.
Ce cas est de loin le plus facile, l'interface avec l'assembleur étant tout naturel en langage C. A
peine aura-t-on besoin de se souvenir de quelle manière les paramètres sont transmis (ce qui a d'ailleurs
déjà été expliqué dans le précédent numéro de l'ANT,
qui traitait alors de
l'utilisation de l'amiga.lib en assembleur). Le cas inverse (à savoir : appeler des routines écrites en C
ou accéder à des données définies dans ce langage) n'est pas beaucoup plus difficile, pour peu que l'on
prenne la peine de s'y intéresser.
Bref rappel
A l'attention de ceux qui ne nous rejoindraient qu'à partir de ce numéro, un bref mais court rappel de ce
qu'il faut faire pour appeler une routine assembleur depuis le C ne mangera pas de pain.
Par définition, le langage C passe tous les paramètres des fonctions par la pile, empilés en ordre inverse
de leur déclaration. Pour un "long" ou un pointeur, un mot long (32 bits) est empilé, tandis que pour un
"int" (16 bits, certains préfèrent dire "short" ou "word"), un "char" (8 bits), c'est un mot qui est empilé.
Quant à la valeur de retour, s'il y a en une, elle est contenue dans le registre d0 au sortir de la fonction.
Quand on sait ça, la suite n'est plus qu'un jeu d'enfant.
Voici tout de même un petit exemple, comme ça, pour la forme.
En C :
Et en assembleur :
Quelques remarques qui n'ont pas été faites précédemment. Côté C, la fonction à appeler doit être déclarée
"extern" pour que l'éditeur de liens sache qu'il la trouvera ailleurs que dans le programme compilé. D'autre part,
il est de bon ton de lui déclarer un prototype, histoire d'être sûr de ne pas se gourrer dans les paramètres...
Côté assembleur, plusieurs petites choses sont à prendre en compte. D'abord, le source doit évidemment être
assemblé en objet et non en exécutable (n'importe quel macro-assembleur digne de ce nom fait cela très bien),
ça tombe sous le sens.
De plus, le point d'entrée de la fonction doit être "exporté" grâce à la directive XDEF, sinon l'éditeur de liens
se plaindra de ne pas trouver ce qu'il cherche. Ensuite, à cause d'une convention du C, son nom doit être
précédé du caractère souligné "_". Enfin, c'est la fonction appelante qui a la charge de restaurer la pile,
et non la fonction appelée, comme je l'ai encore vu récemment dans un magazine que je ne citerai pas mais
qui parle beaucoup des 16/32 bits...
Permettez-moi d'ouvrir ici une parenthèse utile : il est tout à fait possible qu'une même routine assembleur
soit appelée à la fois par le C et par une autre routine assembleur. Que faire dans ce cas ? Il
serait en effet superflu - et en tout bien ennuyeux - de devoir empiler les paramètres et réajuster soi-même
la pile alors que les registres du processeur sont tout de même faits pour ça ! La petite "astuce"
(et encore !) suivante y remédie, en utilisant justement le coup du souligné :
Ainsi, cette routine pourra être appelé depuis le C par et depuis l'assembleur par :
Et depuis l'assembleur par :
Sympathique, non ?
Du C dans l'assembleur
Voyons maintenant le cas inverse. Il n'est pas rare qu'une routine assembleur appelée par une fonction C
ait à son tour besoin d'appeler une autre fonction C et/ou d'accéder à des données globales du programme, elles-mêmes
définies en C... Ce qui, encore une fois, n'est pas si terrible que ça en a l'air.
Il faut évidemment se rappeler la convention du passage des paramètres, mais également ne pas oublier que la pile
doit être restaurée dans son état initial. Vite, vite, un exemple.
En C :
Et en assembleur :
Jusque-là, rien de vraiment compliqué : on s'est contenté d'utiliser la directive XREF pour indiquer à
l'assembleur que le label "_RoutineC" (sans oublier le souligné) se trouve dans un autre module, extérieur
à celui qu'il traite. Enfin, pour appeler la fonction, on a empilé le paramètre (32 bits), sauté avec
JSR puis additionné à SP le nombre d'octets empilé, c'est-à-dire quatre. Enfantin.
Accéder aux données globales du programme C est tout aussi simple, bien qu'il faille différencier deux cas :
- Le programme est compilé en modèle SMALL.
- Le programme est compilé en modèle LARGE.
Dans le premier cas (SMALL), les données globales sont référencées par l'intermédiaire du registre A4
(et seulement par lui !). On aura par exemple :
Ceci limite les données à 32 ko - à cause de la manière dont le 68000 code ce mode d'adressage - mais
produit un code plus compact et plus rapide.
Dans le second cas (LARGE), les données globales sont adressées directement par leur adresse effective :
Là, on n'est plus limité (?) qu'à 16 Mo de données, mais le code produit est plus gros et moins rapide.
C'est une question de choix.
Vérifiez donc dans le manuel de votre compilateur quel type de code il produit (normalement, il doit savoir
faire les deux !), mais si vous me permettez un conseil, utilisez toujours le modèle SMALL 32 ko
de données, c'est déjà énorme ! Et si d'aventure vous en aviez besoin de plus, allouez-les
dynamiquement avec AllocMem(), c'est beaucoup plus rentable !
|