Vous êtes sur la page 1sur 204

Langage Assembleur PC

Paul A. Carter
20 mars 2005

c 2001, 2002, 2004 by Paul Carter


Copyright

Traduction par Sbastien Le Ray

Ce document peut tre reproduit et distribu dans sa totalit (en incluant


cette note de l'auteur, le copyright et l'indication de l'autorisation), sous
rserve qu'aucun frais ne soit demand pour le document lui-mme, sans le
consentement de l'auteur. Cela inclut une utilisation raisonnable d'extraits
pour des revues ou de la publicit et les travaux drivs comme les traductions.
Notez que ces restrictions ne sont pas destins empcher la participation
aux frais pour l'impression ou la copie du document.
Les enseignants sont encourags utiliser ce document comme support de
cours ; cependant, l'auteur aprcierait d'tre averti dans ce cas.

Table des matires


Prface

1 Introduction

1.1

1.2

1.3

1.4

Systmes Numriques

. . . . . . . . . . . . . . . . . . . . . .

1.1.1

Dcimal . . . . . . . . . . . . . . . . . . . . . . . . . .

1.1.2

Binaire

1.1.3

Hexadecimal

. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . .

Organisation de l'Ordinateur

. . . . . . . . . . . . . . . . . .

1.2.1

Mmoire . . . . . . . . . . . . . . . . . . . . . . . . . .

1.2.2

Le CPU (processeur) . . . . . . . . . . . . . . . . . . .

1.2.3

La famille des processeurs 80x86

. . . . . . . . . . . .

1.2.4

Regitres 16 bits du 8086 . . . . . . . . . . . . . . . . .

1.2.5

Registres 32 bits du 80386 . . . . . . . . . . . . . . . .

1.2.6

Mode Rel

1.2.7

Mode Protg 16 bits

. . . . . . . . . . . . . . . . . .

1.2.8

Mode Protg 32 bits

. . . . . . . . . . . . . . . . . .

10

1.2.9

Interruptions

. . . . . . . . . . . . . . . . . . . . . . .

11

Langage Assembleur

. . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . .

11

1.3.1

Langage Machine . . . . . . . . . . . . . . . . . . . . .

11

1.3.2

Langage d'Assembleur . . . . . . . . . . . . . . . . . .

12

1.3.3

Oprandes d'Instruction . . . . . . . . . . . . . . . . .

13

1.3.4

Instructions de base

. . . . . . . . . . . . . . . . . . .

13

1.3.5

Directives . . . . . . . . . . . . . . . . . . . . . . . . .

14

1.3.6

Entres et Sorties . . . . . . . . . . . . . . . . . . . . .

17

1.3.7

Dbogage

. . . . . . . . . . . . . . . . . . . . . . . . .

18

Crer un Programme . . . . . . . . . . . . . . . . . . . . . . .

19

1.4.1

Premier programme

. . . . . . . . . . . . . . . . . . .

19

1.4.2

Dpendance vis vis du compilareur . . . . . . . . . .

23

1.4.3

Assembler le code . . . . . . . . . . . . . . . . . . . . .

24

1.4.4

Compiler le code C . . . . . . . . . . . . . . . . . . . .

24

1.4.5

Lier les chiers objets

24

1.4.6

Comprendre un listing assembleur


i

. . . . . . . . . . . . . . . . . .
. . . . . . . . . . .

25

ii

TABLE DES MATIRES

1.5

Fichier Squelette

. . . . . . . . . . . . . . . . . . . . . . . . .

2 Bases du Langage Assembleur


2.1

2.2

2.3

2.4

Travailler avec les Entiers

29

. . . . . . . . . . . . . . . . . . . .

29

2.1.1

Reprsentation des entiers . . . . . . . . . . . . . . . .

29

2.1.2

Extension de signe

32

2.1.3

Arithmtique en complment deux

2.1.4

Programme exemple

2.1.5

Arithmtique en prcision tendue

. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . .

35

. . . . . . . . . . . . . . . . . . .

37

. . . . . . . . . . .

39

Structures de Contrle . . . . . . . . . . . . . . . . . . . . . .

40

2.2.1

Comparaison

. . . . . . . . . . . . . . . . . . . . . . .

40

2.2.2

Instructions de branchement . . . . . . . . . . . . . . .

41

2.2.3

Les instructions de boucle . . . . . . . . . . . . . . . .

44

Traduire les Structures de Contrle Standards . . . . . . . . .

44

2.3.1

Instructions if . . . . . . . . . . . . . . . . . . . . . . .

44

2.3.2

Boucles while . . . . . . . . . . . . . . . . . . . . . . .

45

2.3.3

Boucles do while

45

. . . . . . . . . . . . . . . . . . . . .

Exemple : Trouver des Nombres Premiers

. . . . . . . . . . .

3 Oprations sur les Bits


3.1

3.2

26

46

49
. . . . . . . . . . . . . . . . . . . . .

49

. . . . . . . . . . . . . . . . . . . .

49

3.1.2

Utilisation des dcalages . . . . . . . . . . . . . . . . .

50

3.1.3

Dcalages arithmtiques . . . . . . . . . . . . . . . . .

50

3.1.4

Dcalages circulaires . . . . . . . . . . . . . . . . . . .

51

3.1.5

Application simple . . . . . . . . . . . . . . . . . . . .

51

Oprations de Dcalage
3.1.1

Dcalages logiques

Oprations Boolennes Niveau Bit

. . . . . . . . . . . . . . .

52

. . . . . . . . . . . . . . . . . . . . .

52

. . . . . . . . . . . . . . . . . . . . .

53

. . . . . . . . . . . . . . . . . . . . .

53

. . . . . . . . . . . . . . . . . . . . .

53

3.2.4

ET .
L'opration OU .
L'opration XOR
L'opration NOT

3.2.5

L'instruction

. . . . . . . . . . . . . . . . . . . .

54

3.2.6

Utilisation des oprations sur les bits . . . . . . . . . .

54

3.3

Eviter les Branchements Conditionnels . . . . . . . . . . . . .

55

3.4

Manipuler les bits en C

58

3.2.1
3.2.2
3.2.3

L'opration

TEST

. . . . . . . . . . . . . . . . . . . . .

3.4.1

Les oprateurs niveau bit du C

3.4.2

Utiliser les oprateurs niveau bit en C

. . . . . . . . . . . . .

58

. . . . . . . . .

59

3.5

Reprsentations Big et Little Endian . . . . . . . . . . . . . .

60

3.5.1

61

3.6

Compter les Bits

. . . . . . . . . . . . . . . . . . . . . . . . .

63

Quand se Soucier du Caractre Big ou Little Endian

3.6.1

Mthode une

. . . . . . . . . . . . . . . . . . . . . . .

63

3.6.2

Mthode deux . . . . . . . . . . . . . . . . . . . . . . .

64

3.6.3

Mthode trois . . . . . . . . . . . . . . . . . . . . . . .

66

iii

TABLE DES MATIRES

4 Sous-Programmes

69

4.1

Adressage Indirect

. . . . . . . . . . . . . . . . . . . . . . . .

69

4.2

Exemple de Sous-Programme Simple . . . . . . . . . . . . . .

70

4.3

La pile . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

72

4.4

Les Instructions CALL et RET

. . . . . . . . . . . . . . . . .

73

4.5

Conventions d'Appel . . . . . . . . . . . . . . . . . . . . . . .

74

4.5.1

Passer les paramtres via la pile . . . . . . . . . . . . .

74

4.5.2

Variables locales sur la pile

. . . . . . . . . . . . . . .

80

. . . . . . . . . . . . . . . . . . .

82

4.6

Programme Multi-Modules

4.7

Interfacer de l'assembleur avec du C

4.8

. . . . . . . . . . . . . .

85

4.7.1

Sauvegarder les registres . . . . . . . . . . . . . . . . .

86

4.7.2

Etiquettes de fonctions . . . . . . . . . . . . . . . . . .

86

4.7.3

Passer des paramtres

87

4.7.4

Calculer les adresses des variables locales

4.7.5

Retourner des valeurs

4.7.6

Autres conventions d'appel

4.7.7

Exemples

4.7.8

. . . . . . . . . . . . . . . . . .
. . . . . . .

87

. . . . . . . . . . . . . . . . . .

88

. . . . . . . . . . . . . . .

88

. . . . . . . . . . . . . . . . . . . . . . . . .

89

Appeler des fonctions C depuis l'assembleur . . . . . .

93

Sous-Programmes Rentrants et Rcursifs

. . . . . . . . . . .

93

4.8.1

Sous-programmes rcursifs . . . . . . . . . . . . . . . .

94

4.8.2

Rvision des types de stockage des variables en C . . .

96

5 Tableaux
5.1

5.2

99

Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . .

99

5.1.1

Dnir des tableaux

99

5.1.2

Accder aux lments de tableaux

5.1.3

Adressage indirect plus avanc

5.1.4

Exemple . . . . . . . . . . . . . . . . . . . . . . . . . . 103

5.1.5

Tableaux Multidimensionnels

. . . . . . . . . . . . . . . . . . .

6.2

. . . . . . . . . . . . . 102
. . . . . . . . . . . . . . 107

Instructions de Tableaux/Chanes . . . . . . . . . . . . . . . . 110


5.2.1

Lire et crire en mmoire

. . . . . . . . . . . . . . . . 111

5.2.2

Le prxe d'instruction

5.2.3

Instructions de comparaison de chanes . . . . . . . . . 114

5.2.4

Les prxes d'instruction

5.2.5

Exemple . . . . . . . . . . . . . . . . . . . . . . . . . . 115

REP

. . . . . . . . . . . . . . . 113

REPx

6 Virgule Flottante
6.1

. . . . . . . . . . . 100

. . . . . . . . . . . . . 114

121

Reprsentation en Virgule Flottante

. . . . . . . . . . . . . . 121

6.1.1

Nombres binaires non entiers

. . . . . . . . . . . . . . 121

6.1.2

Reprsentation en virgule ottante IEEE . . . . . . . . 123

Arithmtique en Virgule Flottante

. . . . . . . . . . . . . . . 126

6.2.1

Addition . . . . . . . . . . . . . . . . . . . . . . . . . . 127

6.2.2

Soustraction . . . . . . . . . . . . . . . . . . . . . . . . 127

iv

TABLE DES MATIRES

6.3

6.2.3

Multiplication et division

6.2.4

Consquences sur la programmation

Le Coprocesseur Arithmtique

. . . . . . . . . . . . . . . . 128

6.3.1

Matriel . . . . . . . . . . . . . . . . . . . . . . . . . . 129

6.3.2

Instructions . . . . . . . . . . . . . . . . . . . . . . . . 130

6.3.3

Exemples

6.3.4

Formule quadratique . . . . . . . . . . . . . . . . . . . 136

6.3.5

Lire un tableau depuis un chier

6.3.6

Renchercher les nombres premiers . . . . . . . . . . . . 141

. . . . . . . . . . . . . . . . . . . . . . . . . 136

7 Structures et C++
7.1

7.2

. . . . . . . . . . 128

. . . . . . . . . . . . . . . . . 129

. . . . . . . . . . . . 139

149

Structures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149
7.1.1

Introduction . . . . . . . . . . . . . . . . . . . . . . . . 149

7.1.2

Alignement en mmoire

7.1.3

Champs de Bits . . . . . . . . . . . . . . . . . . . . . . 153

7.1.4

Utiliser des structures en assembleur . . . . . . . . . . 156

Assembleur et C++

. . . . . . . . . . . . . . . . . 150

. . . . . . . . . . . . . . . . . . . . . . . 157

7.2.1

Surcharge et Dcoration de Noms . . . . . . . . . . . . 157

7.2.2

Rfrences . . . . . . . . . . . . . . . . . . . . . . . . . 161

7.2.3

Fonctions inline . . . . . . . . . . . . . . . . . . . . . . 161

7.2.4

Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . 163

7.2.5

Hritage et Polymorphisme

7.2.6

Autres fonctionnalits C++ . . . . . . . . . . . . . . . 179

A Instructions 80x86

. . . . . . . . . . . . . . . 172

181

A.1

Instructions hors Virgule Flottante . . . . . . . . . . . . . . . 181

A.2

Instruction en Virgule Flottante . . . . . . . . . . . . . . . . . 188

Index

190

Prface
Objectif
L'objectif de ce livre est de permettre au lecteur de mieux comprendre
comment les ordinateurs fonctionnent rellement un niveau plus bas que les
langages de programmation comme Pascal. En ayant une comprhension plus
profonde de la faon dont fonctionnent les ordinateurs, le lecteur peu devenir
plus productif dans le dveloppement de logiciel dans des langages de plus
haut niveau comme le C et le C++. Apprendre programmer en assembleur
est un excellent moyen d'atteindre ce but. Les autres livres d'assembleur
pour PC apprennent toujours programmer le processeur 8086 qu'utilisait
le PC originel de 1981 ! Le processeur 8086 ne supportait que le mode

rel.

Dans ce mode, tout programme peu adresser n'importe quel endroit de la


mmoire ou n'importe quel priphrique de l'ordinateur. Ce mode n'est pas
utilisable pour un systme d'exploitation scuris et multitche. Ce livre
parle plutt de la faon de programmer les processeurs 80386 et plus rcents
en mode

protg

(le mode dans lequel fonctionnent Windows et Linux). Ce

mode supporte les fonctionnalits que les systmes d'exploitation modernes


orent, comme la mmoire virtuelle et la protection mmoire. Il y a plusieurs
raisons d'utiliser le mode protg :
1. Il est plus facile de programmer en mode protg qu'en mode rel 8086
que les autres livres utilisent.
2. Tous les systmes d'exploitation PC modernes fonctionnent en mode
protg.
3. Il y a des logiciels libres disponibles qui fonctionnent dans ce mode.
Le manque de livres sur la programmation en assembleur PC en mode protg
est la principale raison qui a conduit l'auteur crire ce livre.
Comme nous y avons fait allusion ci-dessus, ce dexte utilise des logiciels Libres/Open Source : l'assembleur NASM et le compilateur C/C++
DJGPP. Les deux sont disponibles en tlchargement sur Internet. Ce texte
parle galement de l'utilisation de code assembleur NASM sous Linux et
avec les compilateurs C/C++ de Borland et Microsoft sous Windows. Les
v

vi

PRFACE

exemples pour toutes ces plateformes sont disponibles sur mon site Web :

http://www.drpaulcarter.com/pcasm. Vous devez

tlcharger le code exemple

si vous voulez assembler et excuter la plupart des exemples de ce tutorial.


Soyez conscient que ce texte ne tente pas de couvrir tous les aspects de
la programmation assembleur. L'auteur a essay de couvrir les sujets les plus
importants avec lesquels

tous

les programmeurs devraient tre familiers.

Remerciements
L'auteur voutrait remercier les nombreux programmeurs qui ont contribu au mouvement Libre/Open Source. Tous les programmes et mme ce
livre lui-mme ont t crs avec des logiciels gratuits. L'auteur voudrait
remercier en particulier John S. Fine, Simon Tatham, Julian Hall et les
autres dveloppeurs de l'assembleur NASM sur lequel tous les exemples de ce
livre sont bass ; DJ Delorie pour le dveloppement du compilateur C/C++
DJGPP utilis ; les nombreuses personnes qui ont contribu au compilateur
GNU gcc sur lequel DJGPP est bas ; Donald Knuth et les autres pour avoir

AT X 2 qui ont t utiliss pour


dvelopp les langages composition TEX et L
E
produire le livre ; Richard Stallman (fondateur de la Free Software Foundation), Linus Torvalds (crateur du noyau Linux) et les autres qui ont cr les
logiciels sous-jacents utiliss pour ce travail.
Merci aux personnes suivantes pour leurs corrections :
 John S. Fine
 Marcelo Henrique Pinto de Almeida
 Sam Hopkins
 Nick D'Imperio
 Jeremiah Lawrence
 Ed Beroset
 Jerry Gembarowski
 Ziqiang Peng
 Eno Compton
 Josh I Cates
 Mik Miin
 Luke Wallis
 Gaku Ueda
 Brian Heward
 Chad Gorshing
 F. Gotti
 Bob Wilkinson
 Markus Koegel
 Louis Taber

vii

Ressources sur Internet

Page de l'auteur
Page NASM sur SourceForge
DJGPP
Assembleur Linux
The Art of Assembly
USENET
Documentation Intel

http://www.drpaulcarter.com/
http://sourceforge.net/projects/nasm/
http://www.delorie.com/djgpp
http://www.linuxassembly.org/
http://webster.cs.ucr.edu/
comp.lang.asm.x86
http://developer.intel.com/design/Pentium4/documentation.htm

Ractions
L'auteur accepte toute raction sur ce travailThe author welcomes any
feedback on this work.

E-mail: pacman128@gmail.com
WWW: http://www.drpaulcarter.com/pcasm

viii

PRFACE

Chapitre 1

Introduction
1.1

Systmes Numriques

La mmoire d'un ordinateur est constitue de nombres. Cette mmoire ne


stocke pas ces nombres en dcimal (base 10). Comme cela simplie grandement le matriel, les ordinateurs stockent toutes les informations au format
binaire (base 2). Tout d'abord, revoyons ensemble le systme dcimal.

1.1.1 Dcimal
Les nombres en base 10 sont constitus de 10 chires possibles (0-9).
Chaque chire d'un nombre est associ une puissance de 10 selon sa position
dans le nombre. Par exemple :

234 = 2 102 + 3 101 + 4 100

1.1.2 Binaire
Les nombres en base 2 sont composs de deux chires possibles (0 et
1). Chaque chire est associ une puissance de 2 selon sa position dans le
nombre (un chire binaire isol est appel bit). Par exemple :

110012 = 1 24 + 1 23 + 0 22 + 0 21 + 1 20
= 16 + 8 + 1
= 25
Cet exemple montre comment passer du binaire au dcimal. Le tableau 1.1
montre la reprsentation binaire des premiers nombres.

i.e., des

La gure 1.1 montre comment des chires binaires individuels (


bits) sont additionns. Voici un exemple :
1

CHAPITRE 1.

INTRODUCTION

Decimal

Binary

Decimal

Binary

0000

1000

0001

1001

0010

10

1010

0011

11

1011

0100

12

1100

0101

13

1101

0110

14

1110

0111

15

1111

Tab. 1.1  Dcimaux 0 15 en Binaire

No previous carry

Previous carry

+0

+1

+0

+1

+0

+1

+0

+1

c
Fig. 1.1  Addition binaire (c signie

carry, retenue)

110112
+100012
1011002
Si l'on considre la division dcimale suivante :

1234 10 = 123 r 4
on peut voir que cette division spare le chire le plus droite du nombre
et dcale les autres chires d'une position vers la droite. Diviser par deux
eectue une opration similaire, mais pour les chires binaires du nombre.
Considrons la division binaire suivante

1 :

11012 102 = 1102 r 1


Cette proprit peut tre utilise pour convertir un nombre dcimal en son
quivalent binaire comme le montre la Figure 1.2. Cette mthode trouve le
chire binaire le plus droite en premier, ce chire est appele le

bit le moins

signicatif (lsb, least signicant bit). Le chire le plus gauche est appel
le bit le plus signicatif (msb, most signicant bit). L'unit de base de la
mmoire consiste en un jeu de 8 bits appel octet (byte).
1
L'indice 2 est utilis pour indiquer que le nombre est reprsent en binaire, pas en
dcimal

1.1.

SYSTMES NUMRIQUES

Decimal

Binary

25 2 = 12 r 1 11001 10 = 1100 r 1
12 2 = 6 r 0

1100 10 = 110 r 0

62=3r 0

110 10 = 11 r 0

32=1r 1

11 10 = 1 r 1

12=0r 1

1 10 = 0 r 1

Donc

2510 = 110012

Fig. 1.2  Conversion dcimale

589 16 = 36 r 13
36 16 = 2 r 4
2 16 = 0 r 2

Donc

589 = 24D16

Fig. 1.3 

1.1.3 Hexadecimal
Les nombres hexadcimaux utilisent la base 16. L'hexadcimal (ou

hexa

en abrg) peut tre utilis comme notation pour les nombres binaires. L'hexa
a 16 chires possibles. Cela pose un problme car il n'y a pas de symbole
utiliser pour les chires supplmentaires aprs 9. Par convention, on utilise
des lettres pour les reprsenter. Les 16 chires de l'hexa sont 0-9 puis A, B,
C, D, E et F. Le chire A quivaut 10 en dcimal, B 11, etc. Chaque chire
d'un nombre en hexa est associ une puissance de 16. Par exemple :

2BD16 = 2 162 + 11 161 + 13 160


= 512 + 176 + 13
= 701

Pour convertir depuis le dcimal vers l'hexa, utilisez la mme ide que celle
utilise pour la conversion binaire en divisant par 16. Voyez la Figure 1.3
pour un exemple.

CHAPITRE 1.

INTRODUCTION

La raison pour laquelle l'hexa est utile est que la conversion entre l'hexa et
le binaire est trs simple. Les nombres binaires deviennent grands et incomprhensibles rapidement. L'hexa fournit une faon beaucoup plus compacte
pour reprsenter le binaire.
Pour convertir un nombre hexa en binaire, convertissez simplement chaque
chire hexa en un nombre binaire de 4 bits. Par exemple,
en

0010 0100 11012 .

24D16

est converti

Notez que les 0 de tte des 4 bits sont importants ! Si

le 0 de tte pour le chire du milieu de

24D16

n'est pas utilis, le rsultat

est faux. Le conversion du binaire vers l'hexa est aussi simple. On eectue
la mme chose, dans l'autre sens. Convertissez chaque segment de 4 bits du
binaire vers l'hexa. Commencez droite, pas gauche, du nombre binaire.
Cela permet de s'assurer que le procd utilise les segments de 4 bits cor-

rects . Exemple :

110
6

0000
0

0101
5

1010

0111
7

Un nombre de 4 bits est appel

11102
E16

quadruplet (nibble). Donc, chaque chire

hexa correspond un quadruplet. Deux quadruplets forment un octet et


donc un octet peut tre reprsent par un nombre hexa deux chires. La
valeur d'un bit va de 0 11111111 en binaire, 0 FF en hexa et 0 255 en
dcimal.

1.2

Organisation de l'Ordinateur

1.2.1 Mmoire
La mmoire est mesure en
L'unit mmoire de base est l'octet. Un ordinateur avec 32 mega octets
10
kilo octets ( 2
= 1024 oc- de mmoire peut stocker jusqu' environ 32 millions d'octets d'informations.
20
tets), mega octets (2
= Chaque octet en mmoire est tiquet par un nombre unique appel son

1048576 octets) et giga


30
tets (2
= 1073741824
tets).

oc-

oc-

adresse comme le montre la Figure 1.4.


Adresse
Mmoire

2A

45

B8

20

8F

CD

12

2E

Fig. 1.4 

Adresses Mmoire

Souvent, la mmoire est utilise par bouts plus grand que des octets
isoles. Sur l'architecture PC, on a donn des noms ces portions de mmoire
plus grandes, comme le montre le Tableau 1.2.

2
S'il n'est pas clair que le point de dpart est important, essayez de convertir l'exemple
en partant de la gauche

1.2.

ORGANISATION DE L'ORDINATEUR

mot

2 octets

double mot

4 octets

quadruple mot

8 octets

paragraphe

16 octets

Tab. 1.2 

Units Mmoire

Toutes les donnes en mmoire sont numriques. Les caractres sont stocks en utilisant un

code caractre

qui fait correspondre des nombres aux

caractres. Un des codes caractre les plus connus est appel

ASCII

(Ameri-

can Standard Code for Information Interchange, Code Amricain Standard


pour l'Echange d'Informations). Un nouveau code, plus complet, qui supplante l'ASCII est l'Unicode. Une des dirences cls entre les deux codes
est que l'ASCII utilise un octet pour encoder un caractre alors que l'Unicode

mot ). Par exemple, l'ASCII fait correspondre l'octet


4116 (6510 ) au caractre majuscule A ; l'Unicode y fait correspondre le mot

en utilise deux (ou un

004116 .

Comme l'ASCII n'utilise qu'un octet, il est limit 256 caractres

dirrents au maximum . L'Unicode tend les valeurs ASCII des mots et


permet de reprsenter beaucoup plus de caractres. C'est important an de
reprsenter les caractres de tous les langages du monde.

1.2.2 Le CPU (processeur)


Le processeur (CPU, Central Processing Unit) est le dispositif physique
qui excute les instructions. Les instructions que les processeurs peuvent excuter sont gnralement trs simples. Elles peuvent ncessiter que les donnes
sur lesquelles elles agissent soient prsentes dans des emplacements de stockage spciques dans le processeur lui-mme appels

registres. Le processeur

peut accder aux donnes dans les registres plus rapidement qu'aux donnes
en mmoire. Cependant, le nombre de registres d'un processeur est limit,
donc le programmeur doit faire attention n'y conserver que les donnes
actuellement utilises.
Les instructions que peut excuter un type de processeur constituent

le

langage machine.

Les programmes machine ont une structure beaucoup

plus basique que les langages de plus haut niveau. Les instructions du langage machine sont encodes en nombres bruts, pas en format texte lisible.
Un processeur doit tre capable de dcoder les instructions trs rapidement
pour fonctionner ecacement. Le langage machine est conu avec ce but en
tte, pas pour tre facilement dchirable par les humains. Les programmes
crits dans d'autres langages doivent tre convertis dans le langage machine

3
En fait, l'ASCII n'utilise que les 7 bits les plus faible et donc n'a que 128 valeurs
utiliser.

CHAPITRE 1.

natif du processeur pour s'excuter sur l'ordinateur. Un

INTRODUCTION

compilateur

est un

programme qui traduit les programmes crits dans un langage de programmation en langage machine d'une architecture d'ordinateur particulire. En
gnral, chaque type de processeur a son propre langage machine unique.
C'est une des raisons pour lesquelles un programme crit pour Mac ne peut
pas tre excut sur un PC.
Les ordinateurs utilisent une

GHz

horloge

pour synchroniser l'excution des

vitesse d'horloge ). Lorsque vous achetez un ordinateur 1,5GHz, 1,5GHz est la frquence

signie gigahertz ou instructions. L'horloge tourne une frquence xe (appele

un milliards de cycles par

seconde. Un processeur de cette horloge. L'horloge ne dcompte pas les minutes et les secondes. Elle
1,5GHz a 1,5 milliards
bat simplement un rythme constant. Les composants lectroniques du prod'impulsions horloge par
cesseur utilisent les pulsations pour eectuer leurs oprations correctement,
seconde.
comme le battement d'un mtronome aide jouer de la musique un rythme

correct. Le nombre de battements (ou, comme on les appelle couramment

cycles ) que recquiert une instruction dpend du modle et de la gnration


du processeur. Le nombre de cycles dpend de l'instruction.

1.2.3 La famille des processeurs 80x86


Les PC contiennent un processeur de la famille des Intel 80x86 (ou un
clone). Les processeurs de cette famille ont tous des fonctionnalits en commun, y compris un langage machine de base. Cependant, les membre les plus
rcents amliorent grandement les fonctionnalits.

8088,8086:

Ces processeurs, du point de vue de la programmation sont

identiques. Ils taient les processeurs utiliss dans les tous premiers
PC. Il orent plusieurs registres 16 bits : AX, BX, CX, DX, SI, DI,
BP, SP, CS, DS, SS, ES, IP, FLAGS. Ils ne supportent que jusqu'
1Mo de mmoire et n'oprent qu'en

mode rel. Dans ce mode, un pro-

gramme peut accder n'importe quelle adresse mmoire, mme la


mmoire des autres programmes ! Cela rend le dbogage et la scurit
trs diciles ! De plus, la mmoire du programme doit tre divise en

segments. Chaque segment ne peut pas dpasser les 64Ko.


80286: Ce processeur tait utilis dans les PC de type AT.

Il apporte

quelques nouvelles instructions au langage machine de base des 8088/86.


Cependant, sa principale nouvelle fonctionnalit est le

bits.

mode protg 16

Dans ce mode, il peut accder jusqu' 16Mo de mmoire et em-

pcher les programmes d'accder la mmoire des uns et des autres.


Cependant, les programmes sont toujours diviss en segments qui ne
peuvent pas dpasser les 64Ko.

80386:

Ce processeur a grandement amlior le 80286. Tout d'abord, il

tend la plupart des registres 32 bits (EAX, EBX, ECX, EDX, ESI,
EDI, EBP, ESP, EIP) et ajoute deux nouveaux registres 16 bits : FS

1.2.

ORGANISATION DE L'ORDINATEUR

AX
AH

AL

Fig. 1.5  Le registre AX

et GS. Il ajoute galement un nouveau

mode protg 32 bits. Dans ce

mode, il peut accder jusqu' 4Go de mmoire. Les programmes sont


encore diviss en segments mais maintenant, chaque segment peut galement faire jusqu' 4Go !

80486/Pentium/Pentium Pro:

Ces membres de la famille 80x86 apportent

trs peu de nouvelles fonctionnalits. Il acclrent principalement l'excution des instructions.

Pentium MMX:

Ce processeur ajoute les instructions MMX (MultiMedia

eXentions) au Pentium. Ces instructions peuvent acclrer des oprations graphiques courantes.

Pentium II:

C'est un processeur Pentium Pro avec les instructions MMX

(Le Pentium III est grossirement un Pentium II plus rapide).

1.2.4 Regitres 16 bits du 8086


Le processeur 8086 original fournissait quatre registres gnraux de 16
bits : AX, BX, CX et DX. Chacun de ces registres peut tre dcompos en
deux registres de 8 bits. Par exemple, le registre AX pouvait tre dcompos
en AH et AL comme le montre la Figure 1.5. Le registre AH contient les 8
bits de poids fort de AX et AL contient les 8 bits de poids faivble. Souvent,
AH et AL sont utiliss comme des registres d'un octet indpendants ; cependant, il est important de raliser qu'ils ne sont pas indpendants de AX.
Changer la valeur de AX changera les valeurs de AL et BL et

vice versa . Les

registres gnraux sont utiliss dans beaucoup de dplacements de donnes


et instructions arithmtiques.
Il y a deux registres d'index de 16 bits : SI et DI. Ils sont souvent utiliss comme des pointeurs, mais peuvent tre utiliss pour la plupart des
mmes choses que les registres gnraux. Cependant, ils ne peuvent pas tre
dcomposs en registres de 8 bits.
Les registres 16 bits BP et SP sont utiliss pour pointer sur des donnes
dans la pile du langage machine et sont appels le pointeur de base et le
ponteur de pile, respectivement. Nous en reparlerons plus tard.
Les registres 16 bits CS, DS, SS et ES sont des

registres de segment. Ils

indiquent quelle zone de la mmoire est utilise pour les direntes parties
d'un programme. CS signie Code Segment, DS Data Segment, SS Stack
Segment (segment de pile) et ES Extra Segment. ES est utilis en tant que

CHAPITRE 1.

INTRODUCTION

registre de segment temporaire. Des dtails sur ces registres se trouvent dans
les Sections 1.2.6 et 1.2.7.
Le registre de pointeur d'instruction (IP) est utilis avec le registre CS
pour mmoriser l'adresse de la prochaine instruction excuter par le processeur. Normalement, lorsqu'une instruction est excute, IP est incrment
pour pointer vers la prochaine instruction en mmoire.
Le registre FLAGS stocke des informations importantes sur les rsultats
d'une instruction prcdente. Ces rsultats sont stocks comme des bits individuels dans le registre. Par exemple, le bit Z est positionn 1 si le rsultat
de l'instruction prcdente tait 0 ou 0 sinon. Toutes les instructions ne
modient pas les bits dans FLAGS, consultez le tableau dans l'appendice
pour voir comment chaque instruction aecte le registre FLAGS.

1.2.5 Registres 32 bits du 80386


Les processeurs 80386 et plus rcents ont des registres tendus. Par
exemple le registre AX 16 bits est tendu 32 bits. Pour la compatibilit
ascendante, AX fait toujours rfrence au registre 16 bits et on utilise EAX
pour faire rfrence au registre 32 bits. AX reprsente les 16 bits de poids
faible de EAX tout comme AL reprsente les 8 bits de poids faible de AX
(et de EAX). Il n'y a pas de moyen d'accder aux 16 bits de poids fort de
EAX directement. Les autres registres tendus sont EBX, ECX, EDX, ESI
et EDI.
La plupart des autres registres sont galement tendus. BP devient EBP ;
SP devient ESP ; FLAGS devient EFLAGS et IP devient EIP. Cependant,
contrairement aux registres gnraux et d'index, en mode protg 32 bits
(dont nous parlons plus loin) seules les versions tendues de ces registres
sont utilises.
Les registres de segment sont toujours sur 16 bits dans le 80386. Il y a
galement deux registres de segment supplmentaires : FS et GS. Leurs noms
n'ont pas de signication particulire. Ce sont des segments temporaires
supplmentaires (comme ES).
Une des dnitions du terme

mot

se rfre la taille des registres de

donnes du processeur. Pour la famille du 80x86, le terme est dsormais un


peu confus. Dans le Tableau 1.2, on voit que le terme

mot

est dni comme

faisant 2 octets (ou 16 bits). Cette signication lui a t attribue lorsque le


premier 8086 est apparu. Lorsque le 80386 a t dvelopp, il a t dcid
de laisser la dnition de
chang.

mot

inchange, mme si la taille des registres avait

1.2.

ORGANISATION DE L'ORDINATEUR

1.2.6 Mode Rel


20 Alors d'o vient l'infme

En mode rel, la mmoire est limite seulement un mga octet (2

des
640Ko
du
octet). Les adresses valides vont de 00000 FFFFF (en hexa). Ces adresses limite
DOS
?
Le
BIOS
requiert
ncessitent un nombre sur 20 bits. Cependant, un nombre de 20 bits ne

tiendrait dans aucun des registres 16 bits du 8086. Intel a rsolu le problme,
en utilisant deux valeurs de 16 bits pour dterminer une adresse. La premire
valeur de 16 bits est appele le

slecteur.

Les valeurs du slecteur doivent

une partie des 1Mo pour

son propre code et pour


les priphriques matriels
comme l'cran.

tre stockes dans des registres de segment. La seconde valeur de 16 bits est

appele le dplacement (oset). L'adresse physique identie par un couple


slecteur :dplacement 32 bits est calcule par la formule

16 selecteur + deplacement
Multiplier par 16 en hexa est facile, il sut d'ajouter un 0 la droite du
nombre. Par exemple, l'adresse physique rfrence par 047C:0048 est obtenue de la faon suivante :
047C0
+0048
04808
De fait, la valeur du slecter est un numro de paragraphe (voir Tableau 1.2).
Les adresses relles segmentes ont des inconvnients :
 Une seule valeur de slecteur peut seulement rfrencer 64Ko de mmoire (la limite suprieure d'un dplacement de 16 bits). Que se passet-il si un programme a plus de 64Ko de code ? Une seule valeur de CS
ne peut pas tre utilise pour toute l'excution du programme. Le programme doit tre divis en sections (appeles

segments )

de moins de

64Ko. Lorsque l'excution passe d'un segment l'autre, la valeur de


CS doit tre change. Des problmes similaires surviennent avec de
grandes quantits de donnes et le registre DS. Cela peut tre trs
gnant !
 Chaque octet en mmoire n'a pas une adresse segmente unique. L'adresse
physique 04808 peut tre rfrence par 047C:0048, 047D:0038, 047E:0028
ou 047B:0058. Cela complique la comparaison d'adresses segmentes.

1.2.7 Mode Protg 16 bits


Dans le mode protg 16 bits du 80286, les valeurs du slecteur sont
interprtes de faon totalement dirente par rapport au mode rel. En
mode rel, la valeur d'un slecteur est un numro de paragraphe en mmoire.
En mode protg, un slecteur est un

indice dans un tableau de descripteurs.

Dans les deux modes, les programmes sont diviss en segments. En mode rel,
ces segments sont des positions xes en mmoire et le slecteur indique le

10

CHAPITRE 1.

INTRODUCTION

numro de paragraphe auquel commence le segment. En mode protg, les


segments ne sont pas des positions xes en mmoire physique. De fait, il
n'ont mme pas besoin d'tre en mmoire du tout !
Le mode protg utilise une technique appele

mmoire virtuelle . L'ide

de base d'un systme de mmoire virtuelle est de ne garder en mmoire que les
programmes et les donnes actuellement utiliss. Le reste des donnes et du
code sont stocks temporairement sur le disque jusqu' ce qu'on aie nouveau
besoin d'eux. Dans le mode protg 16 bits, les segments sont dplacs entre
la mmoire et le disque selon les besoins. Lorsqu'un segment est recharg
en mmoire depuis le disque, il est trs probable qu'il sera un endroit
en mmoire dirent de celui o il tait avant d'tre plac sur le disque.
Tout ceci est eectu de faon transparente par le systme d'exploitation. Le
programme n'a pas a tre crit diremment pour que la mmoire virtuelle
fonctionne.
En mode protg, chaque segment est assign une entre dans un tableau de descripteurs. Cette entre contient toutes les informations dont le
systme a besoin propos du segment. Ces informations indiquent : s'il est
actuellement en mmoire ; s'il est en mmoire, o il se trouve ; les droits
d'accs (

p.e., lecture seule). L'indice de l'entre du segment est la valeur du

slecteur stocke dans les registres de segment.


Un

journaliste

PC

bien

Un gros inconvnient du mode protg 16 bits est que les dplacements

connu a baptis le proces- sont toujours des quantits sur 16 bits. En consquence, Les tailles de segseur 286 cerveau mort ment sont toujours limites au plus 64Ko. Cela rend l'utilisation de grands
(brain dead)
tableaux problmatique.

1.2.8 Mode Protg 32 bits


Le 80386 a introduit le mode protg 32 bits. Il y a deux dirences
majeures entre les modes protgs 32 bits du 386 et 16 bits du 286 :
1. Les dplacements sont tendus 32 bits. Cela permet un dplacement
d'aller jusqu' 4 milliards. Ainsi, les segments peuvent avoir des tailles
jusqu' 4Go.
2. Les segments peuvent tre diviss en units plus petites de 4Ko appeles

pages .

Le systme de mmoire virtuelle fonctionne maintenant

avec des pages plutt qu'avec des segments. Cela implique que seules
certaines parties d'un segment peuvent tre prsentes en mmoire un
instant donn. En mode 16 bits du 286, soit le segment en entier est
en mmoire, soit rien n'y est. Ce qui n'aurait pas t pratique avec les
segments plus grands que permet le mode 32 bits.

mode standard fait rfrence au mode protg 16


mode amlior (enhanced) fait rfrence au mode 32 bits.

Dans Windows 3.x, le


bits du 286 et le

1.3.

11

LANGAGE ASSEMBLEUR

Windows 9X, Windows NT/2000/XP, OS/2 et Linux fonctionnent tous en


mode protg 32 bits pagin.

1.2.9 Interruptions
Quelques fois, le ot ordinaire d'un programme doit tre interrompu pour
traiter des vnements qui requirent une rponse rapide. Le matriel d'un
ordinateur ore un mcanisme appel

interruptions

pour grer ces vne-

ments. Par exemple, lorsqu'une souris est dplace, la souris interrompt le


programme en cours pour grer le dplacement de la souris (pour dplacer le

etc.). Les interruptions provoquent le passage du contrle un gestionnaire d'interruptions. Les gestionnaires d'interruptions sont des routines

curseur

qui traitent une interruption. Chaque type d'interruption est assigne un


nombre entier. Au dbut de la mmoire physique, rside un tableau de

teurs d'interuptions

vec-

qui contient les adresses segmentes des gestionnaires

d'interruption. Le numro d'une interruption est essentiellement un indice


dans ce tableau.
Les interruptions externes proviennent de l'extrieur du processeur (la
souris est un exemple de ce type). Beaucoup de priphriques d'E/S soulvent des interruptions (

p.e.,

le clavier, le timer, les lecteurs de disque, le

CD-ROM et les cartes son). Les interruptions internes sont souleves depuis le processeur, cause d'une erreur ou d'une instruction d'interruption.
Les interruptions erreur sont galement appeles

traps.

Les interruptions

interruptions logicielles. Le DOS utilise ce type d'interruption pour implmenter son


gnres par l'instruction d'interruption sont galement appeles

API(Application Programming Interface). Les systmes d'exploitation plus

rcents (comme Windows et Unix) utilisent une interface base sur C .


Beaucoup de gestionnaires d'interruptions redonnent le contrle au programme interrompu lorsqu'ils se terminent. Ils restaurent tous les registres
aux valeurs qu'ils avaient avant l'interruption. Ainsi, le programme interrompu s'excute comme si rien n'tait arriv (except qu'il perd quelques
cycles processeur). Les traps ne reviennent gnralement jamais. Souvent,
elles arrtent le programme.

1.3

Langage Assembleur

1.3.1 Langage Machine


Chaque type de processeur comprend son propre langage machine. Les
instructions dans le langage machine sont des nombres stocks sous forme

4
Cependant, ils peuvent galement utiliser une interface de plus bas niveau au niveau
du noyau

12

CHAPITRE 1.

INTRODUCTION

d'octets en mmoire. Chaque instruction a son propre code numrique unique


appel

code d'opration ou opcode (operation code) en raccourci. Les instruc-

tions des processeurs 80x86 varient en taille. L'opcode est toujours au dbut
de l'instruction. Beaucoup d'instructions comprennent galement les donnes

p.e. des constantes ou des adresses) utilises par l'instruction.

Le langage machine est trs dicile programmer directement. Dchiffrer la signication d'instructions codes numriquement est fatigant pour
des humains. Par exemple, l'instruction qui dit d'ajouter les registres EAX
et EBX et de stocker le rsultat dans EAX est encode par les codes hexadcimaux suivants :
03 C3
C'est trs peu clair. Heureusement, un programme appel un

assembleur

peut faire ce travail laborieux la place du programmeur.

1.3.2 Langage d'Assembleur


Un programme en langage d'assembleur est stock sous forme de texte
(comme un programme dans un langage de plus haut niveau). Chaque instruction assembleur reprsente exactement une instruction machine. Par
exemple, l'instruction d'addition dcrite ci-dessus serait reprsente en langage assembleur comme suit :

add eax, ebx


Ici, la signication de l'instruction est
chine. Le mot

add est un mnmonique

beaucoup plus

claire qu'en code ma-

pour l'instruction d'addition. La forme

gnrale d'une instruction assembleur est :

mnmonique oprande(s)
Un

assembleur

est un programme qui lit un chier texte avec des instruc-

tions assembleur et convertit l'assembleur en code machine. Les

compilateurs

sont des programmes qui font des conversions similaires pour les langages de
programmation de haut niveau. Un assembleur est beaucoup plus simple
Cela a pris plusieurs an- qu'un compilateur. Chaque instruction du langage d'assembleur reprsente
nes aux scientiques de directement une instruction machine. Les instructions d'un langage de plus
l'informatique pour conce- haut niveau sont
plus complexes et peuvent requrir beaucoup
voir le simple fait d'crire
d'instructions machine.
un compilateur !
Une autre dirence importante entre l'assembleur et les langages de

beaucoup

haut niveau est que comme chaque type de processeur a son propre langage
machine, il a galement son propre langage d'assemblage. Porter des programmes assembleur entre direntes architectures d'ordinateur est

beaucoup

plus dicile qu'avec un langage de haut niveau.


Les exemples de ce livre utilisent le Netwide Assembler ou NASM en
raccourci. Il est disponible gratuitement sur Internet (voyez la prface pour

1.3.

13

LANGAGE ASSEMBLEUR

l'URL). Des assembleurs plus courants sont l'Assembleur de Microsoft (MASM)


ou l'Assembleur de Borland (TASM). Il y a quelques dirences de syntaxe
entre MASM/TASM et NASM.

1.3.3 Oprandes d'Instruction


Les instructions en code machine ont un nombre et un type variables
d'oprandes ; cependant, en gnral, chaque instruction a un nombre x
d'oprandes (0 3). Les oprandes peuvent avoir les types suivants :

registre :

Ces oprandes font directement rfrence au contenu des registres

du processeur.

mmoire :

Ils font rfrence aux donnes en mmoire. L'adresse de la don-

ne peut tre une constante code en dur dans l'instruction ou calcule


en utilisant les valeurs des registres. Les adresses sont toujours des
dplacements relatifs au dbut d'un segment.

immdiat :

Ce sont des valeurs xes qui sont listes dans l'instruction elle-

mme. Elles sont stockes dans l'instruction (dans le segment de code),


pas dans le segment de donnes.

implicite :

Ces oprandes ne sont pas entrs explicitement. Par exemple,

l'instruction d'incrmentation ajoute un un registre ou la mmoire.


Le un est implicite.

1.3.4 Instructions de base


L'instruction la plus basique est l'instruction

MOV. Elle dplace les donnes

d'un endroit un autre (comme l'oprateur d'assignement dans un langage


de haut niveau). Elle prend deux oprandes :

mov dest, src


La donne spcie par

src

est copie vers

dest .

Une restriction est que

les deux oprandes ne peuvent pas tre tous deux des oprandes mmoire.
Cela nous montre un autre caprice de l'assembleur. Il y a souvent des rgles
quelque peu arbitraires sur la faon dont les direntes instructions sont
utilises. Les oprandes doivent galement avoir la mme taille. La valeur de
AX ne peut pas tre stocke dans BL.
Voici un exemple (les points-virgules marquent un commentaire) :

mov
mov

eax, 3 ; stocke 3 dans le registre EAX (3 est un operande immediat)


bx, ax ; stocke la valeur de AX dans le registre BX

L'instruction

add
add

eax, 4
al, ah

ADD

est utilise pour additionner des entiers.

; eax = eax + 4
; al = al + ah

14

CHAPITRE 1.

L'instruction

sub
sub

SUB

INTRODUCTION

soustrait des entiers.

bx, 10 ; bx = bx - 10
ebx, edi ; ebx = ebx - edi

Les instructions

INC

et

DEC

incrmentent ou dcrmentent les valeurs de

1. Comme le un est un oprande implicite, le code machine pour


est plus petit que celui des instructions

inc
dec

ecx
dl

ADD

et

SUB

INC

et

DEC

quivalentes.

; ecx++
; dl--

1.3.5 Directives
Une

directive

est destine l'assembleur, pas au processeur. Elles sont

gnralement utilises pour indiquer l'assembleur de faire quelque chose


ou pour l'informer de quelque chose. Elles ne sont pas traduites en code
machine. Les utilisations courantes des directives sont :

la dnition de constantes
la dnition de mmoire pour stocker des donnes
grouper la mmoire en segment
inclure des codes sources de faon conditionnelle
inclure d'autres chiers

Le code NASM est analys par un prprocesseur, exactement comme en


C. Il y a beaucoup de commandes identiques celles du prprocesseur C.
Cependant, les directives du prprocesseur NASM commencent par un % au
lieu d'un # comme en C.

La directive equ
La directive

equ

peut tre utilise pour dnir un

symbole. Les symboles

sont des constantes nommes qui peuvent tre utilises dans le programme
assembleur. Le format est le suivant :

symbole equ valeur


Les valeurs des symboles ne peuvent

pas

tre rednies plus tard.

La directive %dene
Cette directive est semblable la directive

#define du C. Elle est le plus

souvent utilise pour dnir des macros, exactement comme en C.

%define SIZE 100


mov
eax, SIZE

1.3.

15

LANGAGE ASSEMBLEUR

Unit

Lettre

octet

mot

double mot

quadruple mot

dix octets

Tab. 1.3  Lettres des Directives

Le code ci-dessus dnit une macro appele


dans une instruction

MOV.

RESX

SIZE

et

DX

et montre son utilisation

Les macros sont plus exibles que les symboles de

deux faons. Elles peuvent tre rednies et peuvent tre plus que de simples
nombres constants.

Directives de donnes
Les directives de donnes sont utilises dans les segments de donnes
pour rserver de la place en mmoire. Il y a deux faons de rserver de la
mmoire. La premire ne fait qu'allouer la place pour les donnes ; la seconde
alloue la place et donne une valeur initiale. La premire mthode utilise une
des directives

RESX.

Le

X est remplac par une lettre qui dtermine la taille

de l'objet (ou des objets) qui sera stock. Le Tableau 1.3 montre les valeurs
possibles.
La seconde mthode (qui dnit une valeur initiale) utilise une des directives

DX.

Les lettres

X sont les mmes que celles de la directive RESX.

Il est trs courant de marquer les emplacements mmoire avec des

labels

(tiquettes). Les labels permettent de faire rfrence facilement aux emplacements mmoire dans le code. Voici quelques exemples :

L1
L2
L3
L4
L5
L6
L7
L8

db
dw
db
db
db
dd
resb
db

0
1000
110101b
12h
17o
1A92h
1
"A"

;
;
;
;
;
;
;
;

octet libelle L1 avec une valeur initiale de 0


mot labelle L2 avec une valeur initiale de 1000
octet initialise a la valeur binaire 110101 (53 en decimal)
octet initialise a la valeur hexa 12 (18 en decimal)
octet initialise a la valeur octale 17 (15 en decimal)
double mot initialise a la valeur hexa 1A92
1 octet non initialise
octet initialise avec le code ASCII du A (65)

Les doubles et simples quotes sont traites de la mme faon. Les dnitions de donnes conscutives sont stockes squentiellement en mmoire.
C'est dire que le mot L2 est stock immdiatement aprs L1 en mmoire.
Des squences de mmoire peuvent galement tre dnies.

L9

db

0, 1, 2, 3

; definit 4 octets

16

L10
L11

CHAPITRE 1.

db
db

"w", "o", "r", 'd', 0


'word', 0
La directive

DD

INTRODUCTION

; definit une chaine C = "word"


; idem L10

peut tre utilise pour dnir la fois des entiers et des

constantes virgule ottante en simple prcision . Cependant, la directive

DQ ne peut tre utilise que pour dnir des constantes virgule ottante en
double prcision.
Pour les grandes squences, la directive de NASM

TIMES est souvent utile.

Cette directive rpte son oprande un certain nombre de fois. Par exemple :

L12
L13

times 100 db 0
resw 100

; equivalent a 100 (db 0)


; reserve de la place pour 100 mots

Souvenez vous que les labels peuvent tre utiliss pour faire rfrence
des donnes dans le code. Il y a deux faons d'utiliser les labels. Si un
label simple est utilis, il fait rfrence l'adresse (ou oset) de la donne.
Si le label est plac entre crochets ([]), il est interprt comme la donne
cette adresse. En d'autres termes, il faut considrer un label comme un

pointeur

vers la donne et les crochets drfrencent le pointeur, exactement

comme l'astrisque en C (MASM/TASM utilisent une convention dirente).


En mode 32 bits, les adresses sont sur 32 bits. Voici quelques exemples :

1
2
3
4
5
6
7

mov
mov
mov
mov
add
add
mov

al, [L1]
eax, L1
[L1], ah
eax, [L6]
eax, [L6]
[L6], eax
al, [L6]

;
;
;
;
;
;
;

Copie l'octet situe en L1 dans AL


EAX = addresse de l'octet en L1
copie AH dans l'octet en L1
copie le double mot en L6 dans EAX
EAX = EAX + double mot en L6
double mot en L6 += EAX
copie le premier octet du double mot en L6 dans AL

La ligne 7 de cet exemple montre une proprit importante de NASM. L'assembleur ne garde

pas

de trace du type de donnes auquel se rfre le label.

C'est au programmeur de s'assurer qu'il (ou elle) utilise un label correctement. Plus tard, il sera courant de stocker des adresses dans les registres et
utiliser les registres comme un pointeur en C. L encore, aucune vrication
n'est faite pour savoir si le pointeur est utilis correctement. A ce niveau,
l'assembleur est beaucoup plus sujet erreur que le C.
Considrons l'instruction suivante :

mov

[L6], 1

; stocke 1 en L6

Cette instruction produit une erreur

operation size not specified. Pour-

quoi ? Parce que l'assembleur ne sait pas s'il doit considrer 1 comme un

5
Les nombres virgule ottante en simple prcision sont quivalent aux variables float
en C.

1.3.

17

LANGAGE ASSEMBLEUR

octet, un mot ou un double mot. Pour rparer cela, il faut ajouter un spcicateur de taille :

mov

dword [L6], 1

; stocke 1 en L6

Cela indique l'assembleur de stocker un 1 dans le double mot qui commence


en

L6.

Les autres spcicateurs de taille sont :

BYTE, WORD, QWORD

et

TWORD6 .

1.3.6 Entres et Sorties


Les entres et sorties sont des activits trs dpendantes du systme.
Cela implique un interfaage avec le matriel. Les langages de plus haut
niveau, comme C, fournissent des bibliothques standards de routines pour
une interface de programmation simple, uniforme pour les E/S. Les langages
d'assembleur n'ont pas de bibliothque standard. Ils doivent soit accder
directement au matriel (avec une opration privilgie en mode protg)
ou utiliser les routines de bas niveau ventuellement fournies par le systme
d'exploitation.
Il est trs courant pour les routines assembleur d'tre interfaces avec
du C. Un des avantages de cela est que le code assembleur peut utiliser
les routines d'E/S de la bibliothque standard du C. Cependant, il faut
connatre les rgles de passage des informations aux routines que le C utilise.
Ces rgles sont trop compliques pour en parler ici (nous en parlerons plus
tard !). Pour simplier les E/S, l'auteur a dvelopp ses propres routines qui
masquent les rgles complexes du C et fournissent une interface beaucoup
plus simple. Le Tableau 1.4 dcrit les routines fournies. Toutes les routines
prservent les valeurs de tous les registres, except les routines read. Ces
routines modient la valeur du registre EAX. Pour utiliser ces routines, il
faut inclure un chier contenant les informations dont l'assembleur a besoin
pour les utiliser. Pour inclure un chier dans NASM, utilisez la directive
du prprocesseur

%include.

La ligne suivante inclut le chier requis par les

7 :
routines d'E/S de l'auteur

%include "asm_io.inc"
Pour utiliser une de ces routines d'achage, il faut charger EAX avec la

CALL pour l'invoquer. L'instruction


CALL est quivalente un appel de fonction dans un langage de haut niveau.
valeur correcte et utiliser une instruction

Elle saute une autre portion de code mais revient son origine une fois

6
TWORD dnit une zone mmoire de 10 octets. Le coprocesseur virgule ottante utilise
ce type de donnes.
7
Le chier asm_io.inc (et le chier objet asm_io que recquiert asm_io.inc)
sont dans les exemples de code tlcharger sur la page web de ce tutorial,

http://www.drpaulcarter.com/pcasm

18

CHAPITRE 1.

print_int
print_char

INTRODUCTION

ache l'cran la valeur d'un entier stock dans EAX


ache l'cran le caractre dont le code ASCII est

print_string

stock dans AL

adresse

ache l'cran le contenu de la chane l'

stocke dans EAX. La chane doit tre une chane de


type C (

print_nl
read_int

i.e. termine par 0).

ache l'cran un caractre de nouvelle ligne.


lit un entier au clavier et le stocke dans le registre

read_char

EAX.
lit un caractre au clavier et stocke son code ASCII
dans le registre EAX.
Tab. 1.4  Routine d'E/S de l'assembleur

la routine termine. Le programme d'exemple ci-dessous montre plusieurs


exemples d'appel ces routines d'E/S.

1.3.7 Dbogage
La bibliothque de l'auteur contient galement quelques routines utiles
pour dboguer les programmes. Ces routines de dbogage achent des informations sur l'tat de l'ordinateur sans le modier. Ces routines sont en fait
des

macros

qui sauvegardent l'tat courant du processeur puis font appel

une sous-routine. Les macros sont dnies dans le chier

asm_io.inc

dont

nous avons parl plus haut. Les macros sont utilises comme des instructions
ordinaires. Les oprandes des macros sont spars par des virgules.
Il y a quatre routines de dbogage nommes
et

dump_math ;

dump_regs, dump_mem, dump_stack

elles achent respectivement les valeurs des registres, de la

mmoire, de la pile et du coprocesseur arithmtique.

dump_regs

Cette macro ache les valeurs des registres (en hexadcimal)

de l'ordinateur sur

stdout (i.e.

l'cran). Elle ache galement les bits

positionns dans le registre FLAGS . Par exemple, si le drapeau zro


(zero ag) est 1,

ZF

est ach. S'il est 0, il n'est pas ach. Elle

prend en argument un entier qui est ach galement. Cela peut aider
distinguer la sortie de direntes commandes

dump_mem

dump_regs.

Cette macro ache les valeurs d'une rgion de la mmoire

(en hexadcimal et galement en caractres ASCII). Elle prend trois


arguments spars par des virgules. Le premier est un entier utilis
pour tiqueter la sortie (comme l'argument de

dump_regs).

Le second

argument est l'adresse acher (cela peut tre un label). Le dernier


argument est le nombre de paragraphes de 16 octets acher aprs

Le Chapitre 2 parle de ce registre

1.4.

19

CRER UN PROGRAMME

l'adresse. La mmoire ache commencera au premier multiple de paragraphe avant l'adresse demande.

dump_stack

Cette macro ache les valeurs de la pile du processeur (la

pile sera prsente dans le Chapitre 4). La pile est organise en double
mots et cette routine les ache de cette faon. Elle prend trois arguments dlimits par des virgules. Le premier est un entier (comme pour

dump_regs).

Le second est le nombre de double mots acher

l'adresse que le registre

EBP

contient et le troisime argument est le

nombre de double mots acher

dump_math

aprs

avant

l'adresse contenue dans

EBP.

Cette macro ache les valeurs des registres du coprocesseur

arithmtique. Elle ne prend qu'un entier en argument qui est utilis


pour tiqueter la sortie comme le fait l'argument de

1.4

dump_regs.

Crer un Programme

Aujourd'hui, il est trs peu courant de crer un programme autonome


crit compltement en langage assembleur. L'assembleur est habituellement
utilis pour optimiser certaines routines critiques. Pourquoi ? Il est

beaucoup

plus simple de programmer dans un langage de plus haut niveau qu'en assembleur. De plus, utiliser l'assembleur rend le programme trs dur porter
sur d'autres plateformes. En fait, il est rare d'utiliser l'assembleur tout court.
Alors, pourquoi apprendre l'assembleur ?
1. Quelques fois, le code crit en assembleur peut tre plus rapide et plus
compact que le code gnr par un compilateur.
2. L'assembleur permet l'accs des fonctionnalits matrielles du systme directement qu'il pourrait tre dicile ou impossible utiliser
depuis un langage de plus haut niveau.
3. Apprendre programmer en assembleur aide acqurir une comprhension plus profonde de la faon dont fonctionne un ordinateur.
4. Apprendre programmer en assembleur aide mieux comprendre comment les compilateurs et les langage de haut niveau comme C fonctionnent.
Ces deux dernier points dmontrent qu'apprendre l'assembleur peut tre utile
mme si on ne programme jamais dans ce langage plus tard. En fait, l'auteur
programme rarement en assembleur, mais il utilise les leons qu'il en a tir
tous les jours.

1.4.1 Premier programme


Les programmes qui suivront dans ce texte partiront tous du programme
de lancement en C de la Figure 1.6. Il appelle simplement une autre fonction

20

1
2
3
4
5
6

CHAPITRE 1.

INTRODUCTION

int main()
{
int ret_status ;
ret_status = asm_main();
return ret_status ;
}
Fig. 1.6  Code de

nomme

asm_main.

driver.c

C'est la routine qui sera crite en assemble proprement

dit. Il y a plusieurs avantages utiliser un programme de lancement en


C. Tout d'abord, cela laisse le systme du C initialiser le programme de
faon fonctionner correctement en mode protg. Tous les segments et les
registres correspondants seront initialiss par le C. L'assembleur n'aura pas
se proccuper de cela. Ensuite, la bibliothque du C pourra tre utilise
par le code assembleur. Les routines d'E/S de l'auteur en tirent partie. Elles
utilisent les fonctions d'E/S du C (printf,

etc.). L'exemple suivant montre

un programme assembleur simple.

1
2
3
4
5
6
7

;
;
;
;
;
;
;

first.asm
fichier: first.asm
Premier programme assembleur. Ce programme attend la saisie de deux
entiers et affiche leur somme.
Pour creer l'executable en utilisant djgpp :
nasm -f coff first.asm
gcc -o first first.o driver.c asm_io.o

8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

%include "asm_io.inc"
;
; Les donnees initialisees sont placees dans le segment .data
;
segment .data
;
; Ces labels referencent les chaines utilisees pour l'affichage
;
prompt1 db
"Entrez un nombre : ", 0
; N'oubliez pas le 0 final
prompt2 db
"Entrez un autre nombre : ", 0
outmsg1 db
"Vous avez entre ", 0
outmsg2 db
" et ", 0
outmsg3 db
", leur somme vaut ", 0

1.4.

23
24
25
26
27
28
29
30
31

CRER UN PROGRAMME

21

;
; Les donnees non initialisees sont placees dans le segment .bss
;
segment .bss
;
; Ces labels referencent les doubles mots utilises pour stocker les entrees
;
input1 resd 1
input2 resd 1

32
33
34
35
36
37
38
39
40

;
; Le code est place dans le segment .text
;
segment .text
global _asm_main
_asm_main:
enter 0,0
; initialisation
pusha

41
42
43

mov
call

eax, prompt1
print_string

; affiche un message

call
mov

read_int
[input1], eax

; lit un entier
; le stocke dans input1

mov
call

eax, prompt2
print_string

; affiche un message

call
mov

read_int
[input2], eax

; lit un entier
; le stocke dans input2

mov
add
mov

eax, [input1]
eax, [input2]
ebx, eax

; eax = dword en input1


; eax += dword en input2
; ebx = eax

44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64

dump_regs 1
dump_mem 2, outmsg1, 1

; affiche les valeurs des registres


; affiche le contenu de la memoire

;
; Ce qui suit affiche le message resultat en plusieurs etapes
;
mov
eax, outmsg1
call
print_string
; affiche le premier message

22

65
66
67
68
69
70
71
72
73
74
75

CHAPITRE 1.

mov
call
mov
call
mov
call
mov
call
mov
call
call

INTRODUCTION

eax, [input1]
print_int
eax, outmsg2
print_string
eax, [input2]
print_int
eax, outmsg3
print_string
eax, ebx
print_int
print_nl

; affiche la somme (ebx)


; affiche une nouvelle ligne

eax, 0

; retourne dans le programme C

; affiche input1
; affiche le second message
; affiche input2
; affiche le troisieme message

76
77
78
79
80

popa
mov
leave
ret

first.asm

La ligne 13 du programme dnit une section qui spcie la mmoire


stocker dans le segment de donnes (dont le nom est

.data).

Seules les

donnes initialises doivent tre dnies dans ce segment. Dans les lignes 17
21, plusieurs chanes sont dclares. Elle seront aches avec la bibliothque
C et doivent donc se terminer par un caractre
vous qu'il y a une grande dirence entre

null (code ASCII 0). Souvenez

et

'0'.

Les donnes non initialises doivent tre dclares dans le segment bss
(appel

.bss

la ligne 26). Ce segment tient son nom d'un vieil oprateur

assembleur UNIX qui signiait block started by symbol. Il y a galement


un segment de pile. Nous en parlerons plus tard.
Le segment de code est appel

.text

historiquement. C'est l que les

instructions sont places. Notez que le label de code pour la routine main

convention
d'appel C . Cette convention spcie les rgles que le C utilise lorsqu'il com-

(ligne 38) a un prxe de soulignement. Cela fait partie de la

pile le code. Il est trs important de connatre cette convention lorsque l'on
interface du C et de l'assembleur. Plus loin, la convention sera prsente
dans son intgralit ; cependant, pour l'instant, il sut de savoir que tous
les symboles C (

i.e., les fonctions et les variables globales) on un prxe de

soulignement qui leur est ajout par le compilateur C (cette rgle s'applique
spciquement pour DOS/Windows, le compilateur C Linux n'ajoute rien
du tout aux noms des symboles).
La directive

global

ligne 37 indique l'assembleur de rendre le label

_asm_main global. Contrairement au C, les labels ont une porte interne

par

dfaut. Cela signie que seul le code du mme module peut utiliser le label.
La directive

global

donne au(x) label(s) spci(s) une

porte externe.

Ce

1.4.

23

CRER UN PROGRAMME

type de label peut tre accd par n'importe quel module du programme.
Le module

asm_io dclare les labels print_int, et.al. comme tant globaux.


first.asm.

C'est pourquoi l'on peut les utiliser dans le module

1.4.2 Dpendance vis vis du compilareur


9

Le code assembleur ci-dessus est spcique au compilateur C/C++ GNU

10 . Ce compilateur peut tre tlcharg gratuitement depuis

gratuit DJGPP

Internet. Il ncessite un PC base de 386 ou plus et tourne sous DOS, Windows 95/98 ou NT. Ce compilateur utilise des chiers objets au format COFF
(Common Object File Format). Pour assembler le chier au format COFF,
utilisez l'option

-f coff avec nasm (comme l'indiquent les commentaires du


o.

code). L'extension du chier objet sera

Le compilateur C Linux est galement un compilateur GNU. Pour convertir le code ci-dessus an qu'il tourne sous Linux, retirez simplement les prxes de soulignement aux lignes 37 et 38. Linux utilise le format ELF (Executable and Linkable Format) pour les chiers objet. Utilisez l'option
pour Linux. Il produit galement un chier objet avec l'extension

o.

-f elf
Les chiers spciques au

disponibles
Borland C/C++ est un autre compilateur populaire. Il utilise le for- compilateur,
sur le site de l'auteur,
mat Microsoft OMF pour les chier objets. Utilisez l'option -f obj pour les
ont
dj
t
modis
compilateurs Borland. L'extension du chier objet sera obj. Le format OMF
pour fonctionner avec le
utilise des directives segment direntes de celles des autres formats objet.
compilateur appropri.
Le segment data (ligne 13) doit tre chang en :

segment _DATA public align=4 class=DATA use32


Le segment bss (ligne 26) doit tre chang en :

segment _BSS public align=4 class=BSS use32


Le segment text (ligne 36) doit tre chang en :

segment _TEXT public align=1 class=CODE use32


De plus, une nouvelle ligne doit tre ajoute avant la ligne 36 :

group DGROUP _BSS _DATA


Le compilateur Microsoft C/C++ peut utiliser soit le format OMF, soit
le format Win32 pour les chiers objet (Si on lui passe un format OMF,
il convertit les informations au format Win32 en interne). Le format Win32
permet de dnir les segment comme pour DJGPP et Linux. Utilisez l'option

-f win32

pour produire un chier objet dans ce format. L'extension du

chier objet sera

9
10

obj.

GNU est un projet de Free Software Foundation (http://www.fsf.org)

http://www.delorie.com/djgpp

24

CHAPITRE 1.

INTRODUCTION

1.4.3 Assembler le code


La premire tape consiste assembler le code. Depuis la ligne de commande, tappez :

nasm -f format-objet first.asm


o

format-objet est soit co , soit elf , soit obj soit win32 selon le compilateur

C utilis (Souvenez vous que le chier source doit tre modi pour Linux
et pour Borland).

1.4.4 Compiler le code C


Compilez le chier

driver.c en utilisant un compilateur C. Pour DJGPP,

utilisez :

gcc -c driver.c
L'option

-c signie de compiler uniquement, sans essayer de lier. Cette option

fonctionne la fois sur les compilateurs Linux, Borland et Microsoft.

1.4.5 Lier les chiers objets


L'dition de liens est le procd qui consiste combiner le code machine
et les donnes des chiers objet et des bibliothques an de crer un chier
excutable. Comme nous allons le voir, ce processus est compliqu.
Le code C ncessite la bibliothque standard du C et un

marrage

spcial an de s'excuter. Il est

beaucoup

code de d-

plus simple de laisser le

compilateur C appeler l'diteur de liens avec les paramtres corrects que


d'essayer d'appeler l'diteur de liens directement. Par exemple, pour lier le
code du premier programme en utilisant DJGPP, utilisez :

gcc -o first driver.o first.o asm_io.o


Cela cre un excutable appel

first.exe

(ou juste

first

sous Linux).

Avec Borland, on utiliserait :

bcc32 first.obj driver.obj asm_io.obj


Borland utilise le nom du premier chier an de dterminer le nom de l'excutable. Donc, dans le cas ci-dessus, le programme s'appelera

first.exe.

Il est possible de combiner les tapes de compilation et d'dition de liens.


Par exemple :

gcc -o first driver.c first.o asm_io.o


Maintenant

gcc

compilera

driver.c

puis liera.

1.4.

25

CRER UN PROGRAMME

1.4.6 Comprendre un listing assembleur


L'option

-l fichier-listing

peut tre utilise pour indiquer

nasm

de crer un chier listing avec le nom donn. Ce chier montre comment


le code a t assembl. Voici comment les lignes 17 et 18 (dans le segment
data) apparassent dans le chier listing (les numros de ligne sont dans le
chier listing ; cependant, notez que les numro de ligne dans le chier source
peuvent ne pas tre les mmes).

48
49
50
51
52

00000000
00000009
00000011
0000001A
00000023

456E7465722061206E756D6265723A2000
456E74657220616E6F74686572206E756D6265723A2000

Les nombres dirent sur

prompt1 db
prompt2 db

la version franaise car ce

"Entrez un nombre : ne
", sont
0

"Entrez un autre nombre : ", 0

La premire colonne de chaque ligne est le numro de la ligne et la seconde est


le dplacement (en hexa) de la donne dans le segment. La troisime colonne
montre les donnes hexa brutes qui seront stockes. Dans ce cas, les donnes hexa correspondent aux codes ASCII. Enn, le texte du chier source
est ach sur la droite. Les dplacements lists dans la seconde colonne ne
sont trs probalement

pas

les dplacements rels auxquels les donnes seront

places dans le programme termin. Chaque module peut dnir ses propres
labels dans le segment de donnes (et les autres segments galement). Lors
de l'tape d'dition de liens (voir Section 1.4.5), toutes ces dnitions de
labels de segment de donnes sont combines pour ne former qu'un segment
de donnes. Les dplacements naux sont alors calculs par l'diteur de liens.
Voici une petite portion (lignes 54 56 du chier source) du segment text
dans le chier listing :

94 0000002C A1[00000000]
95 00000031 0305[04000000]
96 00000037 89C3

mov
add
mov

eax, [input1]
eax, [input2]
ebx, eax

La troisime colonne montre le code machine gnr par l'assembleur. Souvent le code complet pour une instruction ne peut pas encore tre calcul.
Par exemple, ligne 94, le dplacement (ou l'adresse) de

input1

n'est pas

connu avant que le code ne soit li. L'assembleur peut calculer l'opcode pour
l'instruction

mov (qui, d'aprs le listing, est A1), mais il crit le dplacement

entre crochets car la valeur exacte ne peut pas encore tre calcule. Dans
ce cas, un dplacement temporaire de 0 est utilis car

input1

est au dbut

du segment bss dclar dans ce chier. Souvenez vous que cela ne signie

pas

qu'il sera au dbut du segment bss nal du programme. Lorsque le code

est li, l'diteur de liens insrera le dplacement correct la place. D'autres


instructions, comme ligne 96, ne font rfrence aucun label. Dans ce cas,
l'assembleur peut calculer le code machine complet.

pas les mmes ca-

ractres.

26

CHAPITRE 1.

INTRODUCTION

Rprsentations Big et Little Endian


Si l'on regarde attentivement la ligne 95, quelque chose semble trs bizarre propos du dplacement enrte crochets dans le code machine. Le label

input2

est au dplacement 4 (comme dni dans ce chier) ; cepedant, le

dplacement qui apparat en mmoire n'est pas 00000004 mais 04000000.


Pourquoi ? Des processeurs dirents stockent les entiers multioctets dans
des ordres dirents en mmoire. Il y a deux mthodes populaires pour stoEndian
comme

est
indian.

prononc cker les entiers :

big endian

et

little endian.

Le big endian est la mthode

i.e.

qui semble la plus naturelle. L'octet le plus fort (


est stock en premier, puis le second plus fort,

etc.

le plus signicatif )

Par exemple, le dword

00000004 serait stock sous la forme des quatre octets suivants : 00 00 00 04.
Les mainframes IBM, la plupart des processeurs RISC et les processeur Motorola utilisent tous cette mthode big endian. Cependant, les processeurs de
type Intel utilisent la mthode little endian ! Ici, l'octet le moins signicatif
est stock en premier. Donc, 00000004 est stock en mmoire sous la forme
04 00 00 00. Ce format est cod en dur dans le processeur et ne peut pas
tre chang. Normalement, le programmeur n'a pas besoin de s'inquiter du
format utilis. Cependant, il y a des circonstances o c'est important.
1. Lorsque des donnes binaires sont transfres entre dirents ordinateurs (soit via des chiers, soit via un rseau).
2. Lorsque des donnes binaires sont crites en mmoire comme un entier
multioctet puis relues comme des octets individuels ou

vice versa.

Le caractre big ou little endian ne s'applique pas l'ordre des lments


d'un tableau. Le premier lment d'un tableau est toujours l'adresse la
plus petite. C'est galement valable pour les chanes (qui sont juste des tableaux de caractres). Cependant, le caractre big ou little endian s'applique
toujours aux lments individuels des tableaux.

1.5

Fichier Squelette

La Figure 1.7 montre un chier squelette qui peut tre utilis comme
point de dpart pour l'criture de programmes assembleur.

1.5.

1
2
3
4
5

27

FICHIER SQUELETTE

skel.asm
%include "asm_io.inc"
segment .data
;
; Les donnees initialisees sont placees dans ce segment de donnees
;

6
7
8
9
10

segment .bss
;
; Les donnees non initialisees sont placees dans le segment bss
;

11
12
13
14
15
16

segment .text
global _asm_main
_asm_main:
enter 0,0
pusha

; initialisation

17
18
19
20
21

;
; Le code est place dans le segment text. Ne modifiez pas le code
; place avant ou apres ce commentaire.
;

22
23
24
25
26

popa
mov
leave
ret

eax, 0

; retour au C
skel.asm

Fig. 1.7  Squelette de Programme

28

CHAPITRE 1.

INTRODUCTION

Chapitre 2

Bases du Langage Assembleur


2.1

Travailler avec les Entiers

2.1.1 Reprsentation des entiers


Les entiers se dcomposent en deux catgories : signs et non signs. Les
entiers non signs (qui sont positifs) sont reprsents d'une manire binaire
trs intuitive. Les nombre 200 en tant qu'entier non sign sur un octet serait
reprsent par 11001000 (ou C8 en hexa).
Les entiers signs (qui peuvent tre positifs ou ngatifs) sont reprsents

56. +56 serait rereprsenter 56 comme

d'une faon plus complique. Par exemple, considrons


prsent par l'octet 00111000. Sur papier, on peut

111000,

mais comment cela serait-il reprsent dans un octet en mmoire

de l'ordinateur. Comment serait stock le signe moins ?


Il y a trois techniques principales qui ont t utilises pour reprsenter les
entiers signs dans la mmoire de l'ordinateur. Toutes ces mthodes utilisent
le bit le plus signicatif de l'entier comme

bit de signe.

Ce bit vaut 0 si le

nombre est positif et 1 s'il est ngatif.

Grandeur signe
La premire mthode est la plus simple, elle est appele

grandeur signe.

Elle reprsente l'entier en deux parties. La premire partie est le bit de signe
et la seconde est la grandeur entire. Donc 56 serait reprsent par l'octet

00111000

56 serait 10111000. La valeur


01111111 soit +127 et la plus petite valeur
11111111 soit 127. Pour obtenir l'oppos d'une valeur,

(le bit de signe est soulign) et

d'octet la plus grande serait


sur un octet serait

on inverse le bit de signe. Cette mthode est intuitive mais a ses inconvnients. Tout d'abord, il y a deux valeurs possibles pour 0 :

0 (10000000).

+0 (00000000)

et

Comme zro n'est ni positif ni ngatif, ces deux reprsenta-

tions devraient se comporter de la mme faon. Cela complique la logique de


29

30

CHAPITRE 2.

BASES DU LANGAGE ASSEMBLEUR

l'arithmtique pour le processeur. De plus, l'arithmtique gnrale est galement complique. Si l'on ajoute 10

56,

cela doit tre transform en 10

moins 56. L encore, cela complique la logique du processeur.

Complment 1
La seconde mthode est appele reprsentation en

complment un.

Le complment un d'un nombre est trouv en inversant chaque bit du


nombre (une autre faon de l'obtenir est que la valeur du nouveau bit est

1 anciennevaleurdubit). Par exemple,


(+56) est 11000111. Dans la notation en

le complment un de

plment un est quivalent la ngation. Donc


tion de

56.

00111000

complment un, calculer le com-

11000111

est la reprsenta-

Notez que le bit de signe a t automatiquement chang par le

complment un et que, comme l'on s'y attendait, appliquer le complment


un deux fois redonne le nombre de dpart. Comme pour la premire m-

00000000 (+0)

thode, il y a deux reprsentation pour 0 :

et

11111111 (0).

L'arithmtique des nombres en complment un est complique.


Voici une astuce utile pour trouver le complment un d'un nombre en
hexadcimal sans repasser en binaire. L'astuce est d'ter le chire hexa de
F (ou 15 en dcimal). Cette mthode suppose que le nombre de bits dans le
nombre est multiple de 4. Voici un exemple :

+56

est reprsent par 38 en

hexa. pour trouver le complment un, tez chaque chire de F pour obtenir
C7 en hexa. Cela concorde avec le rsultat ci-dessus.

Complment deux
Les deux premires mthodes dcrites ont t utilises sur les premiers
ordinateurs. Les ordinateurs modernes utilisent une troisime mthode appele reprsentation en

complment deux.

On trouve le e complment

deux d'un nombre en eectuant les deux oprations suivantes :


1. Trouver le complment un du nombre
2. Ajouter un au rsultat de l'tape 1
Voici un exemple en utilisant
complment un :

11000111.

00111000

(56). Tout d'abord, on calcule le

Puis, on ajoute un :

11000111
+
1
11001000
Dans la notation en complment deux, calculer le complment deux
est quivalent trouver l'oppos d'un nombre. Donc,
sentation en complment deux de

56.

11001000

est la repr-

Deux ngations devraient repro-

duire le nombre original. Curieusement le complment deux ne rempli pas

2.1.

31

TRAVAILLER AVEC LES ENTIERS

Nombre

Reprsentation Hexa

00

01

127

7F

-128

80

-127

81

-2

FE

-1

FF

Tab. 2.1  Two's Complement Representation

cette condition. Prenez le complment deux de

11001000

en ajoutant un

au complment un.

00110111
+
1
00111000
Lorsque l'on eectue l'addition dans l'opration de complmentation
deux, l'addition du bit le plus gauche peut produire une retenue. Cette retenue n'est

pas utilise. Souvenez vous que toutes les donnes sur un ordinateur

sont d'une taille xe (en terme de nombre de bits). Ajouter deux octets produit toujours un rsultat sur un octet (comme ajouter deux mots donne un
mot,

etc .)

Cette proprit est importante pour la notation en complment

deux. Par exemple, considrons zro comme un nombre en complment


deux sur un octet (00000000). Calculer son complment deux produit la
somme :

11111111
+
1
c 00000000
o

reprsente une retenue (Plus tard, nous montrerons comment dtecter

cette retenue, mais elle n'est pas stocke dans le rsultat). Donc, en notation
en complment deux, il n'y a qu'un zro. Cela rend l'arithmtique en
complment deux plus simple que les mthodes prcdentes.
En utilisant la notation en complment deux, un octet sign peut tre
utilis pour reprsenter les nombres

128

+127.

Le Tableau 2.1 montre

32, 768
+32, 767 peuvent tre reprsents. +32, 767 est reprsent par 7FFF, 32, 768

quelques valeurs choisies. Si 16 bits sont utiliss, les nombres signs

par 8000, -128 par FF80 et -1 par FFFF. Les nombres en complment deux
sur 32 bits vont de

milliards

+2

milliards environ.

Le processeur n'a aucune ide de ce qu'un octet en particulier (ou un mot


ou un double mot) est suppos reprsent. L'assembleur n'a pas le concept
de types qu'un langage de plus haut niveau peut avoir. La faon dont les
donnes sont interprtes dpendent de l'instruction dans laquelle on les

32

CHAPITRE 2.

BASES DU LANGAGE ASSEMBLEUR

utilise. Que la valeur FF soit considre comme reprsentant un


un

+255

1 sign ou

non sign dpend du programmeur. Le langage C dnit des types

entiers signs et non signs. Cela permet au compilateur C de dterminer les


instructions correctes utiliser avec les donnes.

2.1.2 Extension de signe


En assembleur, toutes les donnes ont une taille bien spcie. Il n'est
pas rare de devoir changer la taille d'une donne pour l'utiliser avec d'autres.
Rduire la taille est le plus simple.

Rduire la taille des donnes


Pour rduire la taille d'une donne, il sut d'en retirer les bits les plus
signicatifs. Voici un exemple trivial :

mov
mov

ax, 0034h
cl, al

; ax = 52 (stock sur 16 bits)


; cl = les 8 bits de poids faible de ax

Bien sr, si le nombre ne peut pas tre reprsent correctement dans une
plus petite taille, rduire la taille ne fonctionne pas. Par exemple, si
0134h (ou 308 en dcimal) alors le code ci-dessus mettrait quand

AX valait
mme CL

34h. Cette mthode fonctionne la fois avec les nombres signs et non
signs. Considrons les nombres signs, si
alors
dans

AX valait FFFFh (1 sur un mot),

CL vaudrait FFh (1 sur un octet). Cependant, notez que si la valeur


AX) tait sign, elle aurait t tronqu et le rsultat aurait t faux !

La rgle pour les nombres non signs est que tous les bits retirs soient
0 an que la convertion soit correcte. La rgle pour les nombres signs est
que les bits retirs doivent soit tous tre des 1 soit tous des 0. De plus, Le
premier bit ne pas tre retir doit valoir la mme chose que ceux qui l'ont
t. Ce bit sera le bit de signe pour la valeur plus petite. Il est important
qu'il ait la mme valeur que le bit de signe original !

Augmenter la taille des donnes


Augmenter la taille des donnes est plus compliqu que la rduire. Considrons l'octet hexa FF. S'il est tendu un mot, quelle valeur aura-t-il ? Cela
dpend de la faon dont FF est interprt. Si FF est un octet non sign (255
en dcimal), alors le mot doit tre 00FF ; cependant, s'il s'agit d'un octet
sign (1 en dcimal), alors le mot doit tre FFFF.
En gnral, pour tendre un nombre non sign, on met tous les bits
supplmentaires du nouveau nombre 0. Donc, FF devient 00FF. Cependant,
pour tendre un nombre sign, on doit

tendre

le bit de signe. Cela signie

que les nouveaux bits deviennent des copies du bit de signe. Comme le bit

2.1.

33

TRAVAILLER AVEC LES ENTIERS

de signe de FF est 1, les nouveaux bits doivent galement tre des uns,
pour donner FFFF. Si le nombre sign 5A (90 en dcimal) tait tendu, le
rsultat serait 005A.
Il y a plusieurs instructions fournies par le 80386 pour tendre les nombres.
Souvenez vous que l'ordinateur ne sait pas si un nombre est sign ou non.
C'est au programmeur d'utiliser l'instruction adquate.
Pour les nombres non signs, on peut placer des 0 dans les bits de poids
fort de faon simple en utilisant l'instruction

MOV. Par axemple pour tendre

l'octet de AL en un mot non sign dans AX :

mov

ah, 0

; met les 8 bits de poids for 0

Cependant, il n'est pas possible d'utiliser une instruction

MOV pour convertir

le mot non sign de AX en un double mot non sign dans EAX. Pourquoi
pas ? Il n'y a aucune faon d'accder aux 16 bits de poids fort de EAX
dans un

MOV. Le 80386 rsoud ce problme


MOVZX. Cette instruction a deux

truction :

en fournissant une nouvelle insoprandes. La destination (pre-

mier oprande) doit tre un registre de 16 ou 32 bits. La source (deuxime


oprande) doit tre un registre 8 ou 16 bits. L'autre restriction est que la
destination doit tre plus grande que la source (la plupart des instructions
recquirent que la source soit de la mme taille que la destination). Voici
quelques exemples :

movzx
movzx
movzx
movzx

eax, ax
eax, al
ax, al
ebx, ax

;
;
;
;

tend
tend
tend
tend

ax
al
al
ax

eax
eax
ax
ebx

Pour les nombres signs, il n'y a pas de faon simple d'utiliser l'instruction

MOV

quelque soit le cas. Le 8086 fournit plusieurs instructions pour tendre

les nombres signs. L'instruction

CBW

(Convert Byte to Word, Convertir un

Octet en Mot) tend le signe du registre AL dans AX. Les oprandes sont
implicites. L'instruction

CWD

(Convert Word to Double word, Convertir un

Mot en Double mot) tend le signe de AX dans DX:AX. La notation DX:AX


signie qu'il faut considrer les registres DX et AX comme un seul registre
32 bits avec les 16 bits de poids forts dans DX et les bits de poids faible dans
AX (souvenez vous que le 8086 n'avait pas de registre 32 bits du tout !). Le
80386 a apport plusieurs nouvelles instructions. L'instruction

CWDE (Convert

Word to Double word Extended, convertir un mot en double mot tendue)


tend le signe de AX dans EAX. L'instruction

CDQ (Convert Double word to

Quad word, Convertir un Double mot en Quadruple mot) tend le signe de


EAX dans EDX:EAX (64 bits !) Enn, l'instruction

MOVZX

MOVSX fonctionne comme

except qu'elle utilise les rgles des nombres signs.

34

1
2
3
4

CHAPITRE 2.

BASES DU LANGAGE ASSEMBLEUR

unsigned char uchar = 0xFF;


signed char schar = 0xFF;
int a = (int ) uchar ;
/ a = 255 (0x000000FF) /
int b = (int ) schar ;
/ b = 1 (0xFFFFFFFF) /
Fig. 2.1  Extension de signe en C

char ch;
while ( (ch = fgetc(fp )) != EOF ) {
/ faire qqch avec ch /
}
Fig. 2.2  Utilisation de la fonction fgetc

Application la programmation en C
L'extension d'entiers signs et non signs a galement lieu en C. Les

Le C ANSI ne dnit pas


si le type

char est sign

ou variables en C peuvent tre dclares soit comme signes soit comme non
non, c'est chaque compi- signes (les int sont signs). Considrons le code de la Figure 2.1. A la
lateur de le dcider. C'est
ligne 3, la variable a est tendue en utilisant les rgles pour les valeurs non
pourquoi on dnit le type
signes (avec MOVZX), mais la ligne 4, les rgles signes sont utilises pour
explicitement dans la Fib (avec MOVSX).
gure 2.1.
Il existe un bug de programmation courant en C qui est directement

li notre sujet. Considrons le code de la Figure 2.2. La prototype de

fgetc()est

int fgetc( FILE * );


On peut se demander pourquoi est-ce que la fonction renvoie un

int

qu'elle lit des caractres ? La raison est qu'elle renvoie normalement un


(tendu une valeur

int

puis-

char

en utilisant l'extension de zro). Cependant, il y a

EOF. C'est une


1. Donc, fgetc() retourne soit
ressemble 000000xx en hexa) soit

une valeur qu'elle peut retourner qui n'est pas un caractre,


macro habituellement dnie comme valant

char
EOF (qui

un

int
FFFFFFFF en

tendu une valeur

(qui

ressemble

hexa).

Le problme avec le programme de la Figure 2.2 est que


un

int,

mais cette valeur est stocke dans un

char.

fgetc() renvoie

Le C tronquera alors

les bits de poids fort pour faire tenir la valeur de l'int dans le

000000FF et
FF. Donc, la boucle while ne peut pas

seul problme est que les nombres (en hexa)


tous deux tronqu pour donner l'octet
distinguer la lecture de l'octet

FF

char. Le
FFFFFFFF seront

dans le chier et la n de ce chier.

char est
ch est compar avec EOF. Comme

Ce que fait exactement le code dans ce cas change selon que le


sign ou non. Pourquoi ? Parce que ligne 2,

2.1.

35

TRAVAILLER AVEC LES ENTIERS

EOF est un int1 , ch sera tendu un int an que les deux valeurs compares
2

soient de la mme taille . Comme la Figure 2.1 le montrait, le fait qu'une


variable soit signe ou non est trs important.

char n'est pas sign, FF est tendu et donne 000000FF. Cette valeur
compare EOF (FFFFFFFF) et est dirente. Donc la boucle ne nit

Si le
est

jamais !
So le

char

est sign,

FF

est tendu et donne

FFFFFFFF.

Les deux valeurs

sont alors gale et la boucle se termine. Cependant, comme l'octet

FF

peut

avoir t lu depuis le chier, la boucle peut se terminer prmaturment.


La solution ce problme est de dnir la variable
pas un

char.

ch

comme un

int,

Dans ce cas, aucune troncation ou extension n'est eectue

la ligne 2. Dans la boucle, il est sans danger de tronquer la valeur puisque

ch doit

alors tre rellement un octet.

2.1.3 Arithmtique en complment deux


Comme nous l'avons vu plus tt, l'instruction
tion et l'instruction

sub

carry ag

eectue une addi-

eectue une soustraction. Deux des bits du registre

overow

EFLAGS que ces instructions positionnent sont


capacit) et

add

(dpassement de

(retenue). Le drapeau d'overow est 1 si le rsultat

rel de l'opration est trop grand et ne tient pas dans la destination pour
l'arithmtique signe. Le drapeau de retenue est 1 s'il y a une retenue dans
le bit le plus signicatif d'une addition d'une soustraction. Donc, il peut
tre utilis pour dtecter un dpassement de capacit en arithmtique non
signe. L'utilisation du drapeau de retenue pour l'arithmtique signe va tre
vu sous peu. Un des grands avantages du complment 2 est qu les rgles
pour l'addition et la soustraction sont exactement les mmes que pour les
entiers non signs. Donc,

add

et

sub

peuvent tre utilises pour les entiers

signs ou non.

002C
+ FFFF
002B

44
+ (1)
43

Il y a une retenue de gnre mais elle ne fait pas partie de la rponse.


Il y a deux instructions de multiplication et de division direntes. Tout
d'abord, pour multiplier, utilisez les instructions

MUL

MUL

ou

IMUL. L'instruction
IMUL est utili-

est utilise pour multiplier les nombres non signs et

se pour multiplier les entiers signs. Pourquoi deux instructions direntes


sont ncessaires ? Les rgles pour la multiplication sont direntes pour les
nombres non signs et les nombres signs en complment 2. Pourquoi cela ?

Il est courant de penser que les chiers ont un carctre EOF comme dernier caractre.
Ce n'est pas vrai !
2
La raison de cette ncessit sera expose plus tard.

36

CHAPITRE 2.

dest

source1

BASES DU LANGAGE ASSEMBLEUR

source2

reg/mem8

Action
AX = AL*source1

reg/mem16

DX:AX = AX*source1

reg/mem32

EDX:EAX = EAX*source1

reg16

reg/mem16

dest *= source1

reg32

reg/mem32

dest *= source1

reg16

immed8

dest *= immed8

reg32

immed8

dest *= immed8

reg16

immed16

dest *= immed16

reg32

immed32

dest *= immed32

reg16

reg/mem16

immed8

dest = source1*source2

reg32

reg/mem32

immed8

dest = source1*source2

reg16

reg/mem16

immed16

dest = source1*source2

reg32

reg/mem32

immed32

dest = source1*source2

Tab. 2.2 

Instructions imul

Considrons la multiplication de l'octet FF avec lui-mme donnant un rsultat sur un mot. En utilisant la multiplication non signe, il s'agit de 255
fois 255 soit 65025 (ou FE01 en hexa). En utilisant la multiplication signe,
il s'agit de

fois

soit 1 (ou 0001 en hexa).

Il y a plusieurs formes pour les instructions de multiplication. La plus


ancienne ressemble cela :

mul
La

source

source

est soit un registre soit une rfrence mmoire. Cela ne peut pas

tre une valeur immdiate. Le type exact de la multiplication dpend de la


taille de l'oprande source. Si l'oprande est un octet, elle est multiplie par
l'octet situ dans le registre AL et le rsultat est stock dans les 16 bits de
AX. Si la source fait 16 bits, elle est multiplie par le mot situ dans AX et
le rsultat 32 bits est stock dans DX:AX. Si la source fait 32 bits, elle est
multiplie par EAX et le rsultat 64 bits est stock dans EDX:EAX.
L'instruction

IMUL

a les mmes formats que

MUL,

mais ajoute galement

d'autres formats d'instruction. Il y a des formats deux et trois oprandes :

imul
imul

dest, source1
dest, source1, source2

Le Tableau 2.2 montre les combinaisons possibles.


Les deux oprateurs de division sont

DIV

et

IDIV.

Il eectuent respec-

tivement une division entire non signe et une division entire signe. Le
format gnral est :

div

source

2.1.

37

TRAVAILLER AVEC LES ENTIERS

Si la source est sur 8 bits, alors AX est divis par l'oprande. Le quotient est
stock dans AL et le reste dans AH. Si la source est sur 16 bits, alors DX:AX
est divis par l'oprande. Le quotient est stock dans AX et le reste dans
DX. Si la source est sur 32 bits, alors EDX:EAX est divis par l'oprande,

IDIV
IDIV spciales comme

le quotient est stock dans EAX et le reste dans EDX. L'instruction


fonctionne de la mme faon. Il n'y a pas d'instructions
pour

IMUL.

Si le quotient est trop grand pour tenir dans son registre ou que

le diviseur vaut 0, le programme et interrompu et se termine. Une erreur


courante est d'oublier d'initialiser DX ou EDX avant la division.
L'instruction

NEG

inverse son oprande en calculant son complment

deux. L'oprande peut tre n'importe quel registre ou emplacement mmoire


sur 8 bits, 16 bits, or 32 bits.

2.1.4 Programme exemple


1
2
3
4
5
6
7
8
9

%include "asm_io.inc"
segment .data
prompt
db
square_msg
db
cube_msg
db
cube25_msg
db
quot_msg
db
rem_msg
db
neg_msg
db

math.asm
; Chaines affichees
"Entrez un nombre : ", 0
"Le carre de l'entree vaut ", 0
"Le cube de l'entree vaut ", 0
"Le cube de l'entree fois 25 vaut ", 0
"Le quotient de cube/100 vaut ", 0
"Le reste de cube/100 vaut ", 0
"La ngation du reste vaut ", 0

10
11
12

segment .bss
input resd 1

13
14
15
16
17
18

segment .text
global _asm_main
_asm_main:
enter 0,0
pusha

; routine d'initialisation

19
20
21

mov
call

eax, prompt
print_string

call
mov

read_int
[input], eax

imul
mov

eax
ebx, eax

22
23
24
25
26
27

; edx:eax = eax * eax


; sauvegarde le rsultat dans ebx

38

28
29
30
31
32

CHAPITRE 2.

mov
call
mov
call
call

eax, square_msg
print_string
eax, ebx
print_int
print_nl

mov
imul
mov
call
mov
call
call

ebx, eax
ebx, [input]
eax, cube_msg
print_string
eax, ebx
print_int
print_nl

imul
mov
call
mov
call
call

ecx, ebx, 25
eax, cube25_msg
print_string
eax, ecx
print_int
print_nl

mov
cdq
mov
idiv
mov
mov
call
mov
call
call
mov
call
mov
call
call

eax, ebx
ecx, 100
ecx
ecx, eax
eax, quot_msg
print_string
eax, ecx
print_int
print_nl
eax, rem_msg
print_string
eax, edx
print_int
print_nl

neg
mov
call
mov
call

edx
eax, neg_msg
print_string
eax, edx
print_int

BASES DU LANGAGE ASSEMBLEUR

33
34
35
36
37
38
39
40

; ebx *= [entree]

41
42
43
44
45
46
47

; ecx = ebx*25

48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63

;
;
;
;

initialise edx avec extension de signe


on ne peut pas diviser par une valeur immdiate
edx:eax / ecx
sauvegarde le rsultat dans ecx

64
65
66
67
68
69

; inverse le reste

2.1.

70

39

TRAVAILLER AVEC LES ENTIERS

call

print_nl

71
72
73
74
75

popa
mov
leave
ret

eax, 0

; retour au C
math.asm

2.1.5 Arithmtique en prcision tendue


Le langage assembleur fournit galement des instructions qui permettent
d'eectuer des additions et des soustractions sur des nombres plus grands que
des doubles mots. Ces instructions utilisent le drapeau de retenue. Comme
nous l'avons dit plus haut, les instructions

ADD

et

SUB

modient le drapeau

de retenue si une retenue est gnre. Cette information stocke dans le drapeau de retenue peut tre utilise pour additionner ou soustraire de grands
nombres en morcelant l'opration en doubles mots (ou plus petit).
Les instructions

ADC

et

SBB

utilisent les informations donnes par le dra-

ADC eectue l'opration suivante :


oprande1 = oprande1 + drapeau de retenue + oprande2

peau de retenue. L'instruction

L'instruction

SBB

eectue :

oprande1 = oprande1 - drapeau de retenue - oprande2


Comment sont elles utilises ? Considrons la somme d'entiers 64 bits dans
EDX:EAX et EBX:ECX. Le code suivant stockerait la somme dans EDX:EAX :

1
2

add
adc

eax, ecx
edx, ebx

; additionne les 32 bits de poids faible


; additionne les 32 bits de poids fort et la retenue

La soustraction est trs similaire. Le code suivant soustrait EBX:ECX de


EDX:EAX :

1
2

sub
sbb

eax, ecx
edx, ebx

Pour les nombres

; soustrait les 32 bits de poids faible


; soustrait les 32 bits de poids fort et la retenue

vraiment

grands, une boucle peut tre utilise (voir

Section 2.2). Pour une boucle de somme, il serait pratique d'utiliser l'instruction

ADC

pour toutes les itrations (au lieu de toutes sauf la premire).

Cela peut tre fait en utilisant l'instruction

CLC

(CLear Carry) juste avant

que la boucle ne commence pour initialiser le drapeau de retenue 0. Si le


drapeau de retenue vaut 0, il n'y a pas de dirence entre les instructions

ADD

et

ADC.

La mme ide peut tre utilise pour la soustraction.

40

CHAPITRE 2.

2.2

BASES DU LANGAGE ASSEMBLEUR

Structures de Contrle

Les langages de haut niveau fournissent des structures de contrle de haut

p.e., les instructions if

niveau (

et

while ) qui contrlent le ot d'excution. Le

langage assembleur ne fournit pas de structures de contrle aussi complexes.

goto

Il utilise la place l'infme

et l'utiliser de manire inapproprie peut

conduire du code spaghetti ! Cependant, il

est

possible d'crire des pro-

grammes structurs en assembleur. La procdure de base est de concevoir la


logique du programme en utilisant les structures de contrle de haut niveau
habituelles et de traduire cette conception en langage assembleur (comme le
ferait un compilateur).

2.2.1 Comparaison
Les structures de contrle dcident ce qu'il faut faire en comparant des
donnes. En assembleur, le rsultat d'une comparaison est stock dans le
registre FLAGS pour tre utilis plus tard. Le 80x86 fournit l'instruction

CMP pour eectuer des comparaisons. Le registre FLAGS est positionn selon
la dirence entre les deux oprandes de l'instruction CMP. Les oprandes
sont soustraites et le registre FLAGS est positionn selon le rsultat, mais
ce rsultat n'est stock
l'instruction

SUB

nulle part.

au lieu de

Si vous avez besoin du rsultat, utilisez

CMP.

Pour les entiers non signs, il y a deux drapeaux (bits dans le registre
FLAGS) qui sont importants : les drapeaux zro (ZF) et retenue (CF) . Le
drapeau zero est allum (1) si le rsultat de la dirence serait 0. Considrons
une comparaison comme :

cmp

vleft, vright

La dirence

vleft - vright

est calcule et les drapeaux sont positionns

en fonction. Si la dirence calcule par

i.e.

ZF est allum(

1) et CF est teint (

teint et CF galement. Si

CMP

i.e.

vleft = vright, alors


vleft > vright, ZF est

vaut 0,
0). Si

vleft < vright,

alors ZF est teint et CF est

allum (retenue).
Pour les entiers signs, il y a trois drapeaux importants : le drapeau
Pourquoi

SF

vleft > vright ?

OF

si zro (ZF), le drapeau d'overow (OF) et le drapeau de signe (SF). Le dra-

S'il n'y peau d'overow est allum si le rsultat d'une opration dpasse la capaa pas d'overow, alors cit (de trop ou de trop peu). Le drapeau de signe est allum si le rsultat
la dirence aura la vad'une opration est ngatif. Si vleft = vright, ZF est allum (comme pour
leur correcte et doit tre
les entiers non signs). Si vleft > vright, ZF est teint et SF = OF. Si
positive ou nulle. Donc,
vleft < vright, ZF est teint et SF 6= OF.
SF = OF = 0. Par contre,
N'oubliez pas que d'autres instructions peuvent aussi changer le registre
s'il y a un overow, la
dirence
valeur
en

fait

n'aura

correcte

pas
(et

ngative).

SF = OF = 1.

la FLAGS, pas seulement

sera
Donc,

CMP.

2.2.

41

STRUCTURES DE CONTRLE

2.2.2 Instructions de branchement


Les instructions de branchement peuvent transfrer l'excution n'importe quel point du programme. En d'autres termes, elles agissent comme un

goto. Il y a deux types de branchements : conditionnel et inconditionnel. Un

branchement inconditionnel est exactement comme un goto, il eectue toujours le branchement. Un branchement conditionnel peut eectuer le branchement ou non selon les drapeaux du registre FLAGS. Si un branchement
conditionnel n'eecute pas le branchement, le contrle passe l'instruction
suivante.
L'instruction

JMP

(abbrviation de

jump )

eectue les branchements in-

conditionnels. Son seul argument est habituellement l'

tiquette de code

de

l'instruction laquelle se brancher. L'assembleur ou l'diteur de liens remplacera l'tiquette par l'adresse correcte de l'instruction. C'est une autre des
oprations pnibles que l'assembleur fait pour rendre la vie du programmeur
plus simple. Il est important de raliser que l'instruction immdiatement
aprs l'instruction

JMP ne sera jamais excute moins qu'une autre instruc-

tion ne se branche dessus !


Il y a plusieurs variantes de l'instruction de saut :

SHORT

Ce saut est trs limit en porte. Il ne peut bouger que de plus

ou moins 128 octets en mmoire. L'avantage de ce type de saut est


qu'il utilise moins de mmoire que les autres. Il utilise un seul octet sign pour stocker le

dplacement

du saut. Le dplacement est le

nombre d'octets sauter vers l'avant ou vers l'arrire (le dplacement


est ajout EIP). Pour spcier un saut court, utilisez le mot cl
immdiatement avant l'tiquette dans l'instruction

NEAR

JMP.

SHORT

Ce saut est le saut par dfaut, que ce soit pour les branchements

inconditionnels ou conditionnels, il peut tre utilis pour sauter vers


n'importe quel emplacement dans un segment. En fait, le 80386 supporte deux types de sauts proches. L'un utilise 2 ocets pour le dplacement. Cela permet de se dplacer en avant ou en arrire d'environ
32 000 octets. L'autre type utilise quatre octets pour le dplacement,
ce qui, bien sr, permet de se dplacer n'importe o dans le segment.
Le type de saut deux octets peut tre spci en plaant le mot cl

WORD

FAR

avant l'tiquette dans l'instruction

JMP.

Ce saut permet de passer le contrle un autre segment de code. C'est


une chose trs rare en mode protg 386.

Les tiquettes de code valides suivent les mmes rgles que les tiquettes
de donnes. Les tiquettes de code sont dnies en les plaant dans le segment
de code au dbut de l'instruction qu'ils tiquettent. Deux points sont placs
la n de l'tiquette lors de sa dnition. Ces deux points ne font
du nom.

pas

partie

42

CHAPITRE 2.

BASES DU LANGAGE ASSEMBLEUR

JZ

saute uniquement si ZF est allum

JNZ

saute uniquement si ZF est teint

JO

saute uniquement si OF est allum

JNO

saute uniquement si OF est teint

JS

saute uniquement si SF est allum

JNS

saute uniquement si SF est teint

JC

saute uniquement si CF est allum

JNC

saute uniquement si CF est teint

JP

saute uniquement si PF est allum

JNP

saute uniquement si PF est teint

Tab. 2.3  Branchements Conditionnels Simples

Il y a beaucoup d'instructions de branchement conditionnel direntes.


Elles prennent toutes une tiquette de code comme seule oprande. Les plus
simples se contentent de regarder un drapeau dans le registre FLAGS pour
dterminer si elles doivent sauter ou non. Voyez le Tableau 2.3 pour une liste
de ces instructions (PF est le

drapeau de parit

qui indique si le nombre de

bits dans les 8 bits de poids faible du rsultat est pair ou impair).
Le pseudo-code suivant :

if ( EAX == 0 )
EBX = 1;
else
EBX = 2;
peut tre crit de cette faon en assembleur :

1
2
3
4
5
6
7

cmp
jz
mov
jmp
thenblock:
mov
next:

eax, 0
thenblock
ebx, 2
next

;
;
;
;

positionne les drapeaux (ZF allum si eax - 0 = 0)


si ZF est allum, branchement vers thenblock
partie ELSE du IF
saute par dessus la partie THEN du IF

ebx, 1

; partie THEN du IF

Les autres comparaisons ne sont pas si simples si l'on se contente des


branchements conditionnels du Tableau 2.3. Pour illustrer, considrons le
pseudo-code suivant :

if ( EAX >= 5 )
EBX = 1;
else
EBX = 2;

2.2.

43

STRUCTURES DE CONTRLE

Sign
JE

saute si

JNE

saute si

JL, JNGE

saute si

JLE, JNG

saute si

JG, JNLE

saute si

JGE, JNL

saute si

vleft
vleft
vleft
vleft
vleft
vleft

= vright
6= vright
< vright
vright
> vright
vright

Non Sign
JE

saute si

JNE

saute si

JB, JNAE

saute si

JBE, JNA

saute si

JA, JNBE

saute si

JAE, JNB

saute si

vleft
vleft
vleft
vleft
vleft
vleft

= vright
6= vright
< vright
vright
> vright
vright

Tab. 2.4  Instructions de Comparaison Signes et Non Signes

Si EAX est plus grand ou gal 5, ZF peut tre allum ou non et SF sera
gal OF. Voici le code assembleur qui teste ces conditions (en supposant
que EAX est sign) :

1
2
3
4
5
6
7
8
9
10
11
12

cmp
js
jo
jmp
signon:
jo
elseblock:
mov
jmp
thenblock:
mov
next:

eax, 5
signon
elseblock
thenblock

; goto signon si SF = 1
; goto elseblock si OF = 1 et SF = 0
; goto thenblock si SF = 0 et OF = 0

thenblock

; goto thenblock si SF = 1 et OF = 1

ebx, 2
next
ebx, 1

Le code ci-dessus est trs confus. Heureusement, le 80x86 fournit des


instructions de branchement additionnelles pour rendre ce type de test

coup

beau-

plus simple. Il y a des versions signes et non signes de chacune. Le

Tableau 2.4 montre ces instructions. Les branchements gal et non gal (JE
et JNE) sont les mmes pour les entiers signs et non signs (en fait, JE et
JNE sont rellement identiques JZ et JNZ, respectivement). Chacune des
autres instructions de branchement a deux synonymes. Par exemple, regardez JL (jump less than, saut si infrieur ) et JNGE (jump not greater than
or equal to, saut si non plus grand ou gal). Ce sont les mmes instructions
car :

x < y = not(x y)
Les branchements non signs utilisent A pour

below

above

(au dessus) et B pour

(en dessous) la place de L et G.

En utilisant ces nouvelles instructions de branchement, le pseudo-code


ci-dessus peut tre traduit en assembleur plus facilement.

44

1
2
3
4
5
6
7

CHAPITRE 2.

cmp
jge
mov
jmp
thenblock:
mov
next:

BASES DU LANGAGE ASSEMBLEUR

eax, 5
thenblock
ebx, 2
next
ebx, 1

2.2.3 Les instructions de boucle


Le 80x86 fournit plusieurs instructions destines implmenter des boucles
de style

for. Chacune de ces instructions prend une tiquette de code comme

seule oprande.

LOOP Decrmente ECX, si ECX 6= 0, saute vers l'tiquette


LOOPE, LOOPZ Dcremente ECX (le registre FLAGS n'est pas modi),
si ECX

6=

0 et ZF = 1, saute

LOOPNE, LOOPNZ

Dcremente ECX (FLAGS inchang), si ECX

6=

et ZF = 0, saute
Les deux dernires instructions de boucle sont utiles pour les boucles de
recherche squentielle. Le pseudo-code suivant :

sum = 0;
for ( i=10; i >0; i )
sum += i;
peut tre traduit en assembleur de la faon suivante :

1
2
3
4
5

mov
mov
loop_start:
add
loop
2.3

eax, 0
ecx, 10

; eax est sum


; ecx est i

eax, ecx
loop_start
Traduire les Structures de Contrle Standards

Cette section dcrit comment les structures de contrle standards des


langages de haut niveau peuvent tre implmentes en langage assembleur.

2.3.1 Instructions if
Le pseudo-code suivant :

2.3.

TRADUIRE LES STRUCTURES DE CONTRLE STANDARDS 45

if ( condition )
then_block;
else
else_block;
peut tre implment de cette faon :

1
2
3
4
5
6
7

; code
jxx
; code
jmp
else_block:
; code
endif:

pour positionner FLAGS


else_block
; choisir xx afin de sauter si la condition est fausse
pour le bloc then
endif
pour le bloc else

S'il n'y a pas de else, le branchement


un branchement

1
2
3
4

endif.

else_block peut tre remplac par

; code pour positionner FLAGS


jxx
endif
; choisir xx afin de sauter si la condition est fausse
; code pour le bloc then
endif:

2.3.2 Boucles while


while est une boucle avec un test au dbut :
while ( condition ) {
corps de la boucle;
}
La boucle

Cela peut tre traduit en :

1
2
3
4
5
6

while:
; code
jxx
; body
jmp
endwhile:

pour positionner FLAGS selon la condition


endwhile
; choisir xx pour sauter si la condition est fausse
of loop
while

2.3.3 Boucles do while


La boucle

do while

do {
corps de la boucle;
} while ( condition );

est une boucle avec un test la n :

46

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

CHAPITRE 2.

BASES DU LANGAGE ASSEMBLEUR

unsigned guess; / candidat courant pour tre premier /


unsigned factor ; / diviseur possible de guess /
unsigned limit ; / rechercher les nombres premiers jusqu' cette valeur /
printf ("Rechercher les nombres premier jusqu'a : ");
scanf("%u", &limit);
printf ("2\n"); / traite les deux premier nombres premiers comme /
printf ("3\n"); / des cas spciaux /
guess = 5;
/ candidat initial /
while ( guess <= limit ) {
/ recherche un diviseur du candidat /
factor = 3;
while ( factor factor < guess &&
guess % factor != 0 )
factor += 2;
if ( guess % factor != 0 )
printf ("%d\n", guess);
guess += 2; / ne regarder que les nombres impairs /
}
Fig. 2.3 

Cela peut tre traduit en :

1
2
3
4

do:

; corps de la boucle
; code pour positionner FLAGS selon la condition
jxx
do
; choisir xx pour se brancher si la condition est vraie

2.4

Exemple : Trouver des Nombres Premiers

Cette section dtaille un programme qui trouve des nombres premiers.


Rappelez vous que les nombres premiers ne sont divisibles que par 1 et par
eux-mmes. Il n'y a pas de formule pour les rechercher. La mthode de base
qu'utilise ce programme est de rechercher les diviseurs de tous les nombres
impairs

3 sous une limite donne. Si aucun facteur ne peut tre trouv pour

un nombre impair, il est premier. La Figure 2.3 montre l'algorithme de base


crit en C.
Voici la version assembleur :

2 est le seul nombre premier pair.

2.4.

1
2
3

EXEMPLE : TROUVER DES NOMBRES PREMIERS

47

prime.asm

%include "asm_io.inc"
segment .data
Message
db

"Rechercher les nombres premiers jusqu'a : ", 0

segment .bss
Limit
Guess

1
1

4
5
6
7

resd
resd

; rechercher les nombres premiers jusqu' cette limite


; candidat courant pour tre premier

8
9
10
11
12
13

segment .text
global _asm_main
_asm_main:
enter 0,0
pusha

; routine d'intialisation

14
15
16
17
18

mov
call
call
mov

eax, Message
print_string
read_int
[Limit], eax

mov
call
call
mov
call
call

eax, 2
print_int
print_nl
eax, 3
print_int
print_nl

; printf("2\n");

dword [Guess], 5

; Guess = 5;
; while ( Guess <= Limit )

; scanf("%u", &limit);

19
20
21
22
23
24
25

; printf("3\n");

26
27
28
29
30
31

mov
while_limit:
mov
cmp
jnbe

eax,[Guess]
eax, [Limit]
end_while_limit

; on utilise jnbe car les nombres ne sont pas signes

32
33
34
35
36
37
38
39
40
41

mov
while_factor:
mov
mul
jo
cmp
jnb
mov
mov

ebx, 3
eax,ebx
eax
end_while_factor
eax, [Guess]
end_while_factor
eax,[Guess]
edx,0

; ebx est factor = 3;


; edx:eax = eax*eax
; si la rponse ne tient pas dans eax seul
; si !(factor*factor < guess)

48

42
43
44

CHAPITRE 2.

div
cmp
je

ebx
edx, 0
end_while_factor

BASES DU LANGAGE ASSEMBLEUR

; edx = edx:eax % ebx


; si !(guess % factor != 0)

45
46
47
48
49
50
51
52
53
54
55
56

add
ebx,2
jmp
while_factor
end_while_factor:
je
end_if
mov
eax,[Guess]
call
print_int
call
print_nl
end_if:
add
dword [Guess], 2
jmp
while_limit
end_while_limit:

; factor += 2;
; si !(guess % factor != 0)
; printf("%u\n")

; guess += 2

57
58
59
60
61

popa
mov
leave
ret

eax, 0

; retour au C
prime.asm

Chapitre 3

Oprations sur les Bits


3.1

Oprations de Dcalage

Le langage assembleur permet au programmeur de manipuler individuellement les bits des donnes. Une opration courante sur les bits est appele

dcalage. Une opration de dcalage dplace la position des bits d'une donne. Les dcalages peuvent tre soit vers la gauche (i.e. vers les bits les plus
signicatifs) soit vers la droite (les bits les moins signicatifs).

3.1.1 Dcalages logiques


Le dcalage logique est le type le plus simple de dcalage. Il dcale d'une
manire trs simple. La Figure 3.1 montre l'exemple du dcalage d'un nombre
sur un octet.
Original

Dcal gauche

Dcal droite

Fig. 3.1  Dcalages logiques

Notez que les nouveaux bits sont toujours 0. Les instructions

SHR

SHL

et

sont respectivement utilises pour dcaler gauche et droite. Ces

instructions permettent de dcaler de n'importe quel nombre de positions.


Le nombre de positions dcaler peut soit tre une contante, soit tre stock
dans le registre

CL.

Le dernier bit dcal de la donne est stock dans le

drapeau de retenue. Voici quelques exemples :

1
2
3
4

mov
shl
shr
shr

ax,
ax,
ax,
ax,

0C123H
1
1
1

; dcale d'1 bit gauche, ax = 8246H, CF = 1


; dcale d'1 bit droite, ax = 4123H, CF = 0
; dcale d'1 bit droite, ax = 2091H, CF = 1
49

50

5
6
7
8

CHAPITRE 3.

mov
shl
mov
shr

ax,
ax,
cl,
ax,

0C123H
2
3
cl

OPRATIONS SUR LES BITS

; dcale de 2 bits gauche, ax = 048CH, CF = 1


; dcale de 3 bits droite, ax = 0091H, CF = 1

3.1.2 Utilisation des dcalages


La multiplication et la division rapides sont les utilisations les plus courantes des oprations de dclage. Rappelez vous que dans le systme dcimal,
la multiplication et la division par une puissance de dix sont simples, on dcale simplement les chires. C'est galement vrai pour les puissances de deux

10112
(ou 11 en dcimal), dcalez d'un cran vers la gauche pour obtenir 101102 (ou

en binaire. Par exemple, pour multiplier par deux le nombre binaire

22). Le quotient d'un division par une puissance de deux est le rsultat d'un
dcalage droite. Pour diviser par deux, utilisez un dcalage d'une position

droite ; pour diviser par 4 (2 ), dcalez droite de 2 positions ; pour divi-

3
ser par 8 (2 ), dcalez de 3 positions vers la droite,
dcalage sont trs basiques et sont

MUL

et

DIV

etc. Les instructions de

beaucoup plus rapides que les instructions

correspondantes !

En fait, les dcalages logiques peuvent tre utiliss pour multiplier ou


diviser des valeurs non signes. Il ne fonctionnent gnralement pas pour les
valeurs signes. Considrons la valeur sur deux octets FFFF (1 sign). Si
on lui applique un dcalage logique d'une position vers la droite, le rsultat
est 7FFF ce qui fait

+32, 767 !

Un autre type de dcalage doit tre utilis

pour les valeurs signes.

3.1.3 Dcalages arithmtiques


Ces dcalages sont conus pour permettre des nombres signs d'tre
rapidement multiplis et diviss par des puissances de 2. Il assurent que le
bit de signe est trait correctement.

SAL

Shift Arithmetic Left (Dcalage Arithmtique Gauche) - Cette instruction est juste un synonyme pour

SHL. Elle est assemble pour donSHL. Tant que le bit de signe

ner exactement le mme code machine que

n'est pas chang par le dcalage, le rsultat sera correct.

SAR

Shift Arithmetic Right (Dcalage Arithmtique Droite) - C'est une

i.e.

nouvelle instruction qui ne dcale pas le bit de signe (

le msb) de

son oprande. Les autres bits sont dcals normalement sauf que les
nouveaux bits qui entrent par la gauche sont des copies du bit de
signe (c'est--dire que si le bit de signe est 1, les nouveaux bits sont
galement 1). Donc, si un octet est dcal avec cette instruction, seuls
les 7 bits de poids faible sont dcals. Comme pour les autres dcalages,
le dernier bit sorti est stock dans le drapeau de retenue.

3.1.

mov
sal
sal
sar

1
2
3
4

51

OPRATIONS DE DCALAGE

ax,
ax,
ax,
ax,

0C123H
1
1
2

; ax = 8246H, CF = 1
; ax = 048CH, CF = 1
; ax = 0123H, CF = 0

3.1.4 Dcalages circulaires


Les instructions de dcalage circulaire fonctionnent comme les dcalages
logiques except que les bits perdus un bout sont rintgrs l'autre. Donc,
la donne est traite comme s'il s'agissait d'une structure circulaire. Les
deux instructions de rotation les plus simples sont

ROL

et

ROR

qui eectuent

des rotations gauche et droite, respectivement. Comme pour les autres


dcalages, ceux-ci laissent une copie du dernier bit sorti dans le drapeau de
retenue.

mov
rol
rol
rol
ror
ror

1
2
3
4
5
6

ax,
ax,
ax,
ax,
ax,
ax,

0C123H
1
1
1
2
1

;
;
;
;
;

ax
ax
ax
ax
ax

=
=
=
=
=

8247H,
048FH,
091EH,
8247H,
C123H,

CF
CF
CF
CF
CF

=
=
=
=
=

1
1
0
1
1

Il y a deux instructions de rotation supplmentaires qui dcalent les bits


de la donne et le drapeau de retenue appeles
on applique au registre

RCL

et

RCR.

Par exemple, si

AX une rotation avec ces instructions,


AX et du drapeau de retenue.

elle applique

aux 17 bits constitus de

mov
clc
rcl
rcl
rcl
rcr
rcr

1
2
3
4
5
6
7

ax, 0C123H
ax,
ax,
ax,
ax,
ax,

1
1
1
2
1

;
;
;
;
;
;

eteint le drapeau de retenue (CF = 0)


ax = 8246H, CF = 1
ax = 048DH, CF = 1
ax = 091BH, CF = 0
ax = 8246H, CF = 1
ax = C123H, CF = 0

3.1.5 Application simple


Voici un extrait de code qui compte le nombre de bits qui sont allums

i.e. 1) dans le registre EAX.

1
2

mov
mov

bl, 0
ecx, 32

; bl contiendra le nombre de bits ALLUMES


; ecx est le compteur de boucle

52

CHAPITRE 3.

OPRATIONS SUR LES BITS

ET

Tab. 3.1  L'opration ET

1
ET

Fig. 3.2  Eectuer un ET sur un octet

3
4
5
6
7
8

count_loop:
shl
jnc
inc
skip_inc:
loop

eax, 1
skip_inc
bl

; decale un bit dans le drapeau de retenue


; si CF == 0, goto skip_inc

count_loop
EAX (EAX vaut zro la n de
EAX, la ligne 4 pourrait tre

Le code ci-dessus dtruit la valeur originale de

la boucle). Si l'on voulait conserver la valeur de


remplace par

3.2

rol eax, 1.

Oprations Boolennes Niveau Bit

Il y a quatre oprateurs boolens courants :

table de vrit

AND, OR, XOR et NOT. Une

montre le rsultat de chaque opration pour chaque valeur

possible de ses oprandes.

3.2.1 L'opration ET
Le rsultat d'un

ET

sur deux bits vaut 1 uniquement si les deux bits

sont 1, sinon, le rsultat vaut 0, comme le montre la table de vrit du


Tableau 3.1.
Les processeurs supportent ces oprations comme des instructions agissant de faon indpendante sur tous les bits de la donne en parallle. Par
exemple, si on applique un

ET

au contenu de

AL

et

BL,

l'opration

ET

de

base est applique chacune des 8 paires de bits correspondantes dans les
deux registres, comme le montre la Figure 3.2. Voici un exemple de code :

1
2

mov
and

ax, 0C123H
ax, 82F6H

; ax = 8022H

3.2.

OPRATIONS BOOLENNES NIVEAU BIT

OU

53

Tab. 3.2  L'opration OU

XOR

Tab. 3.3  L'opration XOR

3.2.2 L'opration OU
Le

OU

de 2 bits vaut 0 uniquement si les deux bits valent 0, sinon le

rsultat vaut 1 comme le montre la table de vrit du Tableau 3.2. Voici un


exemple de code :

1
2

mov
or

ax, 0C123H
ax, 0E831H

; ax = E933H

3.2.3 L'opration XOR


Le

OU

exclusif de 2 bits vaut 0 uniquement si les deux bits sont gaux,

sinon, le rsultat vaut 1 comme le montre la table de vrit du Tableau 3.3.


Voici un exemple de code :

1
2

mov
xor

ax, 0C123H
ax, 0E831H

; ax = 2912H

3.2.4 L'opration NOT


L'opration

NOT

est une opration

unaire (i.e. elle agit sur une seule


binaires de type ET ). Le NOT

oprande, pas deux comme les oprations

d'un bit est la valeur oppose du bit comme le montre la table de vrit du
Tableau 3.4. Voici un exemple de code :

1
2

mov
not

ax, 0C123H
ax

; ax = 3EDCH

54

CHAPITRE 3.

NOT

OPRATIONS SUR LES BITS

Tab. 3.4  L'opration NOT

Allumer le bit

Eectuer un

OU

sur le nombre avec

2i

(qui

est le nombre binaire avec uniquement le bit

Eteindre le bit

d'allum)
Eectuer un

ET

sur le nombre avec le nombre

binaire qui n'a que le bit


Inverser le bit

rande est souvent appele


Eectuer un

XOR

d'teint. Cette op-

masque

sur le nombre avec

2i

Tab. 3.5  Utilisation des oprations boolennes

Notez que le

NOT

donne le complment un. Contrairement aux autres

oprations niveau bit, l'instruction

FLAGS.

NOT ne change aucun des bits du registre

3.2.5 L'instruction TEST


L'instruction

TEST

eectue une opration

rsultat. Elle positionne le registre

AND

mais ne stocke pas le

FLAGS selon ce que ce


CMP qui eectue une

aprs un AND (comme l'instruction


ne fait que positionner

dernier aurait t
soustraction mais

FLAGS). Par exemple, si le rsultat tait zro, ZF serait

allum.

3.2.6 Utilisation des oprations sur les bits


Les oprations sur les bits sont trs utiles pour manipuler individuellement les bits d'une donne sans modier les autres bits. Le Tableau 3.5
montre trois utilisations courantes de ces oprations. Voici un exemple de
code, implmentant ces ides.

1
2
3
4
5
6
7
8

mov
or
and
xor
or
and
xor
xor

ax,
ax,
ax,
ax,
ax,
ax,
ax,
ax,

0C123H
8
0FFDFH
8000H
0F00H
0FFF0H
0F00FH
0FFFFH

;
;
;
;
;
;
;

allumer le bit 3, ax = C12BH


teindre le bit 5, ax = C10BH
inverser le bit 31, ax = 410BH
allumer un quadruplet, ax = 4F0BH
teindre un quadruplet, ax = 4F00H
inverser des quadruplets, ax = BF0FH
complment 1, ax = 40F0H

3.3.

EVITER LES BRANCHEMENTS CONDITIONNELS

ET

L'opration

55

peut aussi tre utilise pour trouver le reste d'une di-

vision par une puissance de deux. Pour trouver le reste d'une division par

2i ,

eectuez un

ET

sur le nombre avec un masque valant

contientra des uns du bit 0 au bit

2i 1.

Ce masque

i 1. Ce sont tout simplement ces bits qui

correspondent au reste. Le resultat du

ET

conservera ces bits et mettra les

autres zro. Voici un extrait de code qui trouve le quotient et le reste de


la division de 100 par 16.

1
2
3

mov
mov
and

eax, 100
; 100 = 64H
ebx, 0000000FH ; masque = 16 - 1 = 15 ou F
ebx, eax
; ebx = reste = 4

En utilisant le registre

CL

il est possible de modier n'importe quel(s) bit(s)

d'une donne. Voici un exemple qui allume un bit de


allumer est stock dans

1
2
3
4

mov
mov
shl
or

cl, bh
ebx, 1
ebx, cl
eax, ebx

BH.

EAX.

Le numro du bit

; tout d'abord, construire le nombre pour le OU


; dcalage gauche cl fois
; allume le bit

Eteindre un bit est un tout petit peut plus dur.

1
2
3
4
5

mov
mov
shl
not
and

cl, bh
ebx, 1
ebx, cl
ebx
eax, ebx

; tout d'abord, construire le nombre pour le ET


; dcalage gauche cl fois
; inverse les bits
; teint le bit

Le code pour inverser un bit est laiss en exercice au lecteur.


Il n'est pas rare de voir l'instruction droutante suivante dans un programme 80x86 :

xor

eax, eax

; eax = 0

Un nombre auquel on applique un

XOR avec lui-mme donne toujours zro.

Cette instruction est utilise car son code machine est plus petit que l'instruction

3.3

MOV

correspondante.

Eviter les Branchements Conditionnels

Les processeurs modernes utilisent des techniques trs sophistiques pour


excuter le code le plus rapidement possible. Une technique rpandue est
appele

excution spculative . Cette technique utilise les possibilits de trai-

tement en parallle du processeur pour excuter plusieurs instructions la

56

1
2
3
4
5
6

CHAPITRE 3.

mov
mov
count_loop:
shl
adc
loop

OPRATIONS SUR LES BITS

bl, 0
ecx, 32

; bl contiendra le nombre de bits allums


; ecx est le compteur de boucle

eax, 1
bl, 0
count_loop

; dcale un bit dans le drapeau de retenue


; ajoute le drapeau de retenue bl

Fig. 3.3  Compter les bits avec

ADC

fois. Les branchements conditionnels posent un problme ce type de fonctionnement. Le processeur, en gnral, ne sait pas si le branchement sera
eectu ou pas. Selon qu'il est eectu ou non, un ensemble d'instructions
dirent sera excut. Les processeurs essaient de prvoir si le branchement
sera eectu. Si la prvision est mauvaise, le processeur a perdu son temps
en excutant le mauvais code.
Une faon d'viter ce problme est d'viter d'utiliser les branchements
conditionnels lorsque c'est possible. Le code d'exemple de 3.1.5 fournit un
exemple simple de la faon de le faire. Dans l'exemple prcdent, les bits
allums du registre EAX sont dcompts. Il utilise un branchement pour
viter l'instruction

INC. La Figure 3.3 montre comment le branchement peut


ADC pour ajouter directement le drapeau

tre retir en utilisant l'instruction


de retenue.
Les instructions

SETxx

fournissent un moyen de retirer les branchements

dans certains cas. Ces instructions positionnent la valeur d'un registre ou


d'un emplacement mmoire d'un octet zro ou un selon l'tat du registre

SET sont les mmes que pour les branchements


condition correspondante au SETxx est vraie, le rsultat

FLAGS. Les caractres aprs


conditionnels. Si la

stock est un, s'il est faux, zro est stock. Par exemple :

setz

al

; AL = 1 si ZF est allume, sinon 0

En utilisant ces instructions, il est possible de dvelopper des techniques


ingnieuses qui calculent des valeurs sans branchement.
Par exemple, considrons le problme de la recherche de la plus grande de
deux valeurs. L'approche standard pour rsoudre ce problme serait d'utiliser
un

CMP

et un branchement conditionnel pour dterminer la valeur la plus

grande. Le programme exemple ci dessous montre comment le maximum


peut tre trouv sans utiliser aucun branchement.

1
2

; file: max.asm
%include "asm_io.inc"

3.3.

EVITER LES BRANCHEMENTS CONDITIONNELS

57

segment .data

4
5
6
7

message1 db "Entrez un nombre : ",0


message2 db "Entrez un autre nombre : ", 0
message3 db "Le plus grand nombre est : ", 0

8
9

segment .bss

10
11

input1 resd

; premier nombre entre

12
13
14
15
16
17

segment .text
global _asm_main
_asm_main:
enter 0,0
pusha

; routine d'initialisation

18
19
20
21
22

mov
call
call
mov

eax, message1
print_string
read_int
[input1], eax

; affichage du premier message

mov
call
call

eax, message2
print_string
read_int

; saisie du second nombre (dans eax)

xor
cmp
setg
neg
mov
and
not
and
or

ebx,
eax,
bl
ebx
ecx,
ecx,
ebx
ebx,
ecx,

;
;
;
;
;
;
;
;
;

mov
call
mov
call
call

eax, message3
print_string
eax, ecx
print_int
print_nl

; saisie du premier nombre

23
24
25
26

; affichage du second message

27
28
29
30
31
32
33
34
35
36

ebx
[input1]
ebx
eax
[input1]
ebx

ebx = 0
compare le premier et le second nombre
ebx = (input2 > input1) ?
1 :
ebx = (input2 > input1) ? 0xFFFFFFFF :
ecx = (input2 > input1) ? 0xFFFFFFFF :
ecx = (input2 > input1) ?
input2 :
ebx = (input2 > input1) ?
0 :
ebx = (input2 > input1) ?
0 :
ecx = (input2 > input1) ?
input2 :

37
38
39
40
41
42
43
44

popa

; affichage du rsultat

0
0
0
0
0xFFFFFFFF
input1
input1

58

CHAPITRE 3.

mov
leave
ret

45
46
47

eax, 0

OPRATIONS SUR LES BITS

; retour au C

L'astuce est de crer un masque de bits qui peut tre utilis pour slectionner la valeur correcte pour le maximum. L'instruction

SETG la ligne 30

positionne BL 1 si la seconde saisie est la plus grande ou 0 sinon. Ce


n'est pas le masque de bits dsir. Pour crer le masque ncessaire, la ligne 31
utilise l'instruction

NEG sur le registre EBX (notez que EBX a t positionn

0 prcdemment). Si EBX vaut 0, cela ne fait rien ; cependant, si EBX


vaut 1, le rsultat est la reprsentation en complment deux de -1 soit
0xFFFFFFFF. C'est exactement le masque de bits dsir. Le code restant
utilise ce masque de bits pour slectionner la saisie correspondant au plus
grand nombre.
Une autre astuce est d'utiliser l'instruction DEC. Dans le code ci-dessus, si
NEG est remplac par DEC, le rsultat sera galement 0 ou 0xFFFFFFFF. Cependant, les valeurs sont inverses par rapport l'utilisation de l'instruction

NEG.
3.4

Manipuler les bits en C

3.4.1 Les oprateurs niveau bit du C


Contrairement certains langages de haut niveau, le C fournit des oprateurs pour les oprations niveau bit. L'opration
l'oprateur binaire

&1 . L'opration OU

ET

est reprsente par

est reprsente par l'oprateur binaire

|. L'opration XOR est reprsente par l'oprateur binaire ^


NOT est reprsente par l'oprateur unaire ~ .

. Et l'opration

Les oprations de dcalage sont eectues au moyen des oprateurs binaires


rateur

 et  du C. L'oprateur  eectue les dcalages gauche et l'op eectue les dcalages droite. Ces oprateurs prennent deux op-

randes. L'oprande de gauche est la valeur dcaler et l'oprande de droite


est le nombre de bits dcaler. Si la valeur dcaler est d'un type non sign,
un dcalage logique est eectu. Si la valeur est d'un type sign (comme

int),

alors un dcalage arithmtique est utilis. Voici un exemple en C utilisant


ces oprateurs :

1
2
3
4

short int s ;
short unsigned u;
s = 1;
u = 100;
1

/ on suppose que les short int font 16 bits /


/ s = 0xFFFF (complment 2) /
/ u = 0x0064 /

Ces oprateur est dirent des oprateurs && binaire et & unaire !

3.4.

59

MANIPULER LES BITS EN C

Macro

S_IRUSR
S_IWUSR
S_IXUSR
S_IRGRP
S_IWGRP
S_IXGRP
S_IROTH
S_IWOTH
S_IXOTH

Signication
l'utilisateur peut lire
l'utilisateur peut crire
l'utilisateur peut excuter
le groupe peut lire
le groupe peut crire
le groupe peut excuter
les autres peuvent lire
les autres peuvent crire
les autres peuvent excuter

Tab. 3.6  Macros POSIX pour les Permissions de Fichiers

5
6
7
8
9

u = u | 0x0100;
s = s & 0xFFF0;
s = s ^ u;
u = u << 3;
s = s >> 2;

/ u = 0x0164 /
/ s = 0xFFF0 /
/ s = 0xFE94 /
/ u = 0x0B20 (dcalage logique) /
/ s = 0xFFA5 (dcalage arithmetique) /

3.4.2 Utiliser les oprateurs niveau bit en C


Les oprateurs niveau bit sont utiliss en C pour les mmes raisons qu'ils
le sont en assembleur. Ils permettent de manipuler les bits d'une donne
individuellement et peuvent tre utiliss pour des multiplications et des divisions rapides. En fait, un compilateur C malin utilisera automatiquement
un dcalage pour une multiplication du type
Beaucoup d'API

x *= 2.

2 de systmes d'exploitation (comme

POSIX 3 et Win32)

contiennent des fonctions qui utilisent des oprandes donc les donnes sont
codes sous forme de bits. Par exemple, les systmes POSIX conservent les
permissions sur les chiers pour trois dirents types d'utilisateurs :

teur

(un nom plus appropri serait

propritaire ), groupe

et

autres.

utilisa-

Chaque

type d'utilisateur peut recevoir la permission de lire, crire et/ou excuter


un chier. Pour changer les permissions d'un chier, le programmeur C doit
manipuler des bits individuels. POSIX dnit plusieurs macros pour l'aider
(voir Tableau 3.6). La fonction

chmod

peut tre utilise pour dnir les per-

missions sur un chier. Cette fonction prend deux paramtres, une chaine

4 avec les bits appropris d'al-

avec le nom du chier modier et un entier

Application Programming Interface, Interface pour la Programmation d'Applications


signie Portable Operating System Interface for Computer Environments, Interface
Portable de Systme d'Exploitation pour les Environnements Informatiques. Un standard
dvelopp par l'IEEE bas sur UNIX.
4
En fait, un paramtre de type mode_t qui est dnit comme entier par un typedef.
3

60

CHAPITRE 3.

OPRATIONS SUR LES BITS

lums pour les permissions dsires. Par exemple, le code ci-dessous dnit
les permissions pour permettre au propritaire du chier de lire et d'crire
dedans, au groupe de lire le chier et interdire l'accs aux autres.

chmod("foo", S_IRUSR | S_IWUSR | S_IRGRP );


La fonction POSIX

stat

peut tre utilise pour rcuprer les bits de

permission en cours pour un chier. En l'utilisant avec la fonction

chmod,

il est possible de modier certaines des permissions sans changer les autres.
Voici un exemple qui retire l'accs en criture aux autres et ajoute les droits
de lecture pour le propritaire. Les autres permissions ne sont pas altres.

1
2
3
4

struct stat le_stats ;


/ structure utilisee par stat () /
stat ("foo", & le_stats ); / lit les infos du chier
le_stats .st_mode contient les bits de permission /
chmod("foo", ( le_stats .st_mode & ~S_IWOTH) | S_IRUSR);
3.5

Reprsentations Big et Little Endian

Le Chapitre 1 a introduit les concepts de reprsentations big et little


endian des donnes multioctets. Cependant, l'auteur s'est rendu compte que
pour beaucoup de gens, ce sujet est confus. Cette section couvre le sujet plus
en dtails.
Le lecteur se souvient srement que le caractre big ou little endian fait
rfrence l'ordre dans lequel les octets (

pas les bits) d'un lment de donnes

multi-octets sont stocks en mmoire. La reprsentation big endian est la


mthode la plus intuitive. Elle stocke l'octet le plus signicatif en premier,
puis le second octet le plus signicatif, etc. En d'autres termes, les

gros (big)

bits sont stocks en premier. La mthode little endian stocke les octets dans
l'ordre inverse (moins signcatif en premier). La famille des processeurs x86
utilise la reprsentation little endian.
Par exemple, considrons le double mot reprsentant

1234567816 .

En re-

prsentation big endian, les octets seraient stocks 12 34 56 78. En reprsentation little endian, les octets seraient stocks 78 56 34 12.
Le lecteur est probablement en train de se demander pourquoi n'importe
quelle concepteur de puce sain d'esprit utiliserait la reprsentation little endian ? Les ingnieurs de chez Intel taient-ils sadiques pour iniger une
multitude de programmeurs cette reprsentation qui prte confusion ? Il
peut sembler que le processeur ait faire du travail supplmentaire pour
stocker les octets en mmoire dans l'odre inverse (et pour les rinverser lorsqu'il lit partir de la mmoire). En fait, le processeur ne fait aucun travail
supplmentaire pour lire et crire en mmoire en utilisant le format little
endian. Il faut comprendre que le processeur est constitu de beaucoup de

3.5.

REPRSENTATIONS BIG ET LITTLE ENDIAN

61

unsigned short word = 0x1234; / on suppose que sizeof ( short) == 2 /


unsigned char p = (unsigned char ) &word;
if ( p[0] == 0x12 )
printf ("Machine Big Endian\n");
else
printf ("Machine Little Endian\n");
Fig. 3.4  Comment dterminer le caractre big ou little endian

circuits lectroniques qui ne travaillent que sur des valeurs de un bit. Les bits
(et les octets) peuvent tre dans n'importe quel ordre dans le processeur.

AX. Il peut tre dcompos en


AH et AL. Il y a des circuits dans le processeur qui
conservent les valeurs de AH and AL. Ces circuits n'ont pas d'ordre dans le
processeur. C'est--dire que les circuits pour AH ne sont pas avant ou aprs
les circuits pour AL. Une instruction MOV qui copie la valeur de AX en mmoire
copie la valeur de AL puis de AH. Ce n'est pas plus dur pour le processeur
que de stocker AH en premier.
Considrons le registre de deux octets

deux registres d'un octet :

Le mme argument s'applique aux bits individuels d'un octet. Il ne sont


pas rellement dans un ordre dtermin dans les circuits du processeur (ou
en mmoire en ce qui nous concerne). Cependant, comme les bits individuels
ne peuvent pas tre adresss dans le processeur ou en mmoire, il n'y a pas
de faon de savoir (et aucune raison de s'en soucier) l'ordre dans lequel ils
sont conservs l'intrieur du processeur.
Le code C de la Figure 3.4 montre comment le caractre big ou little

p traite la variable
lments. Donc, p[0] corres-

endian d'un processeur peut tre dtermin. Le pointeur

word

comme un tableau de caractres de deux

pond au premier octet de

word en mmoire dont la valeur dpend du caractre

big ou little endian du processeur.

3.5.1 Quand se Soucier du Caractre Big ou Little Endian


Pour la programmation courante, le caractre big ou little endian du
processeur n'est pas important. Le moment le plus courant o cela devient
important est lorsque des donnes binaires sont transfres entre dirents
systmes informatiques. Cela se fait habituellement en utilisant un type quelconque de mdia physique (comme un disque) ou via un rseau. Comme les Avec l'avnement des jeux
donnes ASCII sont sur un seul octet, le caractre big ou little endian n'est de caractres multioctets
comme
UNICODE,
le
pas un problme.
Tous les en-ttes internes de TCP/IP stockent les entiers au format big
endian (appel

ordre des octets rseau ).

Les bibliothques TCP/IP orent

caractre

big

ou

little

endian devient important


mme

pour

les

donnes

texte. UNICODE supporte


les deux types de reprsentation et a un mcanisme
pour indiquer celle qui est
utilise

pour

les donnes.

reprsenter

62

1
2
3
4
5

ip [0] =
ip [1] =
ip [2] =
ip [3] =

7
8
9
10
11
13

OPRATIONS SUR LES BITS

unsigned invert_endian( unsigned x )


{
unsigned invert;
const unsigned char xp = (const unsigned char ) &x;
unsigned char ip = (unsigned char ) & invert;

12

CHAPITRE 3.

xp [3];
xp [2];
xp [1];
xp [0];

return invert ;

/ inverse les octets individuels /

/ renvoie les octets inverses /

Fig. 3.5  Fonction invert_endian

des fonctions C pour rsoudre les problmes de reprsentation big ou little


endian d'une faon portable. Par exemple, la fonction

htonl ()

convertit un

double mot (ou un entier long) depuis le format hte vers le format rseau.
La fonction function

ntohl () eectue la transformation inverse5 . Pour un sys-

tme big endian les deux fonctions retournent leur paramtre inchang. Cela
permet d'crire des programmes rseau qui compileront et s'excuteront correctement sur n'importe quel systme sans tenir compte de la reprsentation
utilise. Pour plus d'information sur les reprsentations big et little endian
et la programmation rseau, voyez l'excellent livre de W. Richard Steven :

UNIX Network Programming.

La Figure 3.5 montre une fonction C qui passe d'une reprsentation


l'autre pour un double mot. Le processeur 486 a introduit une nouvelle instruction machine appele

BSWAP

qui inverse les octets de n'importe quel re-

gistre 32 bits. Par exemple,

bswap

edx

; change les octets de edx

L'instruction ne peut pas tre utilise sur des registres de 16 bits. Cependant,
l'instruction

XCHG

peut tre utilise pour changer les octets des registres

16 bits pouvant tre dcomposs en registres de 8 bits. Par exemple :

xchg
5

ah,al

; change les octets de ax

En fait, passer d'une reprsentation l'autre pour entier consiste inverser l'ordre
des octets ; donc, convertir de little vers big ou de big vers little est la mme opration.
Donc, ces deux fonctions font la mme chose.

3.6.

1
2
3

int count_bits( unsigned int data )


{
int cnt = 0;

4
5
6
7
8
9
10

63

COMPTER LES BITS

while ( data != 0 ) {
data = data & (data 1);
cnt++;
}
return cnt;

Fig. 3.6  Compter les Bits : Method Une

3.6

Compter les Bits

Plus haut, nous avons donn une technique intuitive pour compter le
nombre de bits allums dans un double mot. Cette section dcrit d'autres
mthodes moins directes de le faire pour illustrer les oprations sur les bits
dont nous avons parl dans ce chapitre.

3.6.1 Mthode une


La premire mthode est trs simple, mais pas vidente. La Figure 3.6
en montre le code.
Comment fonctionne cette mthode ? A chaque itration de la boucle, un
bit est teint dans

data.

i.e. lorsque data

Quand tous les bits sont teints ((

vaut zro), la boucle s'arrte. Le nombre d'itration requises pour mettre

data

zro est gal au nombre de bits dans la valeur original de

La ligne 6 est l'endroit o un bit de

data

data.

est teint. Comment cela

marche ? Considrons la forme gnrale de la reprsentation binaire de

data

et le 1 le plus droite dans cette reprsentation. Par dnition, chaque bit

data - 1 ?
data, mais

aprs ce 1 est zro. Maintenant, que sera la reprsentation de


Les bits gauche du 1 le plus droite seront les mme que pour

partir du 1 le plus droite, les bits seront les complments des bits originaux

data. Par exemple :


data
=
xxxxx10000
data - 1 = xxxxx01111

de

o les x sont les mmes pour les deux nombres. Lorsque l'on applique un
sur

data

avec

data - 1,

le rsultat mettra le 1 le plus droite de

zro et laissera les autres bits inchangs.

ET

data

64

1
2
3
4
5

void initialize_count_bits ()
{
int cnt , i , data;

7
8
9
10
11
12
13
14
16
17
18
19
20

22
24

for ( i = 0; i < 256; i++ ) {


cnt = 0;
data = i ;
while ( data != 0 ) {
/ methode une /
data = data & (data 1);
cnt++;
}
byte_bit_count[i] = cnt;
}

int count_bits( unsigned int data )


{
const unsigned char byte = ( unsigned char ) & data;

21
23

OPRATIONS SUR LES BITS

static unsigned char byte_bit_count[256]; / tableau de recherche /

15

CHAPITRE 3.

return byte_bit_count[byte[0]] + byte_bit_count[byte[1]] +


byte_bit_count[byte[2]] + byte_bit_count[byte [3]];

Fig. 3.7  Mthode Deux

3.6.2 Mthode deux


Un tableau de recherche peut galement tre utilis pour contrer les bits
de n'importe quel double mot. L'approche intuitive serait de prcalculer le
nombre de bits de chaque double mot et de le stocker dans un tableau.
Cependant, il y a deux problmes relatifs cette approche. Il y a peu prs

4 milliards

de valeurs de doubles mots ! Cela signie que le tableau serait

trs gros et que l'initialiser prendrait beaucoup de temps (en fait, moins
que l'on ait l'intention d'utiliser le tableau plus de 4 milliards de fois, cela
prendrait plus de temps de l'initialiser que cela n'en prendrait de calculer le
nombre de bits avec la mthode une !).
Une mthode plus raliste calculerait le nombre de bits pour toutes les
valeurs possibles d'octet et les stockerait dans un tableau. Le double mot
peut alors tre dcompos en quatre valeurs d'un octet. Le nombre de bits
pour chacune de ces 4 valeurs d'un octet est recherch dans le tableau et

3.6.

1
2
3
4
5
6
7
8
9

int count_bits(unsigned int x )


{
static unsigned int mask[] = { 0x55555555,
0x33333333,
0x0F0F0F0F,
0x00FF00FF,
0x0000FFFF };
int i ;
int shift ; / nombre de position dcaler droite /

10
11
12
13
14

65

COMPTER LES BITS

for ( i=0, shift =1; i < 5; i++, shift = 2 )


x = (x & mask[i ]) + ( ( x >> shift) & mask[i ] );
return x;

Fig. 3.8  Mthode Trois

additionn aux autres pour trouver le nombre de bits du double mot original.
La Figure 3.7 montre le code implmentant cette approche.

initialize_count_bits doit tre appele avant le premier


appel la fonction count_bits. Cette fonction initialise le tableau global
byte_bit_count. La fonction count_bits ne considre pas la variable data
La fonction

comme un double mot mais comme un tableau de quatre octets. Donc,

dword[0]

est un des octets de

data

(soit le moins signicatif, soit le plus

signicatif selon que le matriel est little ou big endian, respectivement).


Bien sr, on peut utiliser une instruction comme :

(data >> 24) & 0x000000FF


pour trouver la valeur de l'octet le plus signicatif et des oprations similaires
pour les autres octets ; cependant, ces oprations seraient plus lentes qu'une
rfrence un tableau.
Un dernier point, une boucle

for

pourrait facilement tre utilise pour

calculer la somme des lignes 22 et 23. Mais, cela inclurait le supplment


de l'initialisation de l'indice, sa comparaison aprs chaque itration et son
incrmentation. Calculer la somme comme une somme explicite de quatre
valeurs est plus rapide. En fait, un compilateur intelligent convertirait la
version avec une boucle

for

en une somme explicite. Ce procd de rduire

ou liminer les itrations d'une boucle est une technique d'optimisation de


compilateur appele

loop unrolling

(droulage de boucle).

66

CHAPITRE 3.

OPRATIONS SUR LES BITS

3.6.3 Mthode trois


Il y a encore une mthode intelligente de compter les bits allums d'une
donne. Cette mthode ajoute littralement les uns et les zros de la donne.
La somme est gale au nombre de 1 dans la donne. Par exemple, considrons
le comptage des uns d'un octet stock dans une variable nomme

data.

La

premire tape est d'eectuer l'opration suivante :

data = (data & 0x55) + ((data >> 1) & 0x55);


0x55 vaut 01010101 en binaire. Dans
ET sur data avec elle, les
impaires sont supprims. La seconde oprande ((data

Pourquoi faire cela ? La constante hexa

la premire oprande de l'addition, on eectue un


bits sur des positions

 1) & 0x55)

commence par dplacer tous les bits des positions paires

vers une position impaire et utilise le mme masque pour supprimer ces bits.
Maintenant, la premire oprande contient les bits impairs et la seconde les
bits pairs de

data.

Lorsque ces deux oprandes sont additionnes, les bits

pairs et les bits impairs de

101100112 ,
+

data sont additionns. Par exemple, si data vaut

alors :

data & 010101012


(data  1) & 010101012

00
soit

01

00

01

01

01

00

01

01

10

00

10

L'addition droite montre les bits additionns ensembles. Les bits de


l'octet sont diviss en deux champs de 2 bits pour montrer qu'il y a en
fait quatre additions indpendantes. Comme la plus grande valeur que ces
sommes peuvent prendre est deux, il n'est pas possibile qu'une somme dborde de son champ et corrompe l'une des autres sommes.
Bien sr, le nombre total de bits n'a pas encore t calcul. Cependant,
la mme technique que celle qui a t utilise ci-dessus peut tre utilise pour
calculer le total en une srie d'tapes similaires. L'tape suivante serait :

data = (data & 0x33) + ((data >> 2) & 0x33);


En continuant l'exemple du dessus (souvenez vous que

data vaut maintenant

011000102 ):
+

data & 001100112


(data  2) & 001100112

soit

0010

0010

0001

0000

0011

0010

Il y a maintenant deux champs de 4 bits additionns individuellement.


La prochaine tape est d'additionner ces deux sommes de bits ensemble
pour former le rsultat nal :

data = (data & 0x0F) + ((data >> 4) & 0x0F);

3.6.

67

COMPTER LES BITS

En utilisant l'exemple ci-dessus (avec


+

data & 000011112


(data  4) & 000011112

Maintenant,

data

data

gale

001100102 ):

00000010
soit

00000011
00000101

vaut 5 ce qui est le rsultat correct. La Figure 3.8 montre

une implmentation de cette mthode qui compte les bits dans un double
mot. Elle utilise une boucle

for pour calculer la somme. Il serait plus rapide

de drouler la boucle ; cependant, la boucle rend plus claire la faon dont la


mthode se gnralise direntes tailles de donnes.

68

CHAPITRE 3.

OPRATIONS SUR LES BITS

Chapitre 4

Sous-Programmes
Ce chapitre explique comment utiliser des sous-programmes pour crer
des programmes modulaires et s'interfacer avec des langages de haut niveau
(comme le C). Les fonctions et les procdures sont des exemples de sousprogrammes dans les langages de haut niveau.
Le code qui appelle le sous-programme et le sous-programme lui-mme
doivent se mettre d'accord sur la faon de se passer les donnes. Ces rgles
sur la faon de passer les donnes sont appeles

conventions d'appel .

Une

grande partie de ce chapitre traitera des conventions d'appel standards du


C qui peuvent tre utilises pour interfacer des sous-programmes assembleur
avec des programmes C. Celle-ci (et d'autres conventions) passe souvent les

i.e. des pointeurs) pour permettre au sou-programme

adresses des donnes (

d'accder aux donnes en mmoire.

4.1

Adressage Indirect

L'adressage indirect permet aux registres de se comporter comme des


pointeurs. Pour indiquer qu'un registre est utilis indirectement comme un
pointeur, il est entour par des crochets ([]). Par exemple :

1
2
3

mov
mov
mov

ax, [Data]
ebx, Data
ax, [ebx]

; adressage mmoire direct normal d'un mot


; ebx = & Data
; ax = *ebx

Comme AX contient un mot, la ligne 3 lit un mot commenant l'adresse


stocke dans EBX. Si AX tait remplac par AL, un seul octet serait lu.
Il est important de raliser que les registres n'ont pas de types comme les
variable en C. Ce sur quoi EBX est cens pointer est totalement dtermin
par les instructions utilises. Si EBX est utilis de manire incorrecte, il n'y
aura souvent pas d'erreur signale de la part de l'assembleur ; cependant,
le programme ne fonctionnera pas correctement. C'est une des nombreuses
69

70

CHAPITRE 4.

SOUS-PROGRAMMES

raisons pour lesquelles la programmation assembleur est plus sujette erreur


que la programmation de haut niveau.
Tous les registres 32 bits gnraux (EAX, EBX, ECX, EDX) et d'index
(ESI, EDI) peuvent tre utiliss pour l'adressage indirect. En gnral, les
registres 16 et 8 bits ne le peuvent pas.

4.2

Exemple de Sous-Programme Simple

Un sous-programme est une unit de code indpendante qui peut tre


utilise depuis direntes parties du programme. En d'autres termes, un
sous-programme est comme une fonction en C. Un saut peut tre utilis pour
appeler le sous-programme, mais le retour prsente un problme. Si le sousprogramme est destin tre utilis par direntes parties du programme,
il doit revenir la section de code qui l'a appel. Donc, le retour du sousprogramme ne peut pas tre cod en dur par un saut vers une tiquette. Le
code ci-dessous montre comment cela peut tre ralis en utilisant une forme
indirecte de l'instruction

JMP.

Cette forme de l'instruction utilise la valeur

d'un registre pour dterminer o sauter (donc, le registre agit plus comme
un

pointeur de fonction

du C). Voici le premier programme du Chapitre 1

rcrit pour utiliser un sous-programme.

1
2
3

sub1.asm
; fichier : sub1.asm
; Programme d'exemple de sous-programme
%include "asm_io.inc"

4
5
6
7
8
9
10

segment
prompt1
prompt2
outmsg1
outmsg2
outmsg3

.data
db
db
db
db
db

"Entrez un nombre : ", 0


; ne pas oublier le zro terminal
"Entrez un autre nombre : ", 0
"Vous avez entr ", 0
" et ", 0
", la somme des deux vaut ", 0

11
12
13
14

segment .bss
input1 resd 1
input2 resd 1

15
16
17
18
19
20
21

segment .text
global _asm_main
_asm_main:
enter 0,0
pusha

; routine d'initialisation

4.2.

22
23

EXEMPLE DE SOUS-PROGRAMME SIMPLE

71

mov
call

eax, prompt1
print_string

; affiche l'invite

mov
mov
jmp

ebx, input1
ecx, ret1
short get_int

; stocke l'adresse de input1 dans ebx


; stocke l'adresse de retour dans ecx
; lit un entier

mov
call

eax, prompt2
print_string

; affiche l'invite

mov
mov
jmp

ebx, input2
ecx, $ + 7
short get_int

; ecx = cette addresse + 7

mov
add
mov

eax, [input1]
eax, [input2]
ebx, eax

; eax = dword dans input1


; eax += dword dans input2
; ebx = eax

mov
call
mov
call
mov
call
mov
call
mov
call
mov
call
call

eax, outmsg1
print_string
eax, [input1]
print_int
eax, outmsg2
print_string
eax, [input2]
print_int
eax, outmsg3
print_string
eax, ebx
print_int
print_nl

24
25
26
27
28

ret1:

29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

; affiche le premier message


; affiche input1
; affiche le second message
; affiche input2
; affiche le troisime message
; affiche la somme (ebx)
; retour la ligne

53
54
55
56
57
58
59
60
61
62
63

;
;
;
;
;
;

popa
mov
eax, 0
; retour au C
leave
ret
subprogram get_int
Paramtres:
ebx - addresse du dword dans lequel stocker l'entier
ecx - addresse de l'instruction vers laquelle retourner
Notes:
la valeur de eax est perdue

72

64
65
66
67

CHAPITRE 4.

get_int:
call
mov
jmp

read_int
[ebx], eax
ecx

Le sous-programme

SOUS-PROGRAMMES

; stocke la saisie en mmoire


; retour l'appelant
sub1.asm

get_int utilise une convention d'appel simple, base

sur les registres. Il s'attend ce que le registre EBX contienne l'adresse


du DWORD dans lequel stocker le nombre saisi et ce que le registre
ECX contiennent l'adresse de l'instruction vers laquelle retourner. Dans les
lignes 25 28, l'tiquette

ret1

est utilise pour calculer cette adresse de re-

tour. Dans les lignes 32 34, l'oprateur


de retour. L'oprateur
L'expression

$ + 7

est utilis pour calculer l'adresse

$ retourne l'adresse de la ligne sur laquelle il apparat.


MOV de la ligne 36.

calcule l'adresse de l'instruction

Ces deux calculs d'adresses de code de retour sont compliqus. La premire mthode requiert la dnition d'une tiquette pour tout appel de sousprogramme. La seconde mthode ne requiert pas d'tiquette mais ncessite
de rchir attentivement. Si un saut proche avait t utilis la place d'un
saut court, le nombre ajouter

n'aurait pas t 7! Heureusement, il y a

une faon plus simple d'appeler des sous-programmes. Cette mthode utilise
la

pile.

4.3

La pile

Beaucoup de processeurs ont un support intgr pour une pile. Une pile
est une liste Last-In First-Out (

LIFO, dernier entr, premier sorti). La pile

est une zone de la mmoire qui est organise de cette faon. L'instruction

PUSH

ajoute des donnes la pile et l'instruction

POP

retire une donne. La

donne retire est toujours la dernire donne ajoute (c'est pourquoi on


appelle ce genre de liste dernier entr, premier sorti).
Le registre de segment SS spcie le segment qui contient la pile (habituellement c'est le mme que celui qui contient les donnes). Le registre
ESP contient l'adresse de la donne qui sera retire de la pile. On dit que
cette donne est au

sommet

de la pile. Les donnes ne peuvent tre ajoutes

que par units de double mots. C'est--dire qu'il est impossible de placer un
octet seul sur la pile.
L'instruction

PUSH

puis en stockant le double mot en


mot en

[ESP]

1 sur la pile en tant 4 de ESP

insre un double mot

[ESP].

L'instruction

POP

lit le double

puis ajoute 4 ESP. Le code ci-dessous montre comment

fonctionnent ces instructions en supposant que ESP vaut initialement

1000H.

1
En fait, il est galement possible d'empiler des mots, mais en mode protg 32 bits, il
est mieux de ne travailler qu'avec des doubles mots sur la pile.

4.4.

1
2
3
4
5
6

73

LES INSTRUCTIONS CALL ET RET

push
push
push
pop
pop
pop

dword 1
dword 2
dword 3
eax
ebx
ecx

;
;
;
;
;
;

1 est
2 est
3 est
EAX =
EBX =
ECX =

stock
stock
stock
3, ESP
2, ESP
1, ESP

en 0FFCh, ESP = 0FFCh


en 0FF8h, ESP = 0FF8h
en 0FF4h, ESP = 0FF4h
= 0FF8h
= 0FFCh
= 1000h

La pile peut tre utilise comme un endroit appropri pour stocker des
donnes temporairement. Elle est galement utilise pour eectuer des appels
de sous-programmes, passer des paramtres et des variables locales.
Le 80x86 fournit galement une instruction,

PUSHA, qui empile les valeurs

des registres EAX, EBX, ECX, EDX, ESI, EDI et EBP (pas dans cet ordre).
L'instruction

4.4

POPA

peut tre utilise pour les dpiler tous.

Les Instructions CALL et RET

Le 80x86 fournit deux instructions qui utilisent la pile pour eectuer des
appels de sous-programmes rapidement et facilement. L'instruction CALL
eectue un saut inconditionnel vers un sous-programme et
l'instruction suivante. L'instruction RET

dpile

empile l'adresse de

une adresse et saute cette

adresse. Lors de l'utilisation de ces instructions, il est trs important de grer


la pile correctement an que le chire correct soit dpil par l'instruction
RET !
Le programme prcdent peut tre rcrit pour utiliser ces nouvelles instructions en changeant les lignes 25 34 en ce qui suit :

mov
call

ebx, input1
get_int

mov
call

ebx, input2
get_int

et en changeant le sous-programme

get_int:
call
mov
ret

get_int

en :

read_int
[ebx], eax

Il y a plusieurs avantages utiliser CALL et RET:


 C'est plus simple !

74

CHAPITRE 4.

SOUS-PROGRAMMES

 Cela permet d'imbriquer des appels de sous-programmes facilement.


Notez que

get_int

appelle

read_int. Cet appel empile une autre


read_int se trouve un RET qui dpile
vers le code de get_int. Puis, lorsque le

adresse. la n du code de
l'adresse de retour et saute

get_int est
asm_main. Cela

RET de

excut, il dpile l'adresse de retour qui revient

vers

fonctionne correctement car il s'agit d'une pile

LIFO.
Souvenez vous, il est

trs

important de dpiler toute donne qui est em-

pile. Par exemple, considrons le code suivant :

1
2
3
4
5

get_int:
call
mov
push
ret

read_int
[ebx], eax
eax
; dpile la valeur de EAX, pas l'adresse de retour !!

Ce code ne reviendra pas correctement !

4.5

Conventions d'Appel

Lorsqu'un sous-programme est appel, le code appelant et le sous-programme

(l'

appel )

doivent s'accorder sur la faon de se passer les donnes. Les lan-

gages de haut niveau ont des manires standards de passer les donnes appeles

conventions d'appel.

Pour interfacer du code de haut niveau avec le

langage assembleur, le code assembleur doit utiliser les mmes conventions


que le langage de haut niveau. Les conventions d'appel peuvent direr d'un
compilateur l'autre ou peuvent varier selon la faon dont le code est com-

p.e.

pil (

selon que les optimisations sont actives ou pas). Une convention

universelle est que le code est appel par une instruction


un

RET.

CALL et revient par

Tous les compilateurs C PC supportent une convention d'appel qui sera


dcrite dans le reste de ce chapitre par tape. Ces conventions permettent de
crer des sous-programmes

rentrants.

Un sous-programme rentrant peut

tre appel depuis n'importe quel endroit du programme en toute scurit


(mme depuis le sous-programme lui-mme).

4.5.1 Passer les paramtres via la pile


Les paramtres d'un sous-programme peuvent tre passs par la pile. Ils
sont empils avant l'instruction

CALL. Comme en C, si le paramtre doit tre

modi par le sous-programme, l'


sa

valeur.

adresse

de la donne doit tre passe, pas

Si la taille du paramtre est infrieure un double mot, il doit

tre converti en un double mot avant d'tre empil.

4.5.

75

CONVENTIONS D'APPEL

ESP + 4
ESP

Paramtre
Adresse de retour

Fig. 4.1  tat de la pile lors du passage d'un paramtre

ESP + 8

Paramtre

ESP + 4

Adresse de retour

ESP

Donn'ee du sous-programme

Fig. 4.2  Etat de la pile aprs empilage d'une donne

Les paramtres sur la pile ne sont pas dpils par le sous-programme,


la place, ils sont accds depuis la pile elle-mme. Pourquoi ?
 Comme ils doivent tre empils avant l'instruction

CALL,

l'adresse de

retour devrait tre dpile avant tout (puis rempile ensuite).


 Souvent, les paramtres sont utiliss plusieurs endroits dans le sousprogramme. Habituellement, ils ne peuvent pas tre conservs dans
un registre durant toute la dure du sous-programme et devront tre
stocks en mmoire. Les laisser sur la pile conserve une copie de la
donne en mmoire qui peut tre accde depuis n'importe quel endroit
du sous-programme.
Considrons un sous-programme auquel on passe un paramtre unique Lors

de

l'utilisation

de

indirect,
le
via la pile. Lorsque le sous-programme est appel, la pile ressemble la l'adressage
processeur
80x86
accde

Figure 4.1. On peut accder au paramtre en utilisant l'adressage indirect

([ESP+4]

2 ).

dirents

Si la pile est galement utilise dans le sous-programme pour stocker des


donnes, le nombre ajouter ESP changera. Par exemple, la Figure 4.2

l'expression
indirect.

montre quoi ressemble la pile si un DWORD est empil. Maintenant, le utilisent


paramtre se trouve en

ESP + 8, plus en ESP + 4. Donc, utiliser ESP lorsque

segments

selon

les registres utiliss dans


d'adressage

ESP
le

(et

EBP)

segment

de

pile alors que EAX, EBX,

l'on fait rfrence des paramtres peut tre une source d'erreurs. Pour ECX

et

EDX

utilisent

rsoudre ce problme, Le 80386 fournit un autre registre utiliser : EBP. La le segment de donnes.
Cependant, ce n'est habiseule utilit de ce registre est de faire rfrence des donnes sur la pile. La
tuellement pas important
convention d'appel C stipule qu'un sous-programme doit d'abord empiler la
pour la plupart des provaleur de EBP puis dnir EBP pour qu'il soit gal ESP. Cela permet ESP
grammes en mode protg,
de changer au fur et mesure que des donnes sont empiles ou dpiles sans car pour eux, les segments
modier EBP. A la n du programme, la valeur originale de EBP doit tre de pile et de donnes sont
restaure (c'est pourquoi elle est sauvegarde au dbut du sous-programme). les mmes.

Il est autoris d'ajouter une constante un registre lors de l'utilisation de l'adressage


indirect. Des expressions plus complexes sont galement possible. Ce sujet est trait dans
le chapitre suivant

76

1
2
3
4
5
6

CHAPITRE 4.

etiquette_sousprogramme:
push ebp
mov
ebp, esp
; code du sousprogramme
pop
ebp
ret

SOUS-PROGRAMMES

; empile la valeur originale de EBP


; EBP = ESP
; restaure l'ancienne valeur de EBP

Fig. 4.3  Forme gnrale d'un sous-programme

ESP + 8

EBP + 8

Parametre

ESP + 4

EBP + 4

Adresse de retour

ESP

EBP

EBP sauvegard

Fig. 4.4  Etat de la pile en suivant la convention d'appel C

La Figure 4.3 montre la forme gnrale d'un sous-programme qui suit ces
conventions.

prologue gnrique d'un


pilogue. La Figure 4.4 montre

Les lignes 2 et 3 de la Figure 4.3 constituent le


sous-programme. Les lignes 5 et 6 consituent l'

quoi ressemble la pile immdiatement aprs le prologue. Maintenant, le


paramtre peut tre accd avec

[EBP + 8]

depuis n'importe quel endroit

du programme sans se soucier de ce qui a t empil entre temps par le


sous-programme.
Une fois le sous-programme termin, les paramtres qui ont t empils doivent tre retirs. La convention d'appel C spcie que c'est au code
appelant de le faire. D'autres conventions sont direntes. Par exemple, la
convention d'appel Pascal spcie que c'est au sous-programme de retirer
les paramtres (Il y une autre forme de l'instruction RET qui permet de le
faire facilement). Quelques compilateurs C supportent cette convention galement. Le mot cl

pascal

est utilis dans le prototype et la dnition de

la fonction pour indiquer au compilateur d'utiliser cette convention. En fait,


la convention

stdcall,

que les fonctions de l'API C MS Windows utilisent,

fonctionne galement de cette faon. Quel est son avantage ? Elle est un petit
peu plus ecace que la convention C. Pourquoi toutes les fonctions C n'utilisent pas cette convention alors ? En gnral, le C autorise une fonction a
avoir un nombre variable d'arguments (

p.e., les fonctions printf et scanf).

Pour ce type de fonction, l'opration consistant retirer les paramtres de la


pile varie d'un appel l'autre. La convention C permet aux instructions ncessaires la ralisation de cette opration de varier facilement d'un appel
l'autre. Les conventions Pascal et stdcall rendent cette opration trs compli-

4.5.

push
call
add

1
2
3

77

CONVENTIONS D'APPEL

dword 1
fun
esp, 4

; passe 1 en paramtre
; retire le paramtre de la pile

Fig. 4.5  Exemple d'appel de sous-programme

que. Donc, la convention Pascal (comme le langage Pascal) n'autorise pas ce


type de fonction. MS Windows peut utiliser cette convention puisqu'aucune
de ses fonctions d'API ne prend un nombre variable d'arguments.
La Figure 4.5 montre comment un sous-programme utilisant la convention d'appel C serait appel. La ligne 3 retire le paramtre de la pile en
manipulant directement le pointeur de pile. Une instruction

POP pourrait ga-

lement tre utilise, mais cela ncessiterait le stockage d'un paramtre inutile
dans un registre. En fait, dans ce cas particulier, beaucoup de compilateurs

POP ECX pour retirer le paramtre. Le compilateur


POP plutot qu'un ADD car le ADD ncessite plus d'octets pour stocker
l'instruction. Cependant, le POP change galement la valeur de ECX ! Voici

utilisent une instruction


utilise un

un autre programme exemple avec deux sous-programmes qui utilisent les


conventions d'appel C dont nous venons de parler. La ligne 54 (et les autres)
montre que plusieurs segments de donnes et de texte peuvent tre dclars
dans un mme chier source. Ils seront combins en des segments de donnes
et de texte uniques lors de l'dition de liens. Diviser les donnes et le code en
segments spars permet d'avoir les donnes d'un sous-programme dnies
proximit de celui-ci.

%include "asm_io.inc"

sub3.asm

2
3
4

segment .data
sum
dd 0

5
6
7

segment .bss
input resd 1

8
9
10
11
12
13
14
15

;
;
;
;
;
;
;

algorithme en pseudo-code
i = 1;
sum = 0;
while( get_int(i, &input), input != 0 ) {
sum += input;
i++;

78

16
17
18
19
20
21
22

CHAPITRE 4.

; }
; print_sum(num);
segment .text
global _asm_main
_asm_main:
enter 0,0
pusha

SOUS-PROGRAMMES

; routine d'initialisation

23
24
25
26
27
28
29

mov
while_loop:
push
push
call
add

edx, 1

; edx est le 'i' du pseudo-code

edx
dword input
get_int
esp, 8

; empile i
; empile l'adresse de input
; dpile i et &input

30
31
32
33

mov
cmp
je

eax, [input]
eax, 0
end_while

add

[sum], eax

inc
jmp

edx
short while_loop

34
35

; sum += input

36
37
38
39
40
41
42
43

end_while:
push
call
pop

dword [sum]
print_sum
ecx

; empile la valeur de sum


; dpile [sum]

44
45
46
47

popa
leave
ret

48
49
50
51
52
53
54
55
56
57

; sous-programme get_int
; Paramtres (dans l'ordre de l'empilement)
; nombre de saisies (en [ebp + 12])
; adresse du mot o stocker la saisie (en [ebp + 8])
; Notes:
; les valeurs de eax et ebx sont dtruites
segment .data
prompt db
") Entrez un nombre entier (0 pour quitter): ", 0

4.5.

58
59
60
61

79

CONVENTIONS D'APPEL

segment .text
get_int:
push
mov

ebp
ebp, esp

62
63
64

mov
call

eax, [ebp + 12]


print_int

mov
call

eax, prompt
print_string

call
mov
mov

read_int
ebx, [ebp + 8]
[ebx], eax

pop
ret

ebp

65
66
67
68
69
70
71

; stocke la saisie en mmoire

72
73
74

; retour l'appelant

75
76
77
78
79
80
81
82
83

; sous-programme print_sum
; affiche la somme
; Paramtre:
; somme afficher (en [ebp+8])
; Note: dtruit la valeur de eax
;
segment .data
result db
"La somme vaut ", 0

84
85
86
87
88

segment .text
print_sum:
push
mov

ebp
ebp, esp

89
90
91

mov
call

eax, result
print_string

mov
call
call

eax, [ebp+8]
print_int
print_nl

pop
ret

ebp

92
93
94
95
96
97
98

sub3.asm

80

1
2
3
4
5
6
7
8

CHAPITRE 4.

etiquette_sousprogramme:
push
ebp
mov
ebp, esp
sub
esp, LOCAL_BYTES
; code du sousprogramme
mov
esp, ebp
pop
ebp
ret

SOUS-PROGRAMMES

; empile la valeur originale de EBP


; EBP = ESP
; = nb octets ncessaires pour les locales
; dsalloue les locales
; restaure la valeur originalede EBP

Fig. 4.6  Forme gnrale d'un sous-programme avec des variables locales

1
2
3

void calc_sum( int n, int sump )


{
int i , sum = 0;

4
5
6
7
8

for ( i=1; i <= n; i++ )


sum += i;
sump = sum;

Fig. 4.7  Version C de sum

4.5.2 Variables locales sur la pile


La pile peut tre utilise comme un endroit pratique pour stocker des
variables locales. C'est exactement ce que fait le C pour les variables normales (ou

automatiques

en C lingo). Utiliser la pile pour les variables est

important si l'on veut que les sous-programmes soient rentrants. Un programme rentrant fonctionnera qu'il soit appel de n'importe quel endroit,
mme partir du sous-programme lui-mme. En d'autres termes, les sousprogrammes rentrants peuvent tre appels

rcursivement.

Utiliser la pile

pour les variables conomise galement de la mmoire. Les donnes qui ne


sont pas stockes sur la pile utilisent de la mmoire du dbut la n du
programme (le C appelle ce type de variables

global

ou

static ). Les donnes

stockes sur la pile n'utilisent de la mmoire que lorsque le sous-programme


dans lequel elles sont dnies est actif.
Les variables locales sont stockes immdiatement aprs la valeur de EBP
sauvegarde dans la pile. Elles sont alloues en soustrayant le nombre d'octets
requis de ESP dans le prologue du sous-programme. La Figure 4.6 montre
le nouveau squelette du sous-programme. Le registre EBP est utilis pour
accder des variables locales. Considrons la fonction C de la Figure 4.7.
La Figure 4.8 montre comment le sous-programme quivalent pourrait tre

4.5.

1
2
3
4

81

CONVENTIONS D'APPEL

cal_sum:
push
mov
sub

ebp
ebp, esp
esp, 4

; fait de la place pour le sum local

5
6
7
8
9
10

mov
mov
for_loop:
cmp
jnle

dword [ebp - 4], 0


ebx, 1

; sum = 0
; ebx (i) = 1

ebx, [ebp+12]
end_for

; i <= n ?

[ebp-4], ebx
ebx
short for_loop

; sum += i

ebx, [ebp+8]
eax, [ebp-4]
[ebx], eax

; ebx = sump
; eax = sum
; *sump = sum;

11
12
13
14

add
inc
jmp

15
16
17
18
19

end_for:
mov
mov
mov

20
21
22
23

mov
pop
ret

esp, ebp
ebp

Fig. 4.8  Version assembleur de sum

crit en assembleur.
La Figure 4.9 montre quoi ressemble la pile aprs le prologue du programme de la Figure 4.8. Cette section de la pile qui contient les paramtres,
les informations de retour et les variables locales est appele

cadre de pile

(stack frame). Chaque appel de fonction C cre un nouveau cadre de pile sur
la pile.

En dpit du fait que

ENTER

Le prologue et l'pilogue d'un sous-programme peuvent tre simplis en et LEAVE simplient le


utilisant deux instructions spciales qui sont conues spcialement dans ce prologue et l'pilogue, ils

ENTER eectue le prologue et l'instruction LEAVE l'pilogue.


ENTER prend deux oprandes immdiates. Dans la convention

but. L'instruction
L'instruction

ne sont pas utiliss trs


souvent. Pourquoi ? Parce

qu'ils sont plus lent que les


d'appel C, la deuxime oprande est toujours 0. La premire oprande est instructions plus simples
le nombre d'octets ncessaires pour les variables locales. L'instruction LEAVE quivalentes ! C'est un des

n'a pas d'oprande. La Figure 4.10 montre comment ces instructions sont exemples o il ne faut pas
utilises. Notez que le squelette de programme (Figure 1.7) utilise galement supposer qu'une instruction est plus rapide qu'une
ENTER et LEAVE.
squence de plusieurs instructions.

82

CHAPITRE 4.

SOUS-PROGRAMMES

ESP + 16

EBP + 12

ESP + 12

EBP + 8

n
sump

ESP + 8

EBP + 4

Adresse de retour

ESP + 4

EBP

ESP

EBP - 4

EBP sauv

sum

Fig. 4.9  tat de la pile aprs le prologue de sum

1
2
3
4
5

etiquette_sousprogramme:
enter LOCAL_BYTES, 0
; code du sous-programme
leave
ret

; = nb octets pour les locales

Fig. 4.10  Forme gnrale d'un sous-programme avec des variables locales

utilisant

4.6
Un

ENTER

et

LEAVE

Programme Multi-Modules

programme multi-modules

est un programme compos de plus d'un

chier objet. Tous les programmes prsents jusqu'ici sont des programmes
multi-modules. Il consistent en un chier objet C pilote et le chier objet
assembleur (plus les chiers objet de la bibliothque C). Souvenez vous que
l'diteur de liens combine les chier objets en un programme excutable
unique. L'diteur de liens doit rapprocher toutes les rfrences faites chaque

i.e.

tiquette d'un module (

un chier objet) de sa dnition dans un autre

module. An que le module A puisse utiliser une tiquette dnie dans le
module B, la directive

extern

doit tre utilise. Aprs la directive

extern

vient une liste d'tiquettes dlimites par des virgules. La directive indique
l'assembleur de traiter ces tiquettes comme

externes au module. C'est--dire

qu'il s'agit d'tiquettes qui peuvent tre utilises dans ce module mais sont
dnies dans un autre. Le chier

etc. comme externes.

asm_io.inc

dnit les routines

read_int,

En assembleur, les tiquettes ne peuvent pas tre accdes de l'extrieur


par dfaut. Si une tiquette doit pouvoir tre accde depuis d'autres modules
que celui dans lequel elle est dnie, elle doit tre dclare comme
dans son module, par le biais de la directive

globale

global . La ligne 13 du listing du


_asm_main est

programme squelette de la Figure 1.7 montre que l'tiquette

dnie comme globale. Sans cette dclaration, l'diteur de liens indiquerait


une erreur. Pourquoi ? Parce que le code C ne pourrait pas faire rfrence
l'tiquette

interne _asm_main.

4.6.

83

PROGRAMME MULTI-MODULES

Voici le code de l'exemple prcdent rcrit an d'utiliser deux modules.


Les deux sous-programmes (get_int et
sources distincts de celui de la routine

%include "asm_io.inc"

print_sum)
_asm_main.

sont dans des chiers

main4.asm

2
3
4

segment .data
sum
dd 0

5
6
7

segment .bss
input resd 1

8
9
10
11
12
13
14

segment .text
global _asm_main
extern get_int, print_sum
_asm_main:
enter 0,0
; routine d'initialisation
pusha

15
16
17
18
19
20
21

mov
while_loop:
push
push
call
add

edx, 1

; edx est le 'i' du pseudo-code

edx
dword input
get_int
esp, 8

; empile i
; empile l'adresse de input
; dpile i et &input

22
23
24
25

mov
cmp
je

eax, [input]
eax, 0
end_while

add

[sum], eax

inc
jmp

edx
short while_loop

26
27

; sum += input

28
29
30
31
32
33
34
35

end_while:
push
call
pop

36
37
38

popa
leave

dword [sum]
print_sum
ecx

; empile la valeur de sum


; dpile [sum]

84

39

CHAPITRE 4.

ret

SOUS-PROGRAMMES

main4.asm

%include "asm_io.inc"

sub4.asm

2
3
4

segment .data
prompt db

") Entrez un nombre entier (0 pour quitter): ", 0

5
6
7
8
9

segment .text
global get_int, print_sum
get_int:
enter 0,0

10
11
12

mov
call

eax, [ebp + 12]


print_int

mov
call

eax, prompt
print_string

call
mov
mov

read_int
ebx, [ebp + 8]
[ebx], eax

13
14
15
16
17
18
19

; stocke la saisie en mmoire

20
21
22

leave
ret

; retour l'appelant

23
24
25

segment .data
result db

"La somme vaut ", 0

segment .text
print_sum:
enter

0,0

26
27
28
29
30
31
32

mov
call

eax, result
print_string

mov
call
call

eax, [ebp+8]
print_int
print_nl

33
34
35
36
37
38
39

leave
ret

sub4.asm

4.7.

INTERFACER DE L'ASSEMBLEUR AVEC DU C

85

L'exemple ci-dessus n'a que des tiquettes de code globales ; cependant,


les tiquettes de donne globales fonctionnent exactement de la mme faon.

4.7

Interfacer de l'assembleur avec du C

Aujourd'hui, trs peu de programmes sont crits compltement en assembleur. Les compilateurs sont trs performants dans la conversion de code
de haut niveau en code machine ecace. Comme il est plus facile d'crire
du code dans un langage de haut niveau, ils sont plus populaires. De plus,
le code de haut niveau est

beaucoup

plus portable que l'assembleur !

Lorsque de l'assembleur est utilis, c'est souvent pour de petites parties


du code. Cela peut tre fait de deux faons : en appelant des sous-routines assembleur depuis le C ou en incluant de l'assembleur. Inclure de l'assembleur
permet au programmeur de placer des instructions assembleur directement
dans le code C. Cela peut tre trs pratique ; cependant, il y a des inconvnients inclure l'assembleur. Le code assembleur doit tre crit dans le
format que le compilateur utilise. Aucun compilateur pour le moment ne
supporte le format NASM. Des compilateurs dirents demandent des formats dirents. Borland et Microsoft demandent le format MASM. DJGPP
et gcc sous Linux demandent le format GAS

3 . La technique d'appel d'une

sous-routine assembleur est beaucoup plus standardise sur les PC.


Les routines assembleur sont habituellement utilises avec le C pour les
raisons suivantes :
 Un accs direct au fonctionnalits matrielles de l'ordinateur est ncessaire car il est dicle ou impossible d'y accder en C.
 La routine doit tre la plus rapide possible et le programmeur peut
optimiser le code la main mieux que le compilateur.
La dernire raison n'est plus aussi valide qu'elle l'tait. La technologie
des compilateurs a t amliore au l des ans et les compilateurs gnrent
souvent un code trs performant (en particulier si les optimisations actives).
Les inconvnients des routines assembleur sont une portabilit et une lisibilit
rduites.
La plus grande partie des conventions d'appel C a dj t prsente.
Cependant, il y a quelques fonctionnalits supplmentaires qui doivent tre
dcrites.

GAS est l'assembleur GNU que tous les compilateurs GNU utilisent. Il utilise la syntaxe AT&T qui est trs direntes des syntaxes relativement similaires de MASM, TASM
et NASM.

86

1
2
3

CHAPITRE 4.

segment .data
x
dd
format
db

SOUS-PROGRAMMES

0
"x = %d\n", 0

4
5
6
7
8
9
10

segment .text
...
push dword [x]
push dword format
call _printf
add
esp, 8

;
;
;
;

empile la valeur de x
empile l'adresse de la chane format
notez l'underscore!
dpile les paramtres

Fig. 4.11  Appel de

EBP + 12
EBP + 8
EBP + 4
EBP

printf

Valeur de

Adresse de la chaine format


Adresse de retour
Sauvegarde de EBP

Fig. 4.12  Pile l'intrieur de

printf

4.7.1 Sauvegarder les registres


Le mot cl

register

peut

Tout d'abord, Le C suppose qu'une sous-routine maintient les valeurs des

tre utilis dans une dcla- registres suivants : EBX, ESI, EDI, EBP, CS, DS, SS, ES. Cela ne signie
ration de variable C pour pas que la sous-routine ne les change pas en interne. Cela signie que si elle
suggrer au compilateur
change leur valeurs, elle doit les restaurer avant de revenir. Les valeurs de
d'utiliser un registre pour
EBX, ESI et EDI doivent tre inchanges car le C utilise ces registres pour
cette variable plutot qu'un
les
. Habituellement, la pile est utilise pour sauvegarder
emplacement mmoire. On
appelle ces variables, va- les valeurs originales de ces registres.

variables de registre

riables

de

registre.

Les

compilateurs modernes le
font automatiquement sans
qu'il y ait besoin d'une suggestion.

4.7.2 Etiquettes de fonctions


La plupart des compilateurs C ajoutent un caractre underscore(_) au
dbut des noms des fonctions et des variables global/static. Par exemple,
une fonction appele

routine assembleur, elle


gcc, n'ajoute

aucun

_f. Donc, s'il s'agit d'une


_f, pas f. Le compilateur Linux

sera assigne l'tiquette

doit

tre tiquete

caractre. Dans un executable Linux ELF, on utiliserait

simplement l'tiquette

pour la fonction

f.

Cependant, le gcc de DJGPP

ajoute un underscore. Notez que dans le squelette de programme assembleur


(Figure 1.7), l'tiquette de la routine principale est

_asm_main.

4.7.

87

INTERFACER DE L'ASSEMBLEUR AVEC DU C

4.7.3 Passer des paramtres


Dans les conventions d'appel C, les arguments d'une fonction sont empils
sur la pile dans l'ordre

inverse

de celui dans lequel ils apparassent dans

l'appel de la fonction.
Considrons l'expression C suivante :

printf("x = %d\n",x);

la Fi-

gure 4.11 montre comment elle serait compile (dans le format NASM quivalent). La Figure 4.12 montre quoi ressemble la pile aprs le prologue de
la fonction

printf.

printf

La fonction

est une des fonctions de la biblio-

thque C qui peut prendre n'importe quel nombre d'arguments. Les rgles
des conventions d'appel C ont t spcialement crite pour autoriser ce type
de fonctions. Comme l'adresse de la chane format est empile en dernier, Il
son emplacement sur la pile sera

toujours EBP + 8 quel que soit le nombre

de paramtres passs la fonction. Le code de

printf peut alors analyser la

chane format pour dterminer combien de paramtres ont du tre passs et


les rcuprer sur la pile.

printf("x = %d\n"), le code de printf


double mot en [EBP + 12]. Cependant, ce

Bien sr, s'il y a une erreur,


achera quand mme la valeur
ne sera pas la valeur de

x!

Trouver l'adresse d'une tiquette dnie dans les segments

data

ou

bss

tif. Nanmoins, c'est un besoin trs courant lors de l'appel de sous-routines.


Considrons le cas du passage de l'adresse d'une variable (appelons la

foo).

Si

est situ en EBP

x)

8 sur la pile, on ne

peut pas utiliser simplement :

eax, ebp - 8
MOV

stocke dans EAX doit tre calcule par l'as-

sembleur (c'est--dire qu'elle doit donner une constante). Cependant, il y a

LEA (pour Load


Eective Address, Charger l'Adresse Eective). L'extrait suivant calculerait
une instruction qui eectue le calcul dsir. Elle est appele
l'adresse de

lea

et la stockerait dans EAX :

eax, [ebp - 8]

Maintenant, EAX contient l'adresse de


l'appel de la fonction

foo.

x et peut tre plac sur la pile lors de

Ne vous mprenez pas, au niveau de la syntaxe,

c'est comme si cette instruction lisait la donne en [EBP8] ; cependant,


ce n'est

pas

grer

alatoire
C.

ncessaire
l'assembleur
un

des

peuvent

en

stdarg.h

L'en-tte

dnit

nombre

d'arguments
macros

tre

qui

utilises

pour l'eectuer de faon

vrai. L'instruction

LEA

ne lit

jamais

bon

livre

sur

pour plus de dtails.

d'une variable locale (ou d'un paramtre) sur la pile n'est pas aussi intui-

Pourquoi ? La valeur que

pour

quel

est simple. Basiquement, l'diteur de liens le fait. Cependant, calculer l'adresse

mov

pas

portable. Voyez n'importe

4.7.4 Calculer les adresses des variables locales

une fonction (appelons la

n'est

d'utiliser

la mmoire ! Elle calcule

simplement l'adresse qui sera lue par une autre instruction et stocke cette

le

88

CHAPITRE 4.

SOUS-PROGRAMMES

adresse dans sa premire oprande registre. Comme elle ne lit pas la mmoire,
aucune taille mmoire (

p.e. dword) n'est ncessaire ni autorise.

4.7.5 Retourner des valeurs


Les fonctions C non void retournent une valeur. Les conventions d'appel
C spcient comment cela doit tre fait. Les valeurs de retour sont passes
via les registres. Tous les types entiers (char,

int, enum, etc.) sont retourns

dans le registre EAX. S'ils sont plus petits que 32 bits, ils sont tendus
32 bits lors du stockage dans EAX (la faon dont ils sont tendus dpend
du fait qu'ils sont signs ou non). Les valeurs 64 bits sont retournes dans
la paire de registres EDX:EAX. Les valeurs de pointeurs sont galement
stockes dans EAX. Les valeurs en virgule ottante sont stockes dans le
registre STP du coprocesseur arithmtique (ce registre est dcrit dans le
chapitre sur les nombres en virgule ottante).

4.7.6 Autres conventions d'appel


Les rgles ci-dessus dcrivent les conventions d'appel C supportes par
tous les compilateurs C 80x86. Souvent, les compilateurs supportent galement d'autres conventions d'appel. Lorsqu'il y a une interface avec le langage
assembleur, il est

trs

important de connatre les conventions utilises par

le compilateur lorsqu'il appelle votre fonction. Habituellement, par dfaut,


ce sont les conventions d'appel standard qui sont utilises ; cependant, ce

n'est pas toujours le cas . Les compilateurs qui utilisent plusieurs conventions ont souvent des options de ligne de commende qui peuvent tre utiliss
pour changer la convention par dfaut. Il fournissent galement des extensions la syntaxe C pour assigner explicitement des conventions d'appel
des fonctions de manire individuelle. Cependant, ces extensions ne sont pas
standardises et peuvent varier d'un compilateur l'autre.
Le compilateur GCC autorise direntes conventions d'appel. La convention utilise par une fonction peut tre dclare explicitement ent utilisant
l'extension

__attribute__

. Par exemple, pour dclarer une fonction void

qui utilise la convention d'appel standard appele


ramtre

int,

qui ne prend qu'un pa-

utilisez la syntaxe suivante pour son prototype :

void f ( int ) __attribute__((cdecl));


GCC supporte galement la convention d'appel

standard call

. La fonction

ci-dessus pourrait tre dclare an d'utiliser cette convention en remplaant


le

cdecl par stdcall. La dirence entre stdcall et cdecl est que stdcall

impose la sous-routine de retirer les paramtres de la pile (comme le fait la

4
Le compiltateur C Watcom est un exemple qui n'utilise pas les conventions standards
par dfaut. Voyez les sources exemple pour Watcom pour plus de dtails

4.7.

89

INTERFACER DE L'ASSEMBLEUR AVEC DU C

convention d'appel Pascal). Donc, la convention

stdcall ne peut tre utilise

i.e.

que par des fonctions qui prennent un nombre xe d'arguments (


qui ne sont pas comme

printf

et

scanf).

GCC supporte galement un attribut supplmentaire appel

celles

regparm qui

indique au compilateur d'utiliser les registres pour passer jusqu' 3 arguments entiers une fonction au lieu d'utiliser la pile. C'est un type d'optimisation courant que beaucoup de compilateurs supportent.
Borland et Microsoft utilisent une syntaxe commune pour dclarer les
conventions d'appel. Ils ajoutent les mots cls

__cdecl

et

__stdcall

au

C. Ces mots cls se comportent comme des modicateurs de fonction et


appraissent immdiatement avant le nom de la fonction dans un prototype.
Par exemple, la fonction

ci-dessus serait dnie comme suit par Borland

et Microsoft:

void __cdecl f( int );


Il y a des avantages et des inconvnients chacune des conventions d'appel. Les principaux avantages de

cdecl est qu'elle est simple et trs exible.

Elle peut tre utilise pour n'importe quel type de fonction C et sur n'importe
quel compilateur C. Utiliser d'autres conventions peut limiter la portabilit
de la sous-routine. Son principal inconvnient est qu'elle peut tre plus lente
que certaines autres et utilise plus de mmoire (puisque chaque appel de
fonction ncessite du code pour retirer les paramtres de la pile).

stdcall est qu'elle utilise moins de mmoire


que cdecl. Aucun nettoyage de pile n'est requis aprs l'instruction CALL. Son
L'avantage de la convention

principal inconvnient est qu'elle ne peut pas tre utilise avec des fonctions
qui ont un nombre variable d'arguments.
L'avantage d'utiliser une convention qui se sert des registres pour passer
des paramtres entiers est la rapidit. Le principal inconvnient est que la
convention est plus complexe. Certains paramtres peuvent se trouver dans
des registres et d'autres sur la pile.

4.7.7 Exemples
Voici un exemple qui montre comment une routine assembleur peut tre
interface avec un programme C (notez que ce programme n'utilise pas le
programme assembleur squelette (Figure 1.7) ni le module driver.c).

main5.c
1
2
3
4

#include <stdio.h>
/ prototype de la routine assembleur /
void calc_sum( int , int ) __attribute__((cdecl));

90

5
6
7
9
10
11
12
14

SOUS-PROGRAMMES

int main( void )


{
int n, sum;

13

CHAPITRE 4.

printf ("Somme des entiers jusqu ' : ");


scanf("%d", &n);
calc_sum(n, &sum);
printf ("La somme vaut %d\n", sum);
return 0;

main5.c
1
2
3
4
5
6
7
8
9
10
11
12
13

;
;
;
;
;
;
;
;
;
;
;
;
;

sub5.asm
sous-routine _calc_sum
trouve la somme des entiers de 1 n
Paramtres:
n
- jusqu'o faire la somme (en [ebp + 8])
sump - pointeur vers un entier dans lequel stocker la somme (en [ebp + 12])
pseudo-code C:
void calc_sum( int n, int * sump )
{
int i, sum = 0;
for( i=1; i <= n; i++ )
sum += i;
*sump = sum;
}

14
15
16
17
18
19
20
21
22

segment .text
global _calc_sum
;
; variable locale :
; sum en [ebp-4]
_calc_sum:
enter 4,0
push
ebx

; Fait de la place pour sum sur la pile


; IMPORTANT !

23
24
25
26
27
28
29

mov
dword [ebp-4],0
dump_stack 1, 2, 4
mov
ecx, 1
for_loop:
cmp
ecx, [ebp+8]
jnle
end_for

; sum = 0
; affiche la pile de ebp-8 ebp+16
; ecx est le i du pseudocode
; cmp i et n
; si non i <= n, quitter

4.7.

91

INTERFACER DE L'ASSEMBLEUR AVEC DU C

Somme des entiers jusqu'a : 10


Stack Dump # 1
EBP = BFFFFB70 ESP = BFFFFB68
+16 BFFFFB80 080499EC
+12 BFFFFB7C BFFFFB80
+8 BFFFFB78 0000000A
+4 BFFFFB74 08048501
+0 BFFFFB70 BFFFFB88
-4 BFFFFB6C 00000000
-8 BFFFFB68 4010648C
La somme vaut 55

Fig. 4.13  Excution du programme sub5

30
31
32
33

add
inc
jmp

[ebp-4], ecx
ecx
short for_loop

; sum += i

ebx, [ebp+12]
eax, [ebp-4]
[ebx], eax

; ebx = sump
; eax = sum

ebx

; restaure ebx

34
35
36
37
38

end_for:
mov
mov
mov

39
40
41
42

pop
leave
ret

Pourquoi la ligne 22 de

sub5.asm
sub5.asm est si importante ? Parce que les conven-

tions d'appel C imposent que la valeur de EBX ne soit pas modie par l'appel de fonction. Si ce n'est pas respect, il est trs probable que le programme
ne fonctionne pas correctement.
La ligne 25 montre la faon dont fonctionne la macro

dump_stack.

Sou-

venez vous que le premier paramtre est juste une tiquette numrique et
les deuxime et troisime paramtres dterminent respectivement combien
de doubles mots elle doit acher en-dessous et au-dessus de EBP. La Figure 4.13 montre une excution possible du programme. Pour cette capture,
on peut voir que l'adresse du dword o stocker la somme est BFFFFB80 (en
EBP + 12) ; le nombre jusqu'auquel additionner est 0000000A (en EBP + 8) ;
l'adresse de retour pour la routine est 08048501 (en EBP + 4) ; la valeur sauvegarde de EBP est BFFFFB88 (en EBP); la valeur de la variable locale est

92

CHAPITRE 4.

SOUS-PROGRAMMES

0 en (EBP - 4) ; et pour nir, la valeur sauvegarde de EBX est 4010648C


(en EBP - 8).
La fonction

calc_sum

pourrait tre rcrite pour retourner la somme

plutt que d'utiliser un pointeur. Comme la somme est une valeur entire,
elle doit tre place dans le registre EAX. La ligne 11 du chier

main5.c

deviendrait :

sum = calc_sum(n);
De plus, le prototype de

calc_sum

devrait tre altr. Voici le code assem-

bleur modi :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

sub6.asm
; sous-routine _calc_sum
; trouve la somme des entiers de 1 n
; Paramtres:
; n
- jusqu'o faire la somme (en [ebp + 8])
; Valeur de retour :
; valeur de la somme
; pseudo-code C :
; int calc_sum( int n )
; {
; int i, sum = 0;
; for( i=1; i <= n; i++ )
;
sum += i;
; return sum;
; }
segment .text
global _calc_sum
;
; variable locale :
; sum en [ebp-4]
_calc_sum:
enter 4,0
; fait de la place pour la somme sur la pile

22
23
24
25
26
27

mov
mov
for_loop:
cmp
jnle

dword [ebp-4],0
ecx, 1

; sum = 0
; ecx est le i du pseudocode

ecx, [ebp+8]
end_for

; cmp i et n
; si non i <= n, quitter

[ebp-4], ecx
ecx
short for_loop

; sum += i

28
29
30
31

add
inc
jmp

4.8.

1
2

93

SOUS-PROGRAMMES RENTRANTS ET RCURSIFS

segment .data
format
db "%d", 0

3
4
5
6
7
8
9
10
11

segment .text
...
lea
eax, [ebp-16]
push eax
push dword format
call _scanf
add
esp, 8
...

Fig. 4.14  Appeler

scanf

depuis l'assembleur

32
33
34

end_for:
mov

eax, [ebp-4]

; eax = sum

35
36
37

leave
ret

sub6.asm

4.7.8 Appeler des fonctions C depuis l'assembleur


Un des avantages majeurs d'interfacer le C et l'assembleur est que cela
permet au code assembleur d'accder la grande bibliothque C et aux
fonctions utilisateur. Par exemple, si l'on veut appeler la fonction

scanf pour

lire un entier depuis le clavier ? La Figure 4.14 montre comment le faire. Une
chose trs importante se rappeler est que

scanf suit les conventions d'appel

C standards la lettre. Cela signie qu'elle prserve les valeurs des registres
EBX, ESI et EDI ; cependant, les registres EAX, ECX et EDX peuvent tre
modis ! En fait, EAX sera modi car il contiendra la valeur de retour de

scanf. Pour d'autres exemple d'interface entre l'assembleur et le C,


observez le code dans asm_io.asm qui a t utilis pour crer asm_io.obj.

l'appel

4.8

Sous-Programmes Rentrants et Rcursifs

Un sous-programme rentrant rempli les critres suivants :


 Il ne doit pas modier son code. Dans un langage de haut niveau, cela
serait dicile mais en assembleur, il n'est pas si dur que cela pour un
programme de modier son propre code. Par exemple :

94

CHAPITRE 4.

mov
add

word [cs:$+7], 5
ax, 2

SOUS-PROGRAMMES

; copie 5 dans le mot 7 octets plus loin


; l'expression prcdente change 2 en 5 !

Ce code fonctionnerait en mode rel, mais sur les systmes d'exploitation en mode protg, le segment de code est marqu en lecture seule.
Lorsque la premire ligne ci-dessus s'excute, le programme est interrompu sur ces systmes. Cette faon de programmer est mauvaise pour
beaucoup de raison. Elle porte confusion, est dicile maintenir et
ne permet pas le partage de code (voir ci-dessous).
 Il ne doit pas modier de donnes globales (comme celles qui se trouvent

data

dans les segments

et

bss).

Toutes les variables sont stockes sur

la pile.
Il y a plusieurs avantages crire du code rentrant.
 Un sous-programme rentrant peut tre appel rcursivement.
 Un programme rentrant peut tre partag par plusieurs processus.
Sur beaucoup de systmes d'exploitation multi-tches, s'il y a plusieurs instances d'un programme en cours, seule

une

copie du code se

trouve en mmoire. Les bibliothques partages et les DLLs (

Link Libraries,

Dynamic

Bibliothques de Lien Dynamique) utilisent le mme

principe.
 Les sous-programmes rentrants fonctionnent beaucoup mieux dans

les programmes multi-threads . Windows 9x/NT et la plupart des


systmes d'exploitation de style Unix (Solaris, Linux,

etc.) supportent

les programmes multi-threads.

4.8.1 Sous-programmes rcursifs


Ce type de sous-programmes s'appellent eux-mmes. La rcursivit peut
tre soit

directe

soit

programme, disons

indirecte. La rcursivit directe survient lorsqu'un sous-

foo,

s'appelle lui-mme dans le corps de

foo.

La rcur-

sivit indirecte survient lorsqu'un sous-programme ne s'appelle pas directement lui-mme mais via un autre sous-programme qu'il appelle. Par exemple,
le sous-programme

foo

pourrait appeler

bar

et

bar

pourrait appeler

Les sous-programmes rcursifs doivent avoir une

son.

foo.

condition de terminai-

Lorsque cette condition est vraie, il n'y a plus d'appel rcursif. Si une

routine rcursive n'a pas de condition de terminaison ou que la condition


n'est jamais remplie, la rcursivit ne s'arrtera jamais (exactement comme
une boucle innie).
La Figure 4.15 montre une fonction qui calcule une factorielle rcursivement. Elle peut tre appele depuis le C avec :

x = fact (3);

/ trouve 3! /

5
Un programme multi-thread a plusieurs threads d'excution. C'est--dire que le programme lui-mme est multi-tches.

4.8.

1
2
3
4
5

SOUS-PROGRAMMES RENTRANTS ET RCURSIFS

; trouve n!
segment .text
global _fact
_fact:
enter 0,0

6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

mov
cmp
jbe
dec
push
call
pop
mul
jmp
term_cond:
mov
end_fact:
leave
ret

eax, [ebp+8]
; eax = n
eax, 1
term_cond
; si n <= 1, termin
eax
eax
_fact
; appel fact(n-1) rcursivement
ecx
; rponse dans eax
dword [ebp+8]
; edx:eax = eax * [ebp+8]
short end_fact
eax, 1

Fig. 4.15  Fonction factorielle rcursive

cadre n=3

n(3)
Adresse de retour

EBP Sauv
n(2)
cadre n=2

Adresse de retour

EBP Sauv
n(1)
cadre n=1

Adresse de retour

EBP Sauv
Fig. 4.16  Cadres de pile pour la fonction factorielle

95

96

1
2
3
4
5
6
7
8

CHAPITRE 4.

SOUS-PROGRAMMES

void f ( int x )
{
int i ;
for ( i=0; i < x; i++ ) {
printf ("%d\n", i);
f( i );
}
}
Fig. 4.17  Un autre exemple (version C)

La Figure 4.16 montre quoi ressemble la pile au point le plus profond pour
l'appel de fonction ci-dessus.
Les Figures 4.17 et 4.18 montrent un exemple rcursif plus compliqu en

f(3) ? Notez que


ENTER cre un nouveau i sur la pile pour chaque appel rcursif.
Donc, chaque instance rcursive de f a sa propre variable i indpendante.
Dnir i comme un double mot dans le segment data ne fonctionnerait pas
C et en assmbleur, respectivement. Quelle est la sortie pour

l'instruction

pareil.

4.8.2 Rvision des types de stockage des variables en C


Le C fournit plusieurs types de stockage des variables.

global

Ces variables sont dclares en dehors de toute fonction et sont sto-

ckes des emplacements mmoire xes (dans les segments

bss)

data

ou

et existent depuis le dbut du programme jusqu' la n. Par

dfaut, on peut y accder de n'importe quelle fonction dans le programme ; cependant, si elles sont dclares comme

static,

i.e.

fonctions dans le mme module peuvent y accder (

seules les

en termes as-

sembleur, l'tiquette est interne, pas externe).

static

Il s'agit des variables

locales d'une fonctions qui sont dclares static

(Malheureusement, le C utilise le mot cl

static

avec deux sens di-

rents !) Ces variables sont galement stockes dans des emplacements


mmoire xes (dans

data

ou

bss),

mais ne peuvent tre accdes di-

rectement que dans la fonction o elles sont dnies.

automatic

C'est le type par dfaut d'une variable C dnie dans une fonc-

tion. Ces variables sont alloues sur la pile lorsque la fonction dans
laquelle elles sont dnies est appele et sont dsalloues lorsque la
fonction revient. Donc, elles n'ont pas d'emplacement mmoire xe.

register

Ce mot cl demande au compilateur d'utiliser un registre pour la

donne dans cette variable. Il ne s'agit que d'une

requte.

Le compi-

4.8.

1
2
3
4
5
6
7
8
9

SOUS-PROGRAMMES RENTRANTS ET RCURSIFS

%define i ebp-4
%define x ebp+8
; macros utiles
segment .data
format
db "%d", 10, 0
; 10 = '\n'
segment .text
global _f
extern _printf
_f:
enter 4,0
; alloue de la place sur la pile pour i

10
11
12

lp:

13
14
15

mov

dword [i], 0

; i = 0

mov
cmp
jnl

eax, [i]
eax, [x]
quit

; i < x?

push
push
call
add

eax
format
_printf
esp, 8

; appelle printf

push
call
pop

dword [i]
_f
eax

; appelle f

inc
jmp

dword [i]
short lp

; i++

16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

quit:

leave
ret

Fig. 4.18  Un autre exemple (version assembleur)

97

98

CHAPITRE 4.

lateur n'a

pas

SOUS-PROGRAMMES

l'honorer. Si l'adresse de la variable est utilise un

endroit quelconque du programme, elle ne sera pas respecte (puisque


les registres n'ont pas d'adresse). De plus, seuls les types entiers simples
peuvent tre des valeurs registres. Les types structures ne peuvent
pas l'tre ; ils ne tiendraient pas dans un registre ! Les compilateurs C
placent souvent les variables automatiques normales dans des registres
sans aide du programmeur.

volatile

Ce mot cl indique au compilateur que la valeur de la variable

peut changer tout moment. Cela signie que le compilateur ne peut


faire aucune supposition sur le moment o la variable est modie.
Souvent, un compilateur stocke la valeur d'une variable dans un registre
temporairement et utilise le registre la place de la variable dans
une section de code. Il ne peut pas faire ce type d'optimisations avec
les variables

volatile.

Un exemple courant de variable volatile serait

une variable qui peut tre modie par deux threads d'un programme
multi-thread. Considrons le code suivant :

1
2
3

x = 10;
y = 20;
z = x;
Si

pouvait tre altr par un autre thread, il est possible que l'autre

thread change
Cependant, si

x
x

entre les lignes 1 et 3, alors

ne vaudrait pas 10.

n'a pas t dclar comme volatile, le compilateur

peut supposer que

est inchang et positionner

Une autre utilisation de

10.

volatile est d'empcher le compilateur d'uti-

liser un registre pour une variable.

Chapitre 5

Tableaux
5.1

Introduction

Un

tableau est un bloc contigu de donnes en mmoire. Tous les lments

de la liste doivent tre du mme type et occuper exactement le mme nombre


d'octets en mmoire. En raison de ces proprits, les tableaux permettent
un accs ecace une donne par sa position (ou indice) dans le tableau.
L'adresse de n'importe quel lment peut tre calcule en connaissant les
trois choses suivantes :
 L'adresse du premier lment du tableau
 Le nombre d'octets de chaque lment
 L'indice de l'lment
Il est pratique de considrer l'indice du premier lment du tableau
comme tant 0 (comme en C). Il est possible d'utiliser d'autres valeurs pour
le premier indice mais cela complique les calculs.

5.1.1 Dnir des tableaux


Dnir des tableaux dans les segments data et bss
Pour dnir un tableau initialis dans le segment
tives

db, dw, etc.

tive utile appele

data, utilisez les direc-

normales. directives. NASM fournit galement une direc-

TIMES qui peut tre utilise pour rpter une expression de

nombreuses fois sans avoir la dupliquer la main. La Figure 5.1 montre


plusieurs exemples.
Pour dnir un tableau non initialis dans le segment
directives

bss,

utilisez les

resb, resw, etc. Souvenez vous que ces directives ont une oprande

qui spcie le nombre d'units mmoire rserver. La Figure 5.1 montre


galement des exemples de ces types de dnitions.
99

100

1
2
3
4
5
6
7
8
9
10

CHAPITRE 5.

TABLEAUX

segment .data
; dfinit un tableau de 10 doubles mots initialiss 1,2,..,10
a1
dd 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
; dfinit un tableau de 10 mots initialiss 0
a2
dw 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
; idem mais en utilisant TIMES
a3
times 10 dw 0
; dfinit un tableau d'octets avec 200 0 puis 100 1
a4
times 200 db 0
times 100 db 1

11
12
13
14
15
16

segment .bss
; dfinit un
a5
; dfinit un
a6

tableau de 10 doubles mots non initialiss


resd 10
tableau de 100 mots non initialiss
resw 100

Fig. 5.1  Dnir des tableaux

Dnir des tableaux comme variables locales


Il n'y a pas de manire directe de dnir un tableau comme variable
locale sur la pile. Comme prcdemment, on calcule le nombre total d'octets
requis pour

toutes

les variables locales, y compris les tableaux, et on l'te de

ESP (soit directement, soit en utilisant l'instruction

ENTER). Par exemple, si

une fonction a besoin d'une variable caractre, deux entiers double-mot et un


tableau de 50 lments d'un mot, il faudrait

1 + 2 4 + 50 2 = 109

octets.

Cependant, le nombre t de ESP doit tre un multiple de quatre (112 dans


ce cas) pour maintenir ESP sur un multiple de double mot. On peut organiser
les variables dans ces 109 octets de plusieurs faons. La Figure 5.2 montre
deux manires possibles. La partie inutilise sert maintenir les doubles mots
sur des adresses multiples de doubles mots an d'acclrer les accs mmoire.

5.1.2 Accder aux lments de tableaux


Il n'y a pas d'oprateur

[ ]

en langage assembleur comme en C. Pour

accder un lment d'un tableau, son adresse doit tre calcule. Considrons
les deux dnitions de tableau suivantes :

array1
array2

db
dw

5, 4, 3, 2, 1
5, 4, 3, 2, 1

; tableau d'octets
; tableau de mots

5.1.

101

INTRODUCTION

EBP - 1

char
unused

EBP - 8

dword 1

EBP - 12

dword 2

word
array

word
array

EBP - 100
EBP - 104

dword 1

EBP - 108

dword 2

EBP - 109
EBP - 112

char
unused

Fig. 5.2  Organisations possibles de la pile

Voici quelques exemple utilisant ces tableaux :

1
2
3
4
5
6
7

mov
mov
mov
mov
mov
mov
mov

al, [array1]
al, [array1 +
[array1 + 3],
ax, [array2]
ax, [array2 +
[array2 + 6],
ax, [array2 +

1]
al
2]
ax
1]

;
;
;
;
;
;
;

al = array1[0]
al = array1[1]
array1[3] = al
ax = array2[0]
ax = array2[1] (PAS array2[2]!)
array2[3] = ax
ax = ??

A la ligne 5, l'lment 1 du tableau de mots est rfrenc, pas l'lment 2.


Pourquoi ? Les mots sont des units de deux octets, donc pour se dplacer
l'lment suivant d'un tableau de mots, on doit se dplacer de deux octets, pas d'un seul. La ligne 7 lit un octet du premier lment et un octet
du suivant. En C, le compilateur regarde la type de pointeur pour dterminer de combien d'octets il doit se dplacer dans une expression utilisant
l'arithmtique des pointeurs, an que le programmeur n'ait pas le faire.
Cependant, en assembleur, c'est au programmeur de prendre en compte a
taille des lments du tableau lorsqu'il se dplace parmi les lments.
La Figure 5.3 montre un extrait de code qui additionne tous les lments
de

array1

de l'exemple de code prcdent. A la ligne 7, AX est ajout

DX. Pourquoi pas AL ? Premirement, les deux oprandes de l'instruction

ADD

doivent avoir la mme taille. Deuximement, il serait facile d'ajouter

des octets et d'obtenir une somme qui serait trop grande pour tenir sur un
octet. En utilisant DX, la somme peut atteindre 65 535. Cependant, il est
important de raliser que AH est galement additionn. C'est pourquoi, AH
est positionn zro

1 la ligne 3.

1
Positionner AH zro quivaut supposer implicitement que AL est un nombre non
sign. S'il est sign, l'action approprie serait d'insrer une instruction CBW entre les lignes 6

102

1
2
3
4
5

lp:

6
7
8
9

CHAPITRE 5.

TABLEAUX

mov
mov
mov
mov

ebx, array1
dx, 0
ah, 0
ecx, 5

; ebx = adresse de array1


; dx contiendra sum
; ?

mov
add
inc
loop

al, [ebx]
dx, ax
ebx
lp

; al = *ebx
; dx += ax (pas al!)
; bx++

Fig. 5.3  Faire la somme des lments d'un tableau (Version 1)

1
2
3
4

lp:

5
6
7
8
9
10

next:

mov
mov
mov

ebx, array1
dx, 0
ecx, 5

; ebx = adresse de array1


; dx contiendra la somme

add
jnc
inc

dl, [ebx]
next
dh

; dl += *ebx
; si pas de retenue goto next
; incrmente dh

inc
loop

ebx
lp

; bx++

Fig. 5.4  Faire la somme des lments d'un tableau (Version 2)

Les Figures 5.4 et 5.5 montrent deux manires alternatives de calculer la


somme. Les lignes en italique remplacent les lignes 6 et 7 de la Figure 5.3.

5.1.3 Adressage indirect plus avanc


Ce n'est pas tonnant, l'adressage indirect est souvent utilis avec les
tableaux. La forme la plus gnrale d'une rfrence mmoire indirecte est :

[ reg de base + facteur *reg d'index + constante ]


o :

reg de base

est un des registres EAX, EBX, ECX, EDX, EBP, ESP, ESI

ou EDI.

facteur
et 7.

est 1, 2, 4 ou 8 (S'il vaut 1, le facteur est omis).

5.1.

1
2
3
4
5
6
7
8

lp:

103

INTRODUCTION

mov
mov
mov

ebx, array1
dx, 0
ecx, 5

; ebx = adresse de array1


; dx contiendra la somme

add
adc
inc
loop

dl, [ebx]
dh, 0
ebx
lp

; dl += *ebx
; dh += drapeau de retenue + 0
; bx++

Fig. 5.5  Faire la somme des lments d'un tableau (Version 3)

reg d'index

est un des registres EAX, EBX, ECX, EDX, EBP, ESI, EDI

(Notez que ESP n'est pas dans la liste).

constante

est une constante 32 bits. Cela peut tre une tiquette (ou une

expression d'tiquette).

5.1.4 Exemple
Voici un exemple qui utilise un tableau et le passe une fonction. Il

array1c.c
driver.c.

utilise le programme
programme

1
2

%define ARRAY_SIZE 100


%define NEW_LINE 10

(dont le listing suit) comme pilote, pas le

array1.asm

3
4
5
6
7
8
9

segment .data
FirstMsg
Prompt
SecondMsg
ThirdMsg
InputFormat

db
db
db
db
db

segment .bss
array

resd ARRAY_SIZE

"10 premiers lments du tableau", 0


"Entrez l'indice de l'lment afficher : ", 0
"L'lment %d vaut %d", NEW_LINE, 0
"Elments 20 29 du tableau", 0
"%d", 0

10
11
12
13
14
15
16
17
18

segment .text
extern _puts, _printf, _scanf, _dump_line
global _asm_main
_asm_main:
enter 4,0
; variable locale dword en EBP - 4

104

19
20

CHAPITRE 5.

push
push

TABLEAUX

ebx
esi

21
22

; initialise le tableau 100, 99, 98, 97, ...

23
24
25
26
27
28
29

mov
mov
init_loop:
mov
add
loop

ecx, ARRAY_SIZE
ebx, array
[ebx], ecx
ebx, 4
init_loop

30
31
32
33

push
call
pop

dword FirstMsg
_puts
ecx

push
push
call
add

dword 10
dword array
_print_array
esp, 8

; affiche FirstMsg

34
35
36
37
38

; affiche les 10 premiers lments du tableau

39
40
41
42
43
44

; demande l'utilisateur l'indice de l'lment


Prompt_loop:
push
dword Prompt
call
_printf
pop
ecx

45
46
47
48
49
50
51
52

lea
push
push
call
add
cmp
je

eax, [ebp-4]
; eax = adresse du dword local
eax
dword InputFormat
_scanf
esp, 8
eax, 1
; eax = valeur de retour de scanf
InputOK

call
jmp

_dump_line ; ignore le reste de la ligne et recommence


Prompt_loop
; si la saisie est invalide

53
54
55
56
57
58
59
60

InputOK:
mov
push
push

esi, [ebp-4]
dword [array + 4*esi]
esi

5.1.

61
62
63

105

INTRODUCTION

push
call
add

dword SecondMsg
_printf
esp, 12

; affiche la valeur de l'lment

push
call
pop

dword ThirdMsg
_puts
ecx

; affiche les lments 20 29

push
push
call
add

dword 10
dword array + 20*4
_print_array
esp, 8

pop
pop
mov
leave
ret

esi
ebx
eax, 0

64
65
66
67
68
69
70
71
72

; adresse de array[20]

73
74
75
76
77
78

; retour au C

79
80
81
82
83
84
85
86
87
88

;
;
;
;
;
;
;
;
;

routine _print_array
Routine appelable depuis le C qui affiche les lments d'un tableau de doubles mots
comme des entiers signs.
Prototype C:
void print_array( const int * a, int n);
Paramtres:
a - pointeur vers le tableau afficher (en ebp+8 sur la pile)
n - nombre d'entiers afficher (en ebp+12 sur la pile)

89
90
91

segment .data
OutputFormat

db

"%-5d %5d", NEW_LINE, 0

92
93
94
95
96
97
98

segment .text
global
_print_array:
enter
push
push

_print_array
0,0
esi
ebx

99
100
101
102

xor
mov
mov

esi, esi
ecx, [ebp+12]
ebx, [ebp+8]

; esi = 0
; ecx = n
; ebx = adresse du tableau

106

103
104

CHAPITRE 5.

print_loop:
push

TABLEAUX

ecx

; printf change ecx !

push
push
push
call
add

dword [ebx + 4*esi]


esi
dword OutputFormat
_printf
esp, 12

; empile tableau[esi]

inc
pop
loop

esi
ecx
print_loop

pop
pop
leave
ret

ebx
esi

105
106
107
108
109
110

; retire les paramtres (laisse ecx !)

111
112
113
114
115
116
117
118
119

array1.asm

array1c.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

#include <stdio.h>
int asm_main( void );
void dump_line( void );
int main()
{
int ret_status ;
ret_status = asm_main();
return ret_status ;
}
/

fonction dump_line
retire tous les caractres restant sur la ligne courante dans le buer d'entre
/

void dump_line()
{
int ch;

20
21
22
23

while ( (ch = getchar()) != EOF && ch != '\n')


/ null body/ ;

5.1.

107

INTRODUCTION

array1c.c
L'instruction LEA revisite
L'instruction

LEA peut tre utilise dans d'autres cas que le calcul d'adresse.

Elle est assez couramment utilise pour les calculs rapides. Considrons le
code suivant :

lea

ebx, [4*eax + eax]

Il stocke la valeur de

5 EAX dans EBX. Utiliser LEA dans ce cas est la fois


MUL. Cependant, il faut tre conscient

plus simple et plus rapide que d'utiliser


du fait que l'expression entre crochets

doit

tre une adresse indirecte lgale.

Donc, par exemple, cette instruction ne peut pas tre utilise pour multiplier
par 6 rapidement.

5.1.5 Tableaux Multidimensionnels


Les tableaux multidimensionnels ne sont pas vraiments dirents de ceux
une dimension dont nous avons dj parl. En fait, il sont reprsents en
mmoire exactement comme cela, un tableau une dimension.

Tableaux Deux Dimensions


Ce n'est pas tonnant, le tableau multidimensionnel le plus simple est
celui deux dimensions. Un tableau deux dimensions est souvent reprsent
comme une grille d'lments. Chaque lment est identi par une paire
d'indices. Par convention, le premier indice est associ la ligne et le second
la colonne.
Considrons un tableau avec trois lignes et deux colonnes dni de la
manire suivante :

int a [3][2];
Le compilateur C rserverait de la place pour un tableau d'entiers de 6
(=

2 3)

et placerait les lments comme suit :


Indice
Elment

a[0][0]

a[0][1]

a[1][0]

a[1][1]

a[2][0]

a[2][1]

Ce que ce tableau tente de montrer et que l'lment rferenc comme

a[0][0]

est stock au dbut du tableau de 6 lments une dimension. L'lment

a[0][1]

est stock la position suivante (indice 1)

etc.

Chaque ligne du

tableau deux dimensions est stocke en mmoire de faon contige. Le dernier lment d'une ligne est suivi par le premier lment de la suivante. On

108

1
2
3
4
5

mov
sal
add
mov
mov

CHAPITRE 5.

eax,
eax,
eax,
eax,
[ebp

[ebp 1
[ebp [ebp +
- 52],

44]

;
;
48]
;
4*eax - 40] ;
eax
;

Fig. 5.6 

ebp - 44 est l'emplacement de i


multiplie i par 2
ajoute j
ebp - 40 est l'adresse de a[0][0]
stocke le rsultat dans x (en ebp - 52)

Assembleur correspondant

appelle cela la reprsentation

TABLEAUX

au niveau ligne

x = a[ i ][ j ]

(rowwise) du tableau et c'est

comme cela qu'un compilateur C/C++ reprsenterait le tableau.


Comment le compilateur dtermine o

a[i][j]

se trouve dans la repr-

sentation au niveau ligne ? Une formule simple calcule l'indice partir de


et

j. La formule dans ce cas est 2i+j . Il n'est pas compliqu de voir comment

on obtient cette formule. Chaque ligne fait deux lments de long ; donc le

i est l'emplacement 2i. Puis on obtient l'emplaj 2i. Cette analyse montre galement
gnralise un tableau de N colonnes : N i + j .

premier lment de la ligne


cement de la colonne

comment la formule est

en ajoutant

Notez que la formule ne dpend

pas

Pour illustrer, voyons comment


tableau

du nombre de lignes.

gcc

compile le code suivant (utilisant le

dni plus haut) :

x = a[ i ][ j ];
La Figure 5.6 montre le code assembleur correspondant. Donc, le compilateur
convertit grossirement le code en :

x = (&a[0][0] + 2 i + j );
et en fait, le programmeur pourrait l'crire de cette manire et obtenir le
mme rsultat.
Il n'y a rien de magique propos du choix de la reprsentation niveau
ligne du tableau. Une reprsentation niveau colonne fonctionnerait galement :
Indice
Elment

a[0][0]

a[1][0]

a[2][0]

a[0][1]

a[1][1]

a[2][1]

Dans la reprsentation niveau colonne, chaque colonne est stocke de manitre contige. L'lment

[i][j] est stock l'emplacement i+3j . D'autres

langages (FORTRAN, par exemple) utilisent la reprsentation niveau colonne. C'est important lorsque l'on interface du code provenant de multiples
langages.

5.1.

109

INTRODUCTION

Dimensions Suprieures Deux


Pour les dimensions suprieures deux, la mme ide de base est applique. Considrons un tableau trois dimensions :

int b [4][3][2];
Ce tableau serait stock comme s'il tait compos de trois tableaux deux
dimensions, chacun de taille

[3][2] stocks conscutivement en mmoire. Le

tableau ci-dessous montre comment il commence :


Indice
Elment

b[0][0][0]

b[0][0][1]

b[0][1][0]

b[0][1][1]

b[0][2][0]

b[0][2][1]

10

11

b[1][0][0]

b[1][0][1]

b[1][1][0]

b[1][1][1]

b[1][2][0]

b[1][2][1]

Indice
Elment

La formule pour calculer la position de


6 est dtermin par la taille des tableaux

b[i][j][k]
[3][2]. En

est

6i + 2j + k .

Le

gnral, pour un ta-

a[L][M][N] l'emplacement de l'lment a[i][j][k] sera


M N i + N j + k . Notez, l encore, que la dimension L n'apparat pas

bleau de dimension
dans la formule.

Pour les dimensions plus grandes, le mme procd est gnralis. Pour un
tableau

dimensions de dimension

repr par les indices

i1

in

D1

Dn ,

l'emplacement d'un lment

est donn par la formule :

D2 D3 Dn i1 + D3 D4 Dn i2 + + Dn in1 + in
ou pour les fanas de maths, on peut l'crire de faon plus concise :

n
X

n
Y

Dk ij

j=1
La premire dimension,

D1 ,

k=j+1

n'apparat pas dans la formule.

Pour la reprsentation au niveau colonne, la formule gnrale serait :

C'est
prenez

que

que

vous
l'auteur

comest

un major de physique (ou

i1 + D1 i2 + + D1 D2 Dn2 in1 + D1 D2 Dn1 in

j1
Y

j=1

Dk ij

k=1

Dans ce cas, c'est la dernire dimension,


formule.

vous avait dj mis sur la


voie ?)

ou dans la notation des fanas de maths :

n
X

la rfrence FORTRAN

Dn ,

qui n'apparat pas dans la

110

CHAPITRE 5.

TABLEAUX

Passer des Tableaux Multidimensionnels comme Paramtres en C


La reprsentation au niveau ligne des tableaux multidimensionnels a un
eet direct sur la programmation C. Pour les tableaux une dimension, la
taille du tableau n'est pas ncessaire pour calculer l'emplacement en mmoire de n'importe quel lment. Ce n'est pas vrai pour les tableaux multidimensionnels. Pour accder aux lments de ces tableaux, le compilateur
doit connatre toutes les dimensions sauf la premire. Cela saute aux yeux
lorsque l'on observe le prototype d'une fonction qui prend un tableau multidimensionnel comme paramtre. Ce qui suit ne compilera pas :

void f ( int a [ ][ ] );

/ pas d' information sur la dimension /

Cependant, ce qui suit compile :

void f ( int a [ ][2] );


Tout tableau deux dimensions deux colonnes peut tre pass cette

fonction. La premire dimension n'est pas ncessaire .


Ne confondez pas avec une fonction ayant ce prototype :

void f ( int a [ ] );
Cela dnit un tableau une dimension de pointeurs sur des entiers (qui peut
son tour tre utilis pour crer un tableau de tableaux qui se comportent
plus comme un tableau deux dimensions).
Pour les tableaux de dimensions suprieures, toutes les dimensions sauf
la premire doivent tre donnes pour les paramtres. Par exemple, un paramtre tableau quatre dimensions peut tre pass de la faon suivante :

void f ( int a [ ][4][3][2] );


5.2

Instructions de Tableaux/Chanes

La famille des processeurs 80x86 fournit plusieurs instructions conues


pour travailler avec les tableaux. Ces instructions sont appeles

de chanes.

instructions

Elles utilisent les registres d'index (ESI et EDI) pour eectuer

une opration puis incrmentent ou dcrmentent automatiquement l'un des


registres d'index ou les deux. Le

drapeau de direction

(DF) dans le registre

FLAGS dtermine si les registres d'index sont incrments ou dcrments.


Il y a deux instructions qui modient le drapeau de direction :

CLD

teint le drapeau de direction. Les registres d'index sont alors incrments.

STD

allume le drapeau de direction. Les registres d'index sont alors dcrments.

On peut indiquer une taille mais elle n'est pas prise en compte par le compilateur.

5.2.

INSTRUCTIONS DE TABLEAUX/CHANES

LODSB

AL = [DS:ESI]
ESI = ESI 1
AX = [DS:ESI]
ESI = ESI 2
EAX = [DS:ESI]
ESI = ESI 4

LODSW
LODSD

STOSB
STOSW
STOSD

111

[ES:EDI] = AL
EDI = EDI 1
[ES:EDI] = AX
EDI = EDI 2
[ES:EDI] = EAX
EDI = EDI 4

Fig. 5.7  Instructions de lecture et d'criture de chane

1
2

segment .data
array1 dd 1, 2, 3, 4, 5, 6, 7, 8, 9, 10

3
4
5

segment .bss
array2 resd 10

6
7
8
9
10
11
12
13
14
15

segment .text
cld
mov
esi, array1
mov
edi, array2
mov
ecx, 10
lp:
lodsd
stosd
loop lp

; ne pas oublier !

Fig. 5.8  Exemples de chargement et de stockage

Une erreur

trs

courante dans la programmation 80x86 est d'oublier de po-

sitionner le drapeau de direction. Cela conduit souvent un code qui fonctionne la plupart du temps (lorsque le drapeau de direction est dans la position dsire), mais ne fonctionne pas

tout

le temps.

5.2.1 Lire et crire en mmoire


Les instructions de chane les plus simples lisent ou crivent en mmoire,
ou les deux. Elles peuvent lire ou crire un octet, un mot ou un double-mot
la fois. La Figure 5.7 montre ces instructions avec une brve description
de ce qu'elles font en pseudo-code. Il y a plusieurs choses noter ici. Tout
d'abord, ESI est utilis pour lire et EDI pour crire. C'est facile retenir si
l'on se souvient que SI signie

Index

Source Index (Indice Source) et DI Destination

(Indice de Destination). Ensuite, notez que le registre qui contient la

112

CHAPITRE 5.

MOVSB
MOVSW
MOVSD

TABLEAUX

byte [ES:EDI] = byte [DS:ESI]


ESI = ESI 1
EDI = EDI 1
word [ES:EDI] = word [DS:ESI]
ESI = ESI 2
EDI = EDI 2
dword [ES:EDI] = dword [DS:ESI]
ESI = ESI 4
EDI = EDI 4

Fig. 5.9  Instructions de dplacement de chane en mmoire

1
2

segment .bss
array resd 10

3
4
5
6
7
8
9

segment .text
cld
mov
edi, array
mov
ecx, 10
xor
eax, eax
rep stosd

; ne pas oublier !

Fig. 5.10  Exemple de tableau mise zro d'un tableau

donne est xe (AL, AX ou EAX). Enn, notez que les instructions de stockage utilisent ES pour dterminer le segment dans lequel crire, pas DS. En
programmation en mode protg, ce n'est habituellement pas un problme,
puisqu'il n'y a qu'un segment de donnes et ES est automatiquement initialis pour y faire rfrence (tout comme DS). Cependant, en programmation
en mode rel, il est

trs

important pour le programmeur d'initialiser ES avec

la valeur de slecteur de segment correcte . La Figure 5.8 montre un exemple


de l'utilisation de ces instructions qui copie un tableau dans un autre.
La combinaison des instructions

LODSx et STOSx (comme aux lignes 13 et

14 de la Figure 5.8) est trs courante. En fait, cette combinaison peut tre
eectue par une seule instruction de chane

MOVSx.

La Figure 5.9 dcrit les

oprations eectues par cette instruction. Les lignes 13 et 14 de la Figure 5.8


pourraient tre remplaces par une seule instruction

MOVSD

avec le mme

Une autre complication est qu'on ne peut pas copier la valeur du registre DS dans ES
directement en utilisant une instruction MOV. A la place, la valeur de DS doit tre copie
dans un registre universel (comme AX) puis tre copi depuis ce registre dans ES, ce qui
utilise deux instruction MOV.

5.2.

113

INSTRUCTIONS DE TABLEAUX/CHANES

CMPSB

CMPSW

CMPSD

SCASB
SCASW
SCASD

compare l'octet en [DS:ESI] avec celui en


[ES:EDI]
ESI = ESI 1
EDI = EDI 1
compare le mot en [DS:ESI] avec celui en
[ES:EDI]
ESI = ESI 2
EDI = EDI 2
compare de double-mot en [DS:ESI] avec celui
en [ES:EDI]
ESI = ESI 4
EDI = EDI 4
compare AL et [ES:EDI]
EDI 1
compare AX et [ES:EDI]
EDI 2
compare EAX et [ES:EDI]
EDI 4

Fig. 5.11  Instructions de comparaison de chanes

rsultat. La seule dirence serait que le registre EAX ne serait pas utilis
du tout dans la boucle.

5.2.2 Le prxe d'instruction REP


La famille 80x86 fournit un prxe d'instruction spcial

4 appel

REP

qui

peut tre utilis avec les instructions de chane prsentes ci-dessus. Ce prxe indique au processeur de rpter l'instruction de chane qui suit un
nombre prcis de fois. Le registre ECX est utilis pour compter les itrations (exactement comme pour l'instruction

REP,

LOOP).

En utilisant le prxe

la boucle de la Figure 5.8 (lignes 12 15) pourrait tre remplace par

une seule ligne :

rep movsd
La Figure 5.10 montre un autre exemple qui met zro le contenu d'un
tableau.

Un prxe d'instruction n'est pas une instruction, c'est un octet spcial qui est plac
avant une instruction de chane qui modie son comportement. D'autres prxes sont
galement utiliss pour modier le segment par dfaut des accs mmoire

114

1
2

CHAPITRE 5.

TABLEAUX

segment .bss
array
resd 100

3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

segment .text
cld
mov
edi, array
; pointeur vers le dbut du tableau
mov
ecx, 100
; nombre d'lments
mov
eax, 12
; nombre rechercher
lp:
scasd
je
found
loop lp
; code excuter si non trouv
jmp
onward
found:
sub
edi, 4
; edi pointe maintenant vers 12
; code excuter si trouv
onward:

Fig. 5.12  Exemple de recherche

5.2.3 Instructions de comparaison de chanes


La Figure 5.11 montre plusieurs nouvelles instructions de chanes qui
peuvent tre utilises pour comparer des donnes en mmoire entre elles
ou avec des registres. Elles sont utiles pour comparer des tableaux ou eectuer des recherches. Elles positionnent le registre FLAGS exactement comme
l'instruction

CMP.

Les instructions

CMPSx

moire correspondants et les instructions

comparent les emplacements m-

SCASx

scannent des emplacements

mmoire pour une valeur particulire.


La Figure 5.12 montre un court extrait de code qui recherche le nombre
12 dans un tableau de double mots. L'instruction

SCASD

la ligne 10 ajoute

toujours 4 EDI, mme si la valeur recherche est trouve. Donc, si l'on veut
trouver l'adresse du 12 dans le tableau, il est ncessaire de soustraire 4 de
EDI (comme le fait la ligne 16).

5.2.4 Les prxes d'instruction REPx


Il y a plusieurs autres prxes d'instruction du mme genre que

REP

qui peuvent tre utiliss avec les instructions de comparaison de chanes.


La Figure 5.13 montre les deux nouveaux prxes et dcrit ce qu'ils font.

5.2.

115

INSTRUCTIONS DE TABLEAUX/CHANES

REPE, REPZ

rpte l'instruction tant que le drapeau Z est allum ou au


plus ECX fois

REPNE, REPNZ

rpte l'instruction tant que le drapeau Z est teint ou au plus


ECX fois
Fig. 5.13  Prxes d'instruction

1
2
3
4
5
6
7
8
9
10
11
12

REPx

segment .text
cld
mov
esi, block1
; adresse du premier bloc
mov
edi, block2
; adresse du second bloc
mov
ecx, size
; taille des blocs en octets
repe cmpsb
; rpter tant que Z est allum
je
equal
; Z est allum => blocs gaux
; code excuter si les blocs ne sont pas gaux
jmp
onward
equal:
; code excuter s'ils sont gaux
onward:

Fig. 5.14  Comparer des blocs mmoire

REPE

et

le sont

REPZ sont simplement des synonymes pour le mme


REPNE et REPNZ). Si l'instruction de comparaison de

prxe (comme
chanes rpte

stoppe cause du rsultat de la comparaison, le ou les registres d'index


sont quand mme incrments et ECX dcrment ; cependant, le registre
FLAGS contient toujours l'tat qu'il avait la n de la rptition. Donc, il Pourquoi ne peut pas pas
regarder
si
est possible d'utiliser le drapeau Z pour dterminer si la comparaison s'est simplement
ECX
est

zro
aprs
la
arrte cause de son rsultat ou si c'est parce que ECX a atteint zro.

La Figure 5.14 montre un extrait de code qui dtermine si deux blocs


de mmoire sont gaux. Le

JE

de la ligne 7 de l'exemple vrie le rsultat

de l'instruction prcdente. Si la comparaison s'est arrte parce qu'elle a


trouve deux octets dirents, le drapeau Z sera toujours teint et aucun
branchement n'est eectu ; cependant, si la comparaison s'est arrte parce
que ECX a atteint zro, le drapeau Z sera toujours allum et le code se
branche l'tiquette

equal.

5.2.5 Exemple
Cette section contient un chier source assembleur avec plusieurs fonctions qui implmentent des oprations sur les tableaux en utilisant des instructions de chane. Beaucoup de ces fonctions font doublons avec des fonc-

comparaison rpte ?

116

CHAPITRE 5.

TABLEAUX

tions familires de la bibliothque C.

memory.asm
global _asm_copy, _asm_find, _asm_strlen, _asm_strcpy

2
3
4
5
6
7
8
9
10
11

segment .text
; fonction _asm_copy
; copie deux blocs de mmoire
; prototype C
; void asm_copy( void * dest, const void * src, unsigned sz);
; paramtres:
; dest - pointeur sur le tampon vers lequel copier
; src - pointeur sur le tampon depuis lequel copier
; sz - nombre d'octets copier

12
13

; ci-dessous, quelques symboles utiles sont dfinis

14
15
16
17
18
19
20
21

%define dest [ebp+8]


%define src [ebp+12]
%define sz
[ebp+16]
_asm_copy:
enter 0, 0
push
esi
push
edi

22

mov
mov
mov

23
24
25

esi, src
edi, dest
ecx, sz

; esi = adresse du tampon depuis lequel copier


; edi = adresse du tampon vers lequel copier
; ecx = nombre d'octets copier

movsb

; teint le drapeau de direction


; excute movsb ECX fois

26

cld
rep

27
28
29

pop
pop
leave
ret

30
31
32
33

edi
esi

34
35
36
37
38
39
40

;
;
;
;
;

fonction _asm_find
recherche un octet donn en mmoire
void * asm_find( const void * src, char target, unsigned sz);
paramtres :
src
- pointeur sur le tampon dans lequel chercher

5.2.

41
42
43
44
45
46
47
48
49
50
51
52

INSTRUCTIONS DE TABLEAUX/CHANES

117

; target - octet rechercher


; sz
- nombre d'octets dans le tampon
; valeur de retour :
; si l'lment recherch est trouv, un pointeur vers sa premire occurence dans le tampon
; est retourn
; sinon, NULL est retourn
; NOTE : l'lment rechercher est un octet, mais il est empil comme une valeur dword.
;
La valeur de l'octet est stocke dans les 8 bits de poids faible.
;
%define src
[ebp+8]
%define target [ebp+12]
%define sz
[ebp+16]

53
54
55
56

_asm_find:
enter
push

0,0
edi

57
58
59
60
61

mov
mov
mov
cld

eax, target
edi, src
ecx, sz

; al a la valeur recherche

repne

scasb

; scanne jusqu' ce que ECX == 0 ou [ES:EDI] == AL

found_it
eax, 0
short quit

; si le drapeau zro est allum, on a trouv


; si pas trouv, retourner un pointeur NULL

62
63
64
65
66
67
68
69
70
71
72
73
74

je
mov
jmp
found_it:
mov
dec
quit:
pop
leave
ret

eax, edi
eax

; si trouv retourner (DI - 1)

edi

75
76
77
78
79
80
81
82

;
;
;
;
;
;

fonction _asm_strlen
retourne la taille d'une chane
unsigned asm_strlen( const char * );
paramtre :
src - pointeur sur la chane
valeur de retour :

118

83

CHAPITRE 5.

TABLEAUX

nombre de caractres dans la chane (sans compter le 0 terminal) (dans EAX)

84
85
86
87
88

%define src [ebp + 8]


_asm_strlen:
enter 0,0
push
edi

89
90
91
92
93

mov
mov
xor
cld

edi, src
; edi = pointeur sur la chane
ecx, 0FFFFFFFFh ; utilise la plus grande valeur possible de ECX
al,al
; al = 0

repnz

scasb

94
95

; recherche le 0 terminal

96
97
98
99
100
101
102

;
; repnz ira un cran trop loin, donc la longueur vaut FFFFFFFE - ECX,
; pas FFFFFFFF - ECX
;
mov
eax,0FFFFFFFEh
sub
eax, ecx
; longueur = 0FFFFFFFEh - ecx

103
104
105
106

pop
leave
ret

edi

107
108
109
110
111
112
113
114
115
116
117
118
119
120

; fonction _asm_strcpy
; copie une chane
; void asm_strcpy( char * dest, const char * src);
; paramtres :
; dest - pointeur sur la chaine vers laquelle copier
; src - pointeur sur la chane depuis laquelle copier
;
%define dest [ebp + 8]
%define src [ebp + 12]
_asm_strcpy:
enter 0,0
push
esi
push
edi

121
122
123
124

mov
mov
cld

edi, dest
esi, src

5.2.

125
126
127
128
129

INSTRUCTIONS DE TABLEAUX/CHANES

cpy_loop:
lodsb
stosb
or
jnz

al, al
cpy_loop

;
;
;
;

119

charge AL & incrmente SI


stocke AL & incrmente DI
positionne les drapeaux de condition
si l'on est pas aprs le 0 terminal, on continue

130

pop
pop
leave
ret

131
132
133
134

edi
esi
memory.asm

memex.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

#include <stdio.h>
#dene STR_SIZE 30
/ prototypes /
void asm_copy( void , const void , unsigned ) __attribute__((cdecl));
void asm_nd( const void ,
char target , unsigned ) __attribute__((cdecl));
unsigned asm_strlen( const char ) __attribute__((cdecl));
void asm_strcpy( char , const char ) __attribute__((cdecl));
int main()
{
char st1 [STR_SIZE] = "chane test";
char st2 [STR_SIZE];
char st ;
char ch;
asm_copy(st2, st1, STR_SIZE); / copie les 30 caractres de la chane /
printf ("%s\n", st2);
printf ("Entrez un caractre : " ); / recherche un octet dans la chane /
scanf("%c%[^\n]", &ch);
st = asm_nd(st2, ch, STR_SIZE);
if ( st )
printf ("Trouv : %s\n", st );
else
printf ("Pas trouv\n");

120

31
32
33
34

asm_strcpy( st2, st1 );


printf ("%s\n", st2 );

35
36
37
39

TABLEAUX

st1 [0] = 0;
printf ("Entrez une chane :");
scanf("%s", st1);
printf ("longueur = %u\n", asm_strlen(st1));

30

38

CHAPITRE 5.

/ copie des donnes dans la chane /

return 0;

memex.c

Chapitre 6

Virgule Flottante
6.1

Reprsentation en Virgule Flottante

6.1.1 Nombres binaires non entiers


Lorsque nous avons parl des systmes numriques dans le premier chapitre, nous n'avons abord que le nombres entiers. Evidemment, il est possible
de reprsenter des nombres non entiers dans des bases autres que le dcimal.
En dcimal, les chires droite de la virgule sont associs des puissances
ngatives de dix :

0, 123 = 1 101 + 2 102 + 3 103


Ce n'est pas tonnant, les nombres binaires fonctionnent de la mme
faon :

0, 1012 = 1 21 + 0 22 + 1 23 = 0, 625
Cette ide peut tre associe avec les mthodes appliques aux entiers dans
le Chapitre 1 pour convertir un nombre quelconque :

110, 0112 = 4 + 2 + 0, 25 + 0, 125 = 6, 375


Convertir du binaire vers le dcimal n'est pas trs dicile non plus. Divisez le nombre en deux parties : entire et dcimale. Convertissez la partie
entire en binaire en utilisant les mthodes du Chapitre 1, la partie dcimale
est convertie en utilisant la mthode dcrite ci-dessous.
Considrons une partie dcimale binaire dont les bits sont nots

a, b, c, . . .

Le nombre en binaire ressemble alors :

0, abcdef . . .
Multipliez le nombre par deux. La reprsentation du nouveau nombre sera :

a, bcdef . . .
121

122

CHAPITRE 6.

0, 5625 2 = 1, 125

VIRGULE FLOTTANTE

premier bit

= 1

0, 125 2 = 0, 25

deuxime bit

= 0

0, 25 2 = 0, 5

troisime bit

= 0

0, 5 2 = 1, 0

quatrime bit

= 1

Fig. 6.1  Convertir 0,5625 en binaire

0, 85 2 = 1, 7
0, 7 2 = 1, 4
0, 4 2 = 0, 8
0, 8 2 = 1, 6
0, 6 2 = 1, 2
0, 2 2 = 0, 4
0, 4 2 = 0, 8
0, 8 2 = 1, 6

Fig. 6.2  Convertir 0,85 en binaire

Notez que le premier bit est maintenant en tte. Remplacez le

par

pour

obtenir :

0, bcdef . . .
et multipliez nouveau par deux, vous obtenez :

b, cdef . . .
Maintenant le deuxime bit (b) est la premire place. Cette procdure peut
tre rpte jusqu' ce qu'autant de bits que ncessaires soient trouvs. La
Figure 6.1 montre un exemple rel qui converti 0,5625 en binaire. La mthode
s'arrte lorsque la partie dcimale arrive zro.
Prenons un autre exemple, considrons la conversion de 23,85 en binaire,
il est facile de convertir la partie entire (23

= 101112 ),

mais qu'en est-il de

la partie dcimale (0, 85) ? La Figure 6.2 montre le dbut de ce calcul. Si

6.1.

REPRSENTATION EN VIRGULE FLOTTANTE

123

l'on regarde les nombres attentivement, on trouve une boucle innie ! Cela
signie que 0,85 est un binaire rptitif (par analogie un dcimal rptitif

en base 10) . Il y a un motif dans les nombres du calcul. En regardant le


motif, on peut voir que

0, 85 = 0, 1101102 .

Donc,

23, 85 = 10111, 1101102 .

Une consquence importante du calcul ci-dessus est que 23,85 ne peut


pas tre reprsent

exactement

en binaire en utilisant un nombre ni de bits

1
(Tout comme
3 ne peut pas tre reprsent en dcimal avec un nombre ni
de chires). Comme le montre ce chapitre, les variables

float et double en C

sont stockes en binaire. Donc, les valeurs comme 23,85 ne peuvent pas tre
stockes exactement dans ce genre de variables. Seule une approximation de
23,85 peut tre stocke.
Pour simplier le matriel, les nombres en virgule ottante sont stocks
dans un format standard. Ce format utilise la notation scientique (mais en
binaire, en utilisant des puissances de deux, pas de dix). Par exemple, 23,85
ou

10111, 11011001100110 . . .2

serait stock sous la forme :

1, 011111011001100110 . . . 2100
(o l'exposant (100) est en binaire). Un nombre en virgule ottante

normalis

a la forme :

1, ssssssssssssssss 2eeeeeee
o

1, sssssssssssss

est la

mantisse

et

eeeeeeee

exposant.

est l'

6.1.2 Reprsentation en virgule ottante IEEE


L'IEEE (Institute of Electrical and Electronic Engineers) est une organisation internationale qui a conu des formats binaires spciques pour stocker
les nombres en virgule ottante. Ce format est utilis sur la plupart des ordinateurs (mais pas tous !) fabriqu de nos jours. Souvent, il est support par
le matriel de l'ordinateur lui-mme. Par exemple, les coprocesseurs numriques (ou arithmtiques) d'Intel (qui sont intgrs tous leurs processeurs
depuis le Pentium) l'utilisent. L'IEEE dnit deux formats dirents avec
des prcisions direntes : simple et double prcision. La simple prcision est
utilise pour les variables

double.

float en C et la double prcision pour les variables

Le coprocesseur arithmtique d'Intel utilise galement une troisime prcision plus leve appele

prcision tendue. En fait toutes les donnes dans

le coprocesseur sont dans ce format. Lorsqu'elles sont stockes en mmoire


depuis le coprocesseur, elles sont converties soit en simple, soit en double

Cela n'est pas surprenant qu'un nombre puisse tre rptitif dans une base, mais pas
dans une autre. Pensez 13 , il se rpte en dcimal, mais en ternaire (base 3) il vaudrait
0, 13 .

124

CHAPITRE 6.

31
s

30

23

VIRGULE FLOTTANTE

22

bit de signe - 0 = positif, 1 = negatif

Exposant dcal (8-bits) = exposant rel + 7F (127 en dcimal). Les


valeurs 00 et FF ont des signications spciales (voir texte).

partie dcimale - les 23 premiers bits aprs le 1, dans la mantisse.


Fig. 6.3  Simple prcision IEEE

prcision automatiquement . La prcision tendue utilise un format gnral


lgrement dirent des formats oat et double de l'IEEE, nous n'en parlerons donc pas ici.

Simple prcision IEEE


La virgule ottante simple prcision utilise 32 bits pour encoder le nombre.
Elle est gnralement prcise jusqu' 7 chires signicatifs. Les nombres en
virgule ottante sont gnralement stocks dans un format beaucoup plus
compliqu que celui des entiers. La Figure 6.3 montre le format de base d'un
nombre en simple prcision IEEE. Ce format a plusieurs inconvnients. Les
nombres en virgule ottante n'utilisent pas la reprsentation en complment
deux pour les nombres ngatifs. Ils utilisent une reprsentation en grandeur
signe. Le bit 31 dtermine le signe du nombre.
L'exposant binaire n'est pas stock directement. A la place, la somme de
l'exposant et de 7F est stocke dans les bits 23 30, Cet

exposant dcal

est

toujours positif ou nul.


La partie dcimale suppose une mantisse normalise (de la forme
Comme le premier bit est toujours un 1, le 1 de gauche n'est

1, sssssssss).

pas stock !

Cela

permet le stockage d'un bit additionnel la n et augmente donc lgrement


la prcision. Cette ide est appele
Il

faut

toujours

l'esprit

41

BE

que

CC

les

CD

octets de signe est 0, Ensuite, l'exposant rel est 4, donc l'exposant dcal est

peuvent

tre interprts de faons


direntes selon ce qu'en
fait

le

comme

programme !
un

Vus

nombre

en

7F + 4 = 8316 .

les direntes sections du format en virgule ottante, le bit de signe et la


fraction on t souligns et les bits ont t regroups en groupes de 4 bits) :

0 100 0001 1 011 1110 1100 1100 1100 11002 = 41BECCCC16

prcision, ils reprsentent


comme

un

mot,

ils

mais

entier

vus

double

reprsentent

1,103,023,309 ! Le processeur ne sait pas quelle est


la bonne interprtation !

Enn, la fraction vaut 01111101100110011001100 (souvenez

vous que le un de tte est masqu). En les mettant bout bout (pour clarier

virgule ottante en simple


23,850000381,

reprsentation en un masqu .

Comment serait stock 23,85 ? Tout d'abord, il est positif, donc le bit

garder

Le type long double de certains compilateurs (comme celui de Borland) utilise cette
prcision tendue. Par contre, d'autres compilateurs utilisent la double prcision la fois
pour les double et long double (C'est autoris par le C ANSI).

6.1.

125

REPRSENTATION EN VIRGULE FLOTTANTE

e = 0 et f = 0

indique le nombre zro (qui ne peut pas tre


normalis). Notez qu'il y a +0 et -0.

nombre dnormalis. Nous en parle-

e = 0 et f 6= 0

indique un

e = FF et f = 0

indique l'inni (). Il y a un inni positif et un

rons dans la section suivante.


inni ngatif.

e = FF et f 6= 0

indique un rsultat indni, appel

NaN

(Not

a Number, pas un nombre).


Tab. 6.1  Valeurs spciales de

et

Cela ne fait pas exactement 23,85 (puisqu'il s'agit d'un binaire rptitif ). Si
l'on convertit le chire ci-dessus en dcimal, on constate qu'il vaut approximativement 23,849998474, ce nombre est trs proche de 23,85 mais n'est pas
exactement le mme. En ralit, en C, 23,85 ne serait pas reprsent exactement comme ci-dessus. Comme le bit le plus gauche qui a t supprim de
la reprsentation exacte valait 1, le dernier bit est arrondi 1, donc 23,85 serait reprsent par 41 BE CC CD en hexa en utilisant la simple prcision. En
dcimal, ce chire vaut 23,850000381 ce qui est une meilleure approximation
de 23,85.
Comment serait reprsent -23,85 ? Il sut de changer le bit de signe :
C1 BE CC CD. Ne prenez

pas

Certaines combinaisons de

le complment deux !

et

ont une signication spciale pour les

ottants IEEE. Le Tableau 6.1 dcrit ces valeurs. Un inni est produit par
un dpassement de capacit ou une division par zro. Un rsultat indni
est produit par une opration invalide comme rechercher la racine care d'un
nombre ngatif, additionner deux innis,

etc.

Les nombres en simple prcision normaliss peuvent prendre des valeurs


de

1, 0 2126 ( 1, 1755 1035 )

1, 11111 . . . 2127 ( 3, 4028 1035 ).

Nombres dnormaliss
Les nombres dnormaliss peuvent tre utiliss pour reprsenter des nombres
avec des grandeurs trop petites normaliser (
Par exemple, considrons le nombre

i.e. infrieures 1, 0 2126 ).

1, 0012 2129 ( 1, 6530 1039 ).

Dans la forme normalise, l'exposant est trop petit. Par contre, il peut tre
reprsent sous une forme non normalise :

0, 010012 2127 .

Pour stocker

ce nombre, l'exposant dcal est positionn 0 (voir Tableau 6.1) et la partie dcimale est la mantisse complte du nombre crite comme un produit
avec

2127

i.e. tous les bits sont stocks, y compris le un gauche du point

dcimal). La reprsentation de

1, 001 2129

est donc :

0 000 0000 0 001 0010 0000 0000 0000 0000

126

CHAPITRE 6.

63

62

52

VIRGULE FLOTTANTE

51

f
Fig. 6.4  Double prcision IEEE

Double prcision IEEE


La double prcision IEEE utilise 64 bits pour reprsenter les nombres
et est prcise jusqu' environ 15 chires signicatifs. Comme le montre la
Figure 6.4, le format de base est trs similaire celui de la simple prcision.
Plus de bits sont utiliss pour l'exposant dcal (11) et la partie dcimale
(52).
La plage de valeurs plus grande pour l'exposant dcal a deux consquences. La premire est qu'il est calcul en faisant la somme de l'exposant
rel et de 3FF (1023) (pas 7F comme pour la simple prcision). Deuximement, une grande plage d'exposants rels (et donc une plus grande plage de
grandeurs) est disponible. Les grandeurs en double prcision peuvent prendre
des valeurs entre environ

10308

et

10308 .

C'est la plus grande taille du champ rserv la partie dcimale qui


est reponsable de l'augmentation du nombre de chires signicatifs pour les
valeurs doubles.
Pour illustrer cela, reprenons 23,85. L'exposant dcal sera

4+3FF = 403

en hexa. Donc, la reprsentation double sera :

0 100 0000 0011 0111 1101 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1010
ou 40 37 D9 99 99 99 99 9A en hexa. Si l'on reconvertit ce nombre en
dcimal, on trouve 23,8500000000000014 (il y a 12 zros !) ce qui est une
approximation de 23,85 nettement meilleure.

La double prcision a les mmes valeurs spciales que la simple prcision .


Les nombres dnormaliss sont galement trs similaires. La seule dirence
principale est que les nombres doubles dnormaliss utilisent

21023

au lieu

127 .
de 2

6.2

Arithmtique en Virgule Flottante

L'arithmtique en virgule ottante sur un ordinateur est dirente de


celle des mathmatiques. En mathmatiques, tous les nombres peuvent tre

3
La seule dirence est que pour l'inni et les valeurs indnies, l'exposant dcal vaut
7FF et non pas FF.

6.2.

ARITHMTIQUE EN VIRGULE FLOTTANTE

127

considrs comme exacts. Comme nous l'avons montr dans la section prcdente, sur un ordinateur beaucoup de nombres ne peuvent pas tre reprsents exactement avec un nombre ni de bits. Tous les calculs sont eectus
avec une prcision limite. Dans les exemples de cette section, des nombres
avec une mantisse de 8 bits seront utiliss pour plus de simplicit.

6.2.1 Addition
Pour additionner deux nombres en virgule ottante, les exposants doivent
tre gaux. S'ils ne le sont pas dj, alors, il faut les rendre gaux en dcalant
la mantisse du nombre ayant le plus petit exposant. Par exemple, considrons

10, 375 + 6, 34375 = 16, 71875

ou en binaire :

1, 0100110 23
+ 1, 1001011 22
Ces deux nombres n'ont pas le mme exposant, donc on dcale la mantisse
pour rendre les exposants gaux, puis on eectue l'addition :

1.0100110 23
+ 0.1100110 23
10.0001100 23
1, 100101122 supprime le un de tte et arrondi plus
3
3
tard le rsultat en 0, 1100110 2 . Le rsultat de l'addition, 10, 0001100 2
4
(ou 1, 00001100 2 ), est gal 10000, 1102 ou 16,75, ce n'est pas gal

Notez que le dcalage de

la rponse exacte (16,71875) ! Il ne s'agit que d'une approximation due aux


erreurs d'arrondi du processus d'addition.
Il est important de raliser que l'arithmtique en virgule ottante sur
un ordinateur (ou une calculatrice) est toujours une approximation. Les lois
des mathmatiques ne fonctionnent pas toujours avec les nombres en virgule
ottante sur un ordinateur. Les mathmatiques supposent une prcision innie qu'aucun ordinateur ne peut atteindre. Par exemple, les mathmatiques
nous apprennent que

(a + b) b = a;

cependant, ce n'est pas forcment

exactement vrai sur un ordinateur !

6.2.2 Soustraction
La soustraction fonctionne d'une faon similaire l'addition et soure
des mmes problmes. Par exemple, considrons

1, 0000110 24
1, 1111111 23

16, 75 15, 9375 = 0, 8125:

128

CHAPITRE 6.

Dcaler

1, 1111111 23

donne (en arrondissant)

VIRGULE FLOTTANTE

1, 0000000 24

1, 0000110 24
1, 0000000 24
0, 0000110 24
0, 0000110 24 = 0, 112 = 0, 75

ce qui n'est pas exactement correct.

6.2.3 Multiplication et division


Pour la multiplication, les mantisses sont multiples et les exposants sont
additionns. Considrons

10, 375 2, 5 = 25, 9375:

1, 0100110 23
1, 0100000 21
10100110
+ 10100110
1, 10011111000000 24
Bien sr, le rsultat rel serait arrondi 8 bits pour donner :

1, 1010000 24 = 11010, 0002 = 26


La division est plus complique, mais soure des mmes problmes avec
des erreurs d'arrondi.

6.2.4 Consquences sur la programmation


Le point essentiel de cette section est que les calculs en virgule ottante ne
sont pas exacts. Le programmeur doit en tre conscient. Une erreur courante
que les programmeurs font avec les nombres en virgule ottante est de les
comparer en supposant qu'un calcul est exact. Par exemple, considrons
une fonction appele

f(x)

qui eectue un calcul complexe et un programme

qui essaie de trouver la racine de la fonction . On peut tre tent d'utiliser


l'expression suivante pour vrier si

x est une racine :

if ( f(x) == 0.0 )
Mais, que se passe-t-il si
ment que

x est une trs

f(x) retourne 1 1030 ? Cela signie trs probable-

bonne approximation d'une racine relle ; cependant,

l'galit ne sera pas vrie. Il n'y a peut tre aucune valeur en virgule ottante IEEE de
dans

f(x).

x qui renvoie exactement zro, en raison des erreurs d'arrondi

Une meilleur mthode serait d'utiliser :

La racine d'une fonction est une valeur x telle que f (x) = 0

6.3.

129

LE COPROCESSEUR ARITHMTIQUE

if ( fabs(f(x)) < EPS )


o

EPS

est une macro dnissant une trs petite valeur positive (du genre

1 1010 ).

Cette expression est vraie ds que

f(x)

est trs proche de zro.

En gnral, pour comparer une valeur en virgule ottante (disons

x)

une

autre ( ), on utilise :

if ( fabs(x y)/fabs(y) < EPS )

6.3

Le Coprocesseur Arithmtique

6.3.1 Matriel
Les premiers processeurs Intel n'avaient pas de support matriel pour
les oprations en virgule ottante. Cela ne signie pas qu'ils ne pouvaient
pas eectuer de telles oprations. Cela signie seulement qu'elles devaient
tre ralises par des procdures composes de beaucoup d'instructions qui
n'taient pas en virgule ottante. Pour ces systmes, Intel fournissait une
puce appele

coprocesseur mathmatique.

Un coprocesseur mathmatique a

des instructions machine qui eectuent beaucoup d'oprations en virgule ottante beaucoup plus rapidement qu'en utilisant une procdure logicielle (sur
les premiers processeurs, au moins 10 fois plus vite !). Le coprocesseur pour
le 8086/8088 s'appelait le 8087, pour le 80286, il y avait un 80287 et pour
le 80386, un 80387 ; le processeur 80486DX intgrait le coprocesseur math-

matique dans le 80486 lui-mme . Depuis le Pentium, toutes les gnrations


de processeurs 80x86 on un coprocesseur mathmatique intgr ; cependant,
on le programme toujours comme s'il s'agissait d'une unit spare. Mme
les systmes plus anciens sans coprocesseur peuvent installer un logiciel qui
mule un coprocesseur mathmatique. Cet mulateur est automatiquement
activ lorsqu'un programme excute une instruction du coprocesseur et lance
la procdure logicielle qui produit le mme rsultat que celui qu'aurait donn
le coprocesseur (bien que cela soit plus lent, bien sr).
Le coprocesseur arithmtique a huit registres de virgule ottante. Chaque
registre contient 80 bits de donnes. Les nombres en virgule ottante sont

toujours stocks sous forme de nombres 80 bits en prcision tendue dans ces
registres. Les registres sont appels

ST0, ST1, ST2, . . . ST7.

Les registres en

virgule ottante sont utiliss diremment des registres entiers du processeur


principal. Ils sont organiss comme une
une liste

Last-In First-Out

pile.

Souvenez vous qu'une pile est

(LIFO, dernier entr, premier sorti).

ST0 fait tou-

jours rfrence la valeur au sommet de la pile. Tous les nouveaux nombres

5
Cependant, le 80486SX n'avait
80487SX pour ces machines.

pas

de coprocesseur intgr. Il y avait une puce

130

CHAPITRE 6.

VIRGULE FLOTTANTE

sont ajouts au sommet de la pile. Les nombres existants sont dcals vers
le bas pour faire de la place au nouveau.
Il y a galement un registre de statut dans le coprocesseur arithmtique.
Il a plusieurs drapeaux. Seuls les 4 drapeaux utiliss pour les comparaisons
seront traits : C0 , C1 , C2 et C3 . Leur utilisation est traite plus tard.

6.3.2 Instructions
Pour faciliter la distinction entre les instructions du processeur normal et
celles du coprocesseur, tous les mnmoniques du coprocesseur commencent
par un

F.

Chargement et stockage
Il y a plusieurs instructions qui chargent des donnes au sommet de la
pile du coprocesseur :

FLD source

Charge un nombre en virgule ottante depuis la mmoire vers


le sommet de la pile. La

source peut tre un nombre en simple,

double ou prcision tendue ou un registre du coprocesseur.

FILD source

Lit un

entier

depuis la mmoire, le convertit en ottant et

stocke le rsultat au sommet de la pile. La

source

peut tre

un mot, un double mot ou un quadruple mot.

FLD1
FLDZ

Stocke un un au sommet de la pile.


Stocke un zro au sommet de la pile.

Il y a galement plusieurs instructions qui dplacent les donnes depuis


la pile vers la mmoire. Certaines de ces instruction retirent le nombre de la
pile en mme temps qu'elles le dplacent.

6.3.

131

LE COPROCESSEUR ARITHMTIQUE

FST dest

Stocke le sommet de la pile (ST0) en mmoire. La

destination

peut tre un nombre en simple ou double prcision ou un


registre du coprocesseur.

FSTP dest

Stocke le sommet de la pile en mmoire, exactement comme

FST ; cependant, une fois le nombre stock, sa valeur est retire


de la pile. La

destination

peut tre un nombre en simple,

double ou prcision tendue ou un registre du coprocesseur.

FIST dest

Stocke la valeur au sommet de la pile convertie en entier


en mmoire. La

destination

peut tre un mot ou un double

mot. La pile en elle-mme reste inchange. La faon dont est


converti le nombre en entier dpend de certains bits dans le

mot de contrle

du coprocesseur. Il s'agit d'un registre d'un

mot spcial (pas en virgule ottante) qui contrle le fonctionnement du coprocesseur. Par dfaut, le mot de contrle est
initialis an qu'il arrondisse l'entier le plus proche lorsqu'il
convertit vers un entier. Cependant, les instructions
(Store Control Word, stocker le mot de contrle) et

FSTCW
FLDCW

(Load Control Word, charger le mot de contrle) peuvent tre


utilises pour changer ce comportement.

FISTP dest

Identique

FIST sauf en deux points. Le sommet de la pile est

supprim et la

destination

peut galement tre un quadruple

mot.
Il y a deux autres instructions qui peuvent placer ou retirer des donnes
de la pile.

FXCH STn

change les valeurs de

ST0

et

STn

sur la pile (o

est un

numro de registre entre 1 et 7).

FFREE STn

libre un registre sur la pile en le marquant comme inutilis


ou vide.

Addition et soustraction

Chacune des instructions d'addition calcule la somme de

ST0

et d'un

autre oprande. Le rsultat est toujours stock dans un registre du coprocesseur.

132

1
2
3

CHAPITRE 6.

VIRGULE FLOTTANTE

segment .bss
array
resq SIZE
sum
resq 1

4
5
6
7
8
9
10
11
12
13

segment .text
mov
ecx, SIZE
mov
esi, array
fldz
lp:
fadd qword [esi]
add
esi, 8
loop lp
fstp qword sum

; ST0 = 0
; ST0 += *(esi)
; passe au double suivant
; stocke le rsultat dans sum

Fig. 6.5  Exemple de somme d'un tableau

FADD source

ST0 += source .

La

source

peut tre n'importe quel

registre du coprocesseur ou un nombre en simple ou


double prcision en mmoire.

dest += ST0. La destination peut tre n'importe quel

FADD dest, ST0

registre du coprocesseur.

FADDP dest ou
FADDP dest, STO

dest += ST0

nation

puis ST0 est retir de la pile. La

desti-

peut tre n'importe quel registre du coproces-

seur.

FIADD source

ST0 += (float) source .

Ajoute un entier

ST0.

source doit tre un mot ou un double mot en mmoire.

Il y a deux fois plus d'instructions pour la soustraction que pour l'addition

i.e. a+b = b+a,


a b 6= b a!). Pour chaque instruction, il y a un mirroir qui eectue la

car l'ordre des oprandes est important pour la soustraction (


mais

soustraction dans l'ordre inverse. Ces instructions inverses se nissent toutes


soit par

soit par

La

RP.

La Figure 6.5 montre un court extrait de code qui

ajoute les lments d'un tableau de doubles. Au niveau des lignes 10 et 13, il
faut spcier la taille de l'oprande mmoire. Sinon l'assembleur ne saurait
pas si l'oprande mmoire est un oat (dword) ou un double (qword).

6.3.

133

LE COPROCESSEUR ARITHMTIQUE

FSUB source

ST0 -= source .

La

source

peut tre n'importe quel

registre du coprocesseur ou un nombre en simple ou


double prcision en mmoire.

FSUBR source

ST0 = source - ST0.

La

source

peut tre n'importe

quel registre du coprocesseur ou un nombre en simple


ou double prcision en mmoire.

dest -= ST0. La destination peut tre n'importe quel

FSUB dest, ST0

registre du coprocesseur.

FSUBR dest, ST0

dest = ST0 - dest .

La

destination

peut tre n'im-

porte quel registre du coprocesseur.

dest -= ST0 puis retire ST0 de la pile. La destination

FSUBP dest ou
FSUBP dest, STO
FSUBRP dest ou
FSUBRP dest, ST0

peut tre n'importe quel registre du coprocesseur.

dest = ST0 - dest

destination

puis retire ST0 de la pile. La

peut tre n'importe quel registre du co-

processeur.

FISUB source

ST0 -= (float) source . Soustrait un entier de ST0.


La

source

doit tre un mot ou un double mot en m-

moire.

FISUBR source

ST0 = (float) source - ST0.


entier. La

source

Soustrait

ST0

d'un

doit tre un mot ou un double mot

en mmoire.

Multiplication et division
Les instructions de multiplication sont totalement analogues celles d'addition.

FMUL source

ST0 *= source .

La

source

peut tre n'importe quel

registre du coprocesseur ou un nombre en simple ou


double prcision en mmoire.

FMUL dest, ST0

dest *= ST0. La destination peut tre n'importe quel


registre du coprocesseur.

FMULP dest ou
FMULP dest, ST0
FIMUL source

dest *= ST0 puis retire ST0 de la pile. La destination


peut tre n'importe quel registre du coprocesseur.

ST0 *= (float) source . Multiplie


ST0. La source peut tre un mot ou

un entier avec
un double mot

en mmoire.
Ce n'est pas tonnant, les instructions de division sont analogues celles
de soustraction. La division par zro donne l'inni.

134

CHAPITRE 6.

FDIV source

ST0 /= source .

La

VIRGULE FLOTTANTE

source

peut tre n'importe quel

registre du coprocesseur ou un nombre en simple ou


double prcision en mmoire.

FDIVR source

ST0 = source / ST0.

source

La

peut tre n'importe

quel registre du coprocesseur ou un nombre en simple


ou double prcision en mmoire.

dest /= ST0. La destination peut tre n'importe quel

FDIV dest, ST0

registre du coprocesseur.

FDIVR dest, ST0

dest = ST0 / dest .

La

destination

peut tre n'im-

porte quel registre du coprocesseur.

dest /= ST0 puis retire ST0 de la pile. La destination

FDIVP dest ou
FDIVP dest, ST0
FDIVRP dest ou
FDIVRP dest, ST0

peut tre n'importe quel registre du coprocesseur.

dest = ST0 / dest

destination

puis retire ST0 de la pile. La

peut tre n'importe quel registre du co-

processeur.

FIDIV source

ST0 /= (float) source .


La

FIDIVR source

Divise

ST0

par un entier.

src doit tre un mot ou un double mot en mmoire.

ST0 = (float) source / ST0. Divise un entier par


ST0. La source doit tre un mot ou un double mot en
mmoire.

Comparaisons
Le coprocesseur eectue galement des comparaisons de nombres en vir-

FCOM est faite pour a.


source . La source peut tre

gule ottante. La famille d'instructions

FCOM source

compare

ST0

et

un registre du

coprocesseur ou un oat ou un double en mmoire.

FCOMP source

compare

ST0

et

source ,

puis retire ST0 de la pile. La

source

peut tre un registre du coprocesseur ou un oat ou un double


en mmoire.

FCOMPP
FICOM source

compare
compare

ST0 et ST1, puis retire ST0 et ST1 de la pile.


ST0 et (float) source . La source peut tre un en-

tier sur un mot ou un double mot en mmoire.

FICOMP source

compare
La

ST0

source

et

(float)source ,

puis retire ST0 de la pile.

peut tre un entier sur un mot ou un double mot

en mmoire.

FTST

compare

ST0

et 0,

Ces instructions changent les bits C0 , C1 , C2 et C3 du registre de statut


du coprocesseur. Malheureusement, il n'est pas possible pour le processeur
d'accder ces bits directement. Les instructions de branchement conditionnel utilisent le registre FLAGS, pas le registre de statut du coprocesseur.
Cependant, il est relativement simple de transfrer les bits du mot de statut
dans les bits correspondants du registre FLAGS en utilisant quelques ins-

6.3.

1
2
3
4
5
6
7
8
9
10
11
12
13

;
;

135

LE COPROCESSEUR ARITHMTIQUE

if ( x > y )

fld
fcomp
fstsw
sahf
jna
then_part:
; code
jmp
else_part:
; code
end_if:

qword [x]
qword [y]
ax

; ST0 = x
; compare STO et y
; place les bits C dans FLAGS

else_part

; si x non < y goto else_part

si vrai
end_if
si faux

Fig. 6.6  Exemple de comparaison

tructions nouvelles :

FSTSW destination

Stocke le mot de statut du coprocesseur soit dans un mot en


mmoire soit dans le registre AX.

SAHF
LAHF

Stocke le registre AH dans le registre FLAGS.


Charge le registre AH avec les bits du registre FLAGS.

La Figure 6.6 montre un court extrait de code en exemple. Les lignes 5


et 6 transfrent les bits C0 , C1 , C2 et C3 du mot de statut du coprocesseur
dans le registre FLAGS. Les bits sont transfrs de faon a tre identiques
au rsultat d'une comparaison de deux entiers
ligne 7 utilise une instruction

JNA.

non signs. C'est pourquoi la

Les Pentium Pro (et les processeurs plus rcents (Pentium II and III))
supportent deux nouveaux oprateurs de comparaison qui modient directement le registre FLAGS du processeur.

FCOMI source

compare

ST0

et

source .

La

source

doit tre un registre du

coprocesseur.

FCOMIP source

compare

ST0

et

source ,

puis retire ST0 de la pile. La

doit tre un registre du coprocesseur.


La Figure 6.7 montre une sous-routine qui trouve le plus grand de deux
doubles en utilisant l'instruction

FCOMIP.

Ne confondez pas ces instructions

avec les fonctions de comparaisons avec un entier (FICOM et

FICOMP).

Instructions diverses
Cette section traite de diverses autres instructions fournies par le coprocesseur.

source

136

CHAPITRE 6.

FCHS
FABS
FSQRT
FSCALE

VIRGULE FLOTTANTE

ST0 = - ST0 Change le signe de ST0


ST0 =
|ST0| Extrait la valeur absolue de ST0
ST0 = STO Extrait la racine carre de ST0
ST0 = ST0 2bST1c multiplie ST0 par une puissance de 2 rapidement. ST1 n'est pas retir de la pile du coprocesseur. La
Figure 6.8 montre un exemple d'utilisation de cette instruction.

6.3.3 Exemples
6.3.4 Formule quadratique
Le premier exemple montre comment la formule quadratique peut tre
code en assembleur. Souvenez vous, la formule quadratique calcule les solutions de l'quation :

ax2 + bx + c = 0
x: x1 et x2 .

b b2 4ac
x1 , x2 =
2a

La formule donne deux solutions pour

L'expression sous la racine carre (b

4ac)

est appele le

dterminant. Sa

valeur est utile pour dterminer laquelle des trois possibilits suivantes est
vrie pour les solutions.
1. Il n'y a qu'une seule solution double.
2. Il y a deux solutions relles.

b2 4ac = 0

b2 4ac > 0

3. Il y a deux solutions complexes.

b2 4ac < 0

Voici un court programme C qui utilise la sous-routine assembleur :

quadt.c
1
2
3
4
5
6
7
8
9
10
11
12

#include <stdio.h>
int quadratic ( double, double, double, double , double );
int main()
{
double a,b,c , root1 , root2;
printf ("Entrez a , b, c : ");
scanf("%lf %lf %lf", &a, &b, &c);
if ( quadratic ( a , b, c, &root1, &root2 ) )
printf (" racines : %.10g %.10g\n", root1, root2 );

6.3.

13
14
15
16

LE COPROCESSEUR ARITHMTIQUE

137

else
printf ("pas de racine relle \n");
return 0;
}

quadt.c
Voici la routine assembleur :

1
2
3
4
5
6
7
8
9
10
11
12

;
;
;
;
;
;
;
;
;
;
;
;

quad.asm
fonction quadratic
trouve les solutions l'quation quadratique :
a*x**2 + b*x + c = 0
prototype C :
int quadratic( double a, double b, double c,
double * root1, double *root2 )
Paramtres:
a, b, c - coefficients des puissances de l'quation quadratique (voir ci-dessus)
root1 - pointeur vers un double o stocker la premire racine
root2 - pointeur vers un double o stocker la deuxime racine
Valeur de retour :
retourne 1 si des racines relles sont trouves, sinon 0

13
14
15
16
17
18
19
20

%define
%define
%define
%define
%define
%define
%define

a
b
c
root1
root2
disc
one_over_2a

qword
qword
qword
dword
dword
qword
qword

[ebp+8]
[ebp+16]
[ebp+24]
[ebp+32]
[ebp+36]
[ebp-8]
[ebp-16]

21
22
23

segment .data
MinusFour

dw

-4

24
25
26
27
28
29
30
31

segment .text
global _quadratic
_quadratic:
push
ebp
mov
ebp, esp
sub
esp, 16
push
ebx

; alloue 2 doubles (disc & one_over_2a)


; on doit sauvegarder l'ebx original

32
33

fild

word [MinusFour]; pile : -4

138

CHAPITRE 6.

fld
fld
fmulp
fmulp
fld
fld
fmulp
faddp
ftst
fstsw
sahf
jb
fsqrt
fstp
fld1
fld
fscale
fdivp
fst
fld
fld
fsubrp
fmulp
mov
fstp
fld
fld
fchs
fsubrp
fmul
mov
fstp
mov
jmp

34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67

a
c
st1
st1
b
b
st1
st1
ax

;
;
;
;

pile
pile
pile
pile

;
;
;
;

pile : b, b, -4*a*c
pile : b*b, -4*a*c
pile : b*b - 4*a*c
compare avec 0

no_real_solutions
;
disc
;
;
a
;
;
st1
;
one_over_2a
;
b
;
disc
;
st1
;
st1
;
ebx, root1
qword [ebx]
;
b
;
disc
;
;
st1
;
one_over_2a
;
ebx, root2
qword [ebx]
;
eax, 1
;
short quit

:
:
:
:

VIRGULE FLOTTANTE

a, -4
c, a, -4
a*c, -4
-4*a*c

; si <
pile :
stocke
pile :
pile :
pile :
pile :
pile :
pile :
pile :
pile :
pile :

0, pas de solution relle


sqrt(b*b - 4*a*c)
et dcale la pile
1,0
a, 1,0
a * 2(1,0) = 2*a, 1
1/(2*a)
1/(2*a)
b, 1/(2*a)
disc, b, 1/(2*a)
disc - b, 1/(2*a)
(-b + disc)/(2*a)

stocke
pile :
pile :
pile :
pile :
pile :

dans *root1
b
disc, b
-disc, b
-disc - b
(-b - disc)/(2*a)

stocke dans *root2


la valeur de retour est 1

68
69
70

no_real_solutions:
mov
eax, 0

71
72
73
74
75

quit:

pop
mov
pop

ebx
esp, ebp
ebp

; la valeur de retour est 0

6.3.

LE COPROCESSEUR ARITHMTIQUE

ret

76

139

quad.asm

6.3.5 Lire un tableau depuis un chier


Dans cet exemple, une routine assembleur lit des doubles depuis un chier. Voici un court programme de test en C :

readt.c
1
2
3
4
5
6
7
8
9
10
11
12

Ce programme teste la procdure assembleur 32 bits read_doubles().


Il lit des doubles depuis stdin ( Utilisez une redirection pour lire depuis un chier ).
/

#include <stdio.h>
extern int read_doubles( FILE , double , int );
#dene MAX 100
int main()
{
int i ,n;
double a[MAX];

13

n = read_doubles(stdin, a , MAX);

14
15
16
17
18
19

for ( i=0; i < n; i++ )


printf ("%3d %g\n", i, a[i ]);
return 0;

readt.c
Voici la routine assembleur

1
2

segment .data
format db

read.asm
"%lf", 0

; format pour fscanf()

3
4
5
6

segment .text
global _read_doubles
extern _fscanf

7
8
9

%define SIZEOF_DOUBLE
%define FP

8
dword [ebp + 8]

140

10
11
12

CHAPITRE 6.

%define ARRAYP
%define ARRAY_SIZE
%define TEMP_DOUBLE

VIRGULE FLOTTANTE

dword [ebp + 12]


dword [ebp + 16]
[ebp - 8]

13
14
15
16
17
18
19
20
21
22
23
24
25

;
;
;
;
;
;
;
;
;
;
;
;

fonction _read_doubles
prototype C :
int read_doubles( FILE * fp, double * arrayp, int array_size );
Cette fonction lit des doubles depuis un fichier texte dans un tableau, jusqu'
EOF ou que le tableau soit plein.
Paramtres :
fp
- FILE pointeur partir duquel lire (doit tre ouvert en lecture)
arrayp
- pointeur vers le tableau de double vers lequel lire
array_size - nombre d'lments du tableau
Valeur de retour :
nombre de doubles stocks dans le tableau (dans EAX)

26
27
28
29
30

_read_doubles:
push
ebp
mov
ebp,esp
sub
esp, SIZEOF_DOUBLE

; dfinit un double sur la pile

31
32
33
34

push
mov
xor

esi
esi, ARRAYP
edx, edx

; sauve esi
; esi = ARRAYP
; edx = indice du tableau (initialement 0)

35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

while_loop:
cmp
edx, ARRAY_SIZE
; edx < ARRAY_SIZE ?
jnl
short quit
; si non, quitte la boucle
;
; appelle fscanf() pour lire un double dans TEMP_DOUBLE
; fscanf() peut changer edx, donc on le sauvegarde
;
push
edx
; sauve edx
lea
eax, TEMP_DOUBLE
push
eax
; empile &TEMP_DOUBLE
push
dword format
; emplie &format
push
FP
; emplie file pointer
call
_fscanf
add
esp, 12
pop
edx
; restaure edx
cmp
eax, 1
; fscanf a retourn 1?

6.3.

LE COPROCESSEUR ARITHMTIQUE

jne

52

short quit

141

; si non, on quitte la boucle

53
54
55
56
57
58
59
60
61

;
; copie TEMP_DOUBLE dans ARRAYP[edx]
; (Les 8 octets du double sont copis en deux fois 4 octets)
;
mov
eax, [ebp - 8]
mov
[esi + 8*edx], eax
; copie des 4 octets de poids faible
mov
eax, [ebp - 4]
mov
[esi + 8*edx + 4], eax ; copie des 4 octets de poids fort

62
63
64

inc
jmp

edx
while_loop

pop

esi

; restaure esi

mov

eax, edx

; stocke la valeur de retour dans eax

mov
pop
ret

esp, ebp
ebp

65
66
67

quit:

68
69
70
71
72
73

read.asm

6.3.6 Renchercher les nombres premiers


Ce dernier exemple recherche les nombres premiers, une fois de plus.
Cette implmentation est plus ecace que la prcdente. Elle stocke les
nombres premiers dj trouvs dans un tableau et ne divise que par ceux-ci
au lieu de diviser par tous les nombres impairs pour trouver de nouveaux
premiers.
Une autre dirence est qu'il calcule la racine carre du candidat pour
le prochain premier an de dterminer le moment o il peut s'arrter de
rechercher des facteurs. Il altre le fonctionnement du coprocesseur an que
lorsqu'il stocke la racine sous forme d'entier, il la tronque au lieu de l'arrondir. Ce sont les bits 10 et 11 du mot de contrle qui permettent de paramtrer
cela. Ces bits sont appeles les bits RC (Rounding Control, contrle d'arrondi). S'ils sont tous les deux 0 (par dfaut), le coprocesseur arrondit
lorsqu'il convertit vers un entier. S'ils sont tous les deux 1, le copresseur
tronque les conversions en entiers. Notez que la routine fait attention sauvegarder le mot de contrle original et le restaurer avant de quitter.
Voici le programme C pilote :

fprime.c

142

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

int main()
{
int statut ;
unsigned i;
unsigned max;
int a;
printf ("Combien de nombres premiers voulez vous trouver ? ");
scanf("%u", &max);

19
20
21

a = calloc ( sizeof ( int ), max);

22
23

if ( a ) {

24
25

nd_primes(a,max);

26
27

/ ache les 20 derniers nombres premiers trouvs /


for ( i= ( max > 20 ) ? max 20 : 0; i < max; i++ )
printf ("%3d %d\n", i+1, a[i]);

28
29
30
31

free (a);
statut = 0;

32
33

}
else {
fprintf ( stderr , "Impossible de crer un tableau de %u entiers\n", max);
statut = 1;
}

34
35
36
37
38
39
41

VIRGULE FLOTTANTE

#include <stdio.h>
#include <stdlib.h>
/
fonction nd_primes
recherche le nombre indiqu de nombres premiers
Paramtres:
a tableau pour contenir les nombres premiers
n nombre de nombres premiers trouver
/
extern void nd_primes( int a , unsigned n );

18

40

CHAPITRE 6.

return statut ;

6.3.

LE COPROCESSEUR ARITHMTIQUE

143

fprime.c
Voici la routine assembleur :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

prime2.asm
segment .text
global _find_primes
;
; fonction find_primes
; trouve le nombre indiqu de nombre premiers
; Paramtres:
; array - tableau pour contenir les nombres premiers
; n_find - nombre de nombres premiers trouver
; Prototype C :
;extern void find_primes( int * array, unsigned n_find )
;
%define array
ebp + 8
%define n_find
ebp + 12
%define n
ebp - 4
; Nombre de nombres premiers trouvs
%define isqrt
ebp - 8
; racine du candidat
%define orig_cntl_wd ebp - 10
; mot de contrle original
%define new_cntl_wd ebp - 12
; nouveau mot de contrle

18
19
20

_find_primes:
enter

12,0

; fait de la place pour les variables locales

push
push

ebx
esi

; sauvegarde les variables registre ventuelles

fstcw
mov
or
mov
fldcw

word [orig_cntl_wd]
ax, [orig_cntl_wd]
ax, 0C00h
[new_cntl_wd], ax
word [new_cntl_wd]

; rcupre le mot de contrle courant


; positionne les bits d'arrondi 11 (tronquer)

mov
mov
mov
mov
mov

esi, [array]
dword [esi], 2
dword [esi + 4], 3
ebx, 5
dword [n], 2

;
;
;
;
;

21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

esi pointe sur array


array[0] = 2
array[1] = 3
ebx = guess = 5
n = 2

;
; Cette boucle externe trouve un nouveau nombre premier chaque itration qu'il
; ajoute la fin du tableau. Contrairement au programme de recherche de nombres

144

39
40
41
42
43
44
45
46

CHAPITRE 6.

VIRGULE FLOTTANTE

; premiers prcdent, cette fonction ne dtermine pas la primaut du nombre en


; divisant par tous les nombres impairs. Elle ne divise que par les nombres
; premiers qu'elle a dj trouv (c'est pourquoi ils sont stocks dans un tableau).
;
while_limit:
mov
eax, [n]
cmp
eax, [n_find]
; while ( n < n_find )
jnb
short quit_limit

47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70

mov
push
fild
pop
fsqrt
fistp

ecx, 1
ebx
dword [esp]
ebx

;
;
;
;
;
;

ecx est utilis comme indice


stocke le candidat sur la pile
le charge sur la pile du coprocesseur
retire le candidat de la pile
calcule sqrt(guess)
isqrt = floor(sqrt(quess))

dword [isqrt]
;
; Cette boucle interne divise le candidat (ebx) par les nombres premiers
; calculs prcdemment jusqu' ce qu'il trouve un de ses facteurs premiers
; (ce qui signifie que le candidat n'est pas premier) ou jusqu' ce que le
; nombre premier par lequel diviser soit plus grand que floor(sqrt(guess))
;
while_factor:
mov
eax, dword [esi + 4*ecx]
; eax = array[ecx]
cmp
eax, [isqrt]
; while ( isqrt < array[ecx]
jnbe
short quit_factor_prime
mov
eax, ebx
xor
edx, edx
div
dword [esi + 4*ecx]
or
edx, edx
; && guess % array[ecx] != 0 )
jz
short quit_factor_not_prime
inc
ecx
; essaie le nombre premier suivant
jmp
short while_factor

71
72
73
74
75
76
77
78
79
80

;
; found a new prime !
;
quit_factor_prime:
mov
eax, [n]
mov
dword [esi + 4*eax], ebx
inc
eax
mov
[n], eax

; ajoute le candidat au tableau


; incrmente n

6.3.

81
82
83

LE COPROCESSEUR ARITHMTIQUE

quit_factor_not_prime:
add
ebx, 2
jmp
short while_limit

145

; essaie le nombre impair suivant

84
85

quit_limit:

86
87
88
89

fldcw
pop
pop

word [orig_cntl_wd]
esi
ebx

90
91
92

leave
ret

prime2.asm

; restaure le mot de contrle


; restaure les variables registre

146

CHAPITRE 6.

VIRGULE FLOTTANTE

global _dmax

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

segment .text
; fonction _dmax
; retourne le plus grand de ses deux arguments double
; prototype C
; double dmax( double d1, double d2 )
; Paramtres :
; d1 - premier double
; d2 - deuxime double
; Valeur de retour :
; le plus grand de d1 et d2 (dans ST0)
%define d1 ebp+8
%define d2 ebp+16
_dmax:
enter 0, 0

17
18
19
20
21
22
23
24
25

fld
qword
fld
qword
fcomip st1
jna
short
fcomp st0
fld
qword
jmp
short
d2_bigger:

26
27
28
29

exit:

[d2]
[d1]
d2_bigger
[d1]
exit

; ST0 = d1, ST1 = d2


; ST0 = d2
; retire d2 de la pile
; ST0 = d1
; si d2 est le plus grand
; rien faire

leave
ret

Fig. 6.7  Exemple de

FCOMIP

6.3.

1
2
3

147

LE COPROCESSEUR ARITHMTIQUE

segment .data
x
dq 2,75
five
dw 5

; converti au format double

4
5
6
7
8

segment .text
fild dword [five]
fld
qword [x]
fscale

; ST0 = 5
; ST0 = 2,75, ST1 = 5
; ST0 = 2,75 * 32, ST1 = 5

Fig. 6.8  Exemple d'utilisation de

FSCALE

148

CHAPITRE 6.

VIRGULE FLOTTANTE

Chapitre 7

Structures et C++
7.1

Structures

7.1.1 Introduction
Les structures sont utilises en C pour regrouper des donnes ayant un
rapport entre elles dans une variable composite. Cette technique a plusieurs
avantages :
1. Cela clarie le code en montrant que les donnes dnies dans la structure sont intimement lies.
2. Cela simplie le passage des donnes aux fonctions. Au lieu de passer
plusieurs variables sparment, elles peuvent tre passes en une seule
entit.
3. Cela augmente la

localit 1

du code.

Du point de vue de l'assembleur, une structure peut tre considre


comme un tableau avec des lments de taille

variable. Les lments des vrais

tableaux sont toujours de la mme taille et du mme type. C'est cette proprit qui permet de calculer l'adresse de n'importe quel lment en connaissant l'adresse de dbut du tableau, la taille des lments et l'indice de l'lment voulu.
Les lments d'une structure ne sont pas ncessairement de la mme taille
(et habituellement, ils ne le sont pas). A cause de cela, chaque lment d'une
structure doit tre explicitement spci et doit recevoir un

tag

(ou nom) au

lieu d'un indice numrique.


En assembleur, on accde un lment d'une structure d'une faon similaire l'accs un lment de tableau. Pour accder un lment, il faut
connatre l'adresse de dpart de la structure et le

dplacement relatif

de cet

1
Votez le chapitre sur la gestion de la mmoire virtuelle de n'importe quel livre sur les
Systmes d'Exploitation pour une explication de ce terme.
149

150

CHAPITRE 7.

Dplacement

STRUCTURES ET C++

Elment

0
2

y
6

z
Fig. 7.1  Structure S

lment par rapport au dbut de la structure. Cependant, contrairement


un tableau o ce dplacement peut tre calcul grce l'indice de l'lment,
c'est le compilateur qui aecte un dplacement aux lments d'une structure.
Par exemple, considrons la structure suivante :

struct S {
short int x ;
int
y;
double z ;
};

/ entier sur 2 octets /


/ entier sur 4 octets /
/ ottant sur 8 octets /

La Figure 7.1 montre quoi pourrait ressembler une variable de type

pourrait ressembler en mmoire. Le standard ANSI C indique que les lments d'une structure sont organiss en mmoire dans le mme ordre que
celui de leur dnition dans le

struct.

Il indique galement que le premier

lment est au tout dbut de la structure (

i.e. au dplacement zro). Il d-

nit galement une macro utile dans le chier d'en-tte

offsetof().

stddef.h

appele

Cette macro calcule et renvoie le dplacement de n'importe

quel lment d'une structure. La macro prend deux paramtres, le premier


est le nom du

type

de la structure, le second est le nom de l'lment dont on

veut le dplacement. Donc le rsultat de

offsetof(S, y) serait 2 d'aprs la

Figure 7.1.

7.1.2 Alignement en mmoire


Si l'on utilise la macro

offsetof

pour trouver le dplacement de

en

utilisant le compilateur gcc, on s'aperoit qu'elle renvoie 4, pas 2 ! Pourquoi ?


Souvenez

vous

qu'une Parce que

gcc (et beaucoup d'autre compilateurs) aligne les variables sur des

adresse est sur un multiple multiples de doubles mots par dfaut. En mode protg 32 bits, le processeur
de double mot si elle est lit la mmoire plus vite si la donne commence sur un multiple de double
divisible par 4
mot. La Figure 7.2 montre quoi ressemble la structure S en utilisant
.

gcc

y
z) sur un multiple de double mot. Cela montre pourquoi c'est une bonne

Le compilateur insre deux octets inutiliss dans la structure pour aligner


(et

7.1.

151

STRUCTURES

Oset
0
2

Elment

inutilis

y
8

z
Fig. 7.2  Structure S

ide d'utiliser

offsetof pour calculer les dplacements, au lieu de les calculer

soi-mme lorsqu'on utilise des structures en C.


Bien sr, si la structure est utilise uniquement en assembleur, le programmeur peut dterminer les dplacements lui-mme. Cependant, si l'on
interface du C et de l'assembleur, il est trs important que l'assembleur et
le C s'accordent sur les dplacements des lments de la structure ! Une des
complications est que des compilateurs C dirents peuvent donner des dplacements dirents aux lments. Par exemple, comme nous l'avons vu,
le compilateur

gcc

cre une structure

qui ressemble la Figure 7.2 ; ce-

pendant, le compilateur de Borland crerait une structure qui ressemble


la Figure 7.1. Les compilateurs C fournissent le moyen de spcier l'alignement utilis pour les donnes. Cependant, le standard ANSI C ne spcie pas
comment cela doit tre fait et donc, des compilateurs dirents procdent
diremment.
Le compilateur

gcc

a une mthode exible et complique de spcier

l'alignement. Le compilateur permet de spcier l'alignement de n'importe


quel type en utilisant une syntaxe spciale. Par exemple, la ligne suivante :

typedef short int unaligned_int __attribute__((aligned(1)));


dnit un nouveau type appel

unaligned_int

qui est align sur des mul-

__attribute__ sont naligned peut tre remplac par d'autres puis-

tiples d'octet (Oui, toutes les parenthses suivant


cessaires !) Le paramtre 1 de

sances de deux pour spcier d'autres alignements (2 pour s'aligner sur les
mots, 4 sur les doubles mots,
en un type

etc.). Si l'lment y de la structure tait chang

unaligned_int, gcc

placerait

au dplacement 2. Cependant,

serait toujours au dplacement 8 puisque les doubles sont galement aligns


sur des doubles mots par dfaut. La dnition du type de

devrait aussi

tre change pour le placer au dplacement 6.


Le compilateur

gcc permet galement de comprimer (pack) une structure.

Cela indique au compilateur d'utiliser le minimum d'espace possible pour la


structure. La Figure 7.3 montre comment

pourrait tre rcrite de cette

152

CHAPITRE 7.

STRUCTURES ET C++

struct S {
short int x ;
/ entier sur 2 octets /
int
y;
/ entier sur 4 octets /
double z ;
/ ottant sur 8 octets /
} __attribute__((packed));
Fig. 7.3  Structure comprime sous

gcc

#pragma pack(push) / sauve l' tat de l 'alignement /


#pragma pack(1)
/ dnit un alignement sur octet /
struct S {
short int x ;
int
y;
double z ;
};

/ entier sur 2 octets /


/ entier sur 4 octets /
/ ottant sur 8 octets /

#pragma pack(pop) / restaure l 'alignement original /


Fig. 7.4  Structure comprime sous les compilateurs Microsoft ou Borland

faon. Cette forme de

utiliserait le moins d'octets possible, soit 14 octets.

Les compilateurs de Microsoft et Borland supportent tous les deux la


mme mthode pour indiquer l'alignement par le biais d'une directive

#pragma.

#pragma pack(1)
La directive ci-dessus indique au compilateur d'aligner les lments des struc-

i.e., sans dcalage superu). Le un peut

tures sur des multiples d'un octet (

tre remplac par deux, quatre, huit ou seize pour spcier un alignement
sur des multiples de mots, doubles mots, quadruples mots ou de paragraphe,
respectivement. La directive reste active jusqu' ce qu'elle soit crase par
une autre. Cela peut poser des problmes puisque ces directives sont souvent
utilises dans des chiers d'en-tte. Si le chier d'en-tte est inclus avant
d'autres chiers d'en-tte dnissant des structures, ces structures peuvent
tre organises diremment de ce qu'elles auraient t par dfaut. Cela peut
conduire des erreurs trs diciles localiser. Les dirents modules d'un
programmes devraient organiser les lments des structures

dirents

en-

droits !
Il y a une faon d'viter ce problme. Microsoft et Borland permettent la
sauvegarde de l'tat de l'alignement courant et sa restauration. La Figure 7.4
montre comment on l'utilise.

7.1.

153

STRUCTURES

struct S {
unsigned f1
unsigned f2
unsigned f3
unsigned f4
};

: 3;
: 10;
: 11;
: 8;

/ champ de 3 bits /
/ champ de 10 bits /
/ champ de 11 bits /
/ champ de 8 bits /

Fig. 7.5  Exemple de Champs de Bits

7.1.3 Champs de Bits


Les champs de bits permettent de dclarer des membres d'une structure
qui n'utilisent qu'un nombre de bits donn. La taille en bits n'a pas besoin
d'tre un multiple de huit. Un membre champ de bit est dni comme un

unsigned int

ou un

int

suivi de deux-points et de sa taille en bits. La Fi-

gure 7.5 en montre un exemple. Elle dnit une variable 32 bits dcompose
comme suit :
8 bits

11 bits

10 bits

3 bits

f4

f3

f2

f1

Le premier champ de bits est assign aux bits les moins signicatifs du double

mot .
Nanmoins, le format n'est pas si simple si l'on observe comment les bits
sont stocks en mmoire. La dicult apparat lorsque les champs de bits
sont cheval sur des multiples d'octets. Car les octets, sur un processeur
little endian seront inverss en mmoire. Par exemple, les champs de bits de
la structure

ressembleront cela en mmoire :

5 bits

3 bits

3 bits

5 bits

8 bits

8 bits

f2l

f1

f3l

f2m

f3m

f4

L'tiquette

f2l

i.e., les cinq bits les


f2m fait rfrence aux

fait rfrence aux cinq derniers bits (

moins signicatifs) du champ de bits


cinq bits les plus signicatifs de

f2.

L'tiquette

f2. Les lignes verticales doubles montrent les


f2

limites d'octets. Si l'on inverse tous les octets, les morceaux des champs
et

f3

seront runis correctement.

L'organisation de la mmoire physique n'est habituellement pas importante moins que des donnes de soient transfres depuis ou vers le programme (ce qui est en fait assez courant avec les champs de bits). Il est
courant que les interfaces de priphriques matriels utilisent des nombres
impairs de bits dont les champs de bits facilitent la reprsentation.

En fait, le standard ANSI/ISO C laisse une certaine libert au compilateur sur la faon
d'organiser les bits. Cependant, les compilateurs C courants (gcc, Microsoft et Borland )
organisent les champs comme cela.

154

CHAPITRE 7.

Byte

Bit

STRUCTURES ET C++

Code Opration (08h)

d'Unit Logique

msb de l'ABL

milieu de l'Adresse de Bloc Logique

lsb de l'Adresse de Bloc Logique

Longueur du Transfert

Contrle

Fig. 7.6  Format de la Commande de Lecture SCSI

Un bon exemple est SCSI . Une commande de lecture directe pour un


priphrique SCSI est spcie en envoyant un message de six octets au
priphrique selon le format indiqu dans la Figure 7.6. La dicult de reprsentation en utilisant les champs de bits est l'

adresse de bloc logique

qui

est cheval sur trois octets dirents de la commande. D'aprs la Figure 7.6,
on constate que les donnes sont stockes au format big endian. La Figure 7.7
montre une dnition qui essaie de fonctionner avec tous les compilateurs.
Les deux premires lignes dnissent une macro qui est vraie si le code est
compil avec un compilateur Borland ou Microsoft. La partie qui peut porter
confusion va des lignes 11 14. Tout d'abord, on peut se demander pourquoi les champs

lba_mid et lba_lsb sont dnis sparment et non pas comme

un champ unique de 16 bits. C'est parce que les donnes sont stockes au
format big endian. Un champ de 16 bits serait stock au format little endian
par le compilateur. Ensuite, les champs

lba_msb

et

logical_unit

semblent

tre inverss ; cependant, ce n'est pas le cas. Ils doivent tre placs dans cet
ordre. La Figure 7.8 montre comment les champs sont organiss sous forme
d'une entit de 48 bits (les limites d'octets sont l encore reprsentes par des
lignes doubles). Lorsqu'elle est stocke en mmoire au format little endian,
les bits sont rarrangs au format voulu (Figure 7.6).
Pour compliquer encore plus le problme, la dnition de

SCSI_read_cmd
sizeof (SCSI_read_cmd)

ne fonctionne pas correctement avec le C Microsoft. Si l'expression

est value, le C Microsoft renvoie 8 et non pas 6 ! C'est parce que le compilateur Microsoft utilise le type du champ de bits pour dterminer comment
organiser les bits. Comme tous les bits sont dclars comme

unsigned,

le

compilateur ajoute deux octets la n de la structure pour qu'elle comporte


un nombre entier de double mots. Il est possible d'y remdier en dclarant tous les champs

unsigned short. Maintenant, le compilateur Microsoft

n'a plus besoin d'ajouter d'octets d'alignement puisque six octets forment

3
etc.

Small Computer Systems Interface, un standard de l'industrie pour les disques durs,

7.1.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

155

STRUCTURES

#dene MS_OR_BORLAND (dened(__BORLANDC__) \


|| dened (_MSC_VER))
#if MS_OR_BORLAND
# pragma pack(push)
# pragma pack(1)
#endif
struct SCSI_read_cmd {
unsigned opcode : 8;
unsigned lba_msb : 5;
unsigned logical_unit : 3;
unsigned lba_mid : 8; / bits du milieu /
unsigned lba_lsb : 8;
unsigned transfer_length : 8;
unsigned control : 8;
}
#if dened(__GNUC__)
__attribute__((packed))
#endif
;
#if MS_OR_BORLAND
# pragma pack(pop)
#endif
Fig. 7.7  Structure du Format de la Commande de Lecture SCSI

un nombre entier de mots de deux octets . Les autres compilateurs fonctionnent galement correctement avec ce changement. La Figure 7.9 montre
une autre dnition qui fonctionne sur les trois compilateurs. Il ne dclare
plus que deux champs de bits en utilisant le type

unsigned char.

Le lecteur ne doit pas se dcourager s'il trouve la discussion ci-dessus


confuse. C'est confus ! L'auteur trouve souvent moins confus d'viter d'utiliser des champs de bits en utilisant des oprations niveau bit pour examiner
et modier les bits manuellement.

4
Mlanger dirents types de champs de bits conduit un comportement trs trange !
Le lecteur est invit tester.

156

CHAPITRE 7.

8 bits

8 bits

8 bits

8 bits

3 bits

5 bits

8 bits

control

transfer_length

lba_lsb

lba_mid

logical_unit

lba_msb

opcode

Fig. 7.8  Organisation des champs de

1
2
3
4
5
6
7
8
9
10
11
12
13

STRUCTURES ET C++

SCSI_read_cmd

struct SCSI_read_cmd {
unsigned char opcode;
unsigned char lba_msb : 5;
unsigned char logical_unit : 3;
unsigned char lba_mid; / bits du milieu /
unsigned char lba_lsb;
unsigned char transfer_length;
unsigned char control;
}
#if dened(__GNUC__)
__attribute__((packed))
#endif
;
Fig. 7.9  Structure du Format de la Commande de Lecture SCSI Alternative

7.1.4 Utiliser des structures en assembleur


Comme nous l'avons dit plus haut, accder une structure en assembleur
ressemble beaucoup accder un tableau. Prenons un exemple simple,
regardons comment l'on pourrait crire une routine assembleur qui mettrait
zro l'lment

y d'une structure S. Supposons que le prototype de la routine

soit :

void zero_y( S s_p );


La routine assembleur serait :

1
2
3
4
5
6
7

%define
_zero_y:
enter
mov
mov
leave
ret

y_offset

0,0
eax, [ebp + 8]
; rcupre s_p depuis la pile
dword [eax + y_offset], 0

Le C permet de passer une structure par valeur une fonction ; cependant, c'est une mauvaise ide la plupart du temps. Toutes les donnes de la

7.2.

1
2
3
4
5
6
7
8
9
10
11

157

ASSEMBLEUR ET C++

#include <stdio.h>
void f ( int x )
{
printf ("%d\n", x);
}
void f ( double x )
{
printf ("%g\n", x);
}
Fig. 7.10  Deux fonctions

f()

structure doivent tre copies sur sur la pile puis rcupres par la routine. Il
est beaucoup plus ecace de passer un pointeur vers la structure la place.
Le C permet aussi qu'une fonction renvoie une structure. Evidemment,
une structure ne peut pas tre retourne dans le registre

EAX.

Des compi-

lateurs dirents grent cette situation de faon dirente. Une situation


courante que les compilateurs utilisent est de rcrire la fonction en interne
de faon ce qu'elle prenne un pointeur sur la structure en paramtre. Le
pointeur est utilis pour placer la valeur de retour dans une structure dnie
en dehors de la routine appele.
La plupart des assembleur (y compris NASM) ont un support intgr
pour dnir des structures dans votre code assembleur. Reportez vous
votre documentation pour plus de dtails.

7.2

Assembleur et C++

Le langage de programmation C++ est une extension du langage C.


Beaucoup des rgles valables pour interfacer le C et l'assembleur s'appliquent
galement au C++. Cependant, certaines rgles doivent tre modies. De
plus, certaines extension du C++ sont plus faciles comprendre en connaissant le langage assembleur. Cette section suppose une connaissance basique
du C++.

7.2.1 Surcharge et Dcoration de Noms


Le C++ permet de dnir des fonctions (et des fonctions membres) diffrentes avec le mme nom. Lorsque plus d'une fonction partagent le mme
nom, les fonctions sont dites

surcharges. Si deux fonctions sont dnies avec

le mme nom en C, l'diteur de liens produira une erreur car il trouvera deux

158

CHAPITRE 7.

STRUCTURES ET C++

dnitions pour le mme symbole dans les chiers objets qu'il est en train
de lier. Par exemple, prenons le code de la Figure 7.10. Le code assembleur
quivalent dnirait deux tiquettes appeles

_f

ce qui serait bien sr une

erreur.
Le C++ utilise le mme procd d'dition de liens que le C mais vite
cette erreur en eectuant une

dcoration de nom (name mangling) ou en mo-

diant le symbole utilis pour nommer une fonction. D'une certaine faon, le
C utilise dj la dcoration de nom. Il ajoute un caractre de soulignement
au nom de la fonction C lorsqu'il cre l'tiquette pour la fonction. Cependant, il dcorera le nom des deux fonctions de la Figure 7.10 de la mme
faon et produira une erreur. Le C++ utilise un procd de dcoration plus
sophistiqu qui produit deux tiquettes direntes pour les fonctions. Par

_f__Fi
_f__Fd, sous DJGPP. Cela vite toute erreur d'dition de liens.

exemple, la premire fonction de la Figure 7.10 recevrait l'tiquette


et la seconde,

Malheureusement, il n'y a pas de standard sur la gestion des noms en


C++ et des compilateurs dirents dcorent les noms de faon dirente.
Par exemple, Borland C++ utiliserait les tiquettes

@f$qi et @f$qd pour les

deux fonctions de la Figure 7.10. Cependant, les rgles ne sont pas totalement
arbitraires. Le nom dcor encode la

signature

de la fonction. La signature

d'une fonction est donne par l'ordre et le type de ses paramtres. Notez
que la fonction qui ne prend qu'un argument

int

a un

la n de son nom

dcor ( la fois sous DJGPP et Borland) et que celle qui prend un argument

double a un d la
f avec le prototype

n de son nom dcor. S'il y avait une fonction appele


suivant :

void f ( int x , int y , double z);


DJGPP dcorerait son nom en

_f__Fiid

Le type de la fonction ne fait

pas

et Borland en

@f$qiid.

partie de la signature d'une fonction et

n'est pas encod dans nom dcor. Ce fait explique une rgle de la surcharge
en C++. Seules les fonctions dont les signatures sont uniques peuvent tre
surcharges. Comme on le voit, si deux fonctions avec le mme nom et la
mme signature sont dnies en C++, elle donneront le mme nom dcor
et creront une erreur lors de l'dition de liens. Par dfaut, toutes les fonctions C++ sont dcores, mme celles qui ne sont pas surcharges. Lorsqu'il
compile un chier, le compilateur n'a aucun moyen de savoir si une fonction
particulire est surcharge ou non, il dcore donc touts les noms. En fait,
il dcore galement les noms des variables globales en encodant le type de
la variable d'une faon similaire celle utilise pour les signatures de fonctions. Donc, si l'on dnit une variable globale dans un chier avec un certain
type puis que l'on essaie de l'utiliser dans un autre chier avec le mauvais
type, l'diteur de liens produira une erreur. Cette caractristique du C++
est connue sous le nom de

typesafe linking

(dition de liens avec respect des

types) . Cela cre un autre type d'erreurs, les prototypes inconsistants. Cela

7.2.

159

ASSEMBLEUR ET C++

arrive lorsque la dnition d'une fonction dans un module ne correspond


pas avec le prototype utilis par un autre module. En C, cela peut tre un
problme trs dicile corriger. Le C ne dtecte pas cette erreur. Le programme compilera et sera li mais aura un comportement imprvisible car le
code appelant placera sur la pile des types dirents de ceux que la fonction
attend. En C++ cela produira une erreur lors de l'dition de liens.
Lorsque le compilateur C++ analyse un appel de fonction, il recherche

la fonction correspondante en observant les arguments qui lui sont passs .


S'il trouve une correspondance, il cre un

CALL

vers la fonction adquate en

utilisant les rgles de dcoration du compilateur.


Comme des compilateurs dirents utilisent direntes rgles de dcoration de nom, il est possible que des codes C++ compils par des compilateurs dirents ne puissent pas tre lis ensemble. C'est important lorsque
l'on a l'intention d'utiliser une bibliothque C++ prcompile ! Si l'on veut
crire une fonction en assembleur qui sera utilise avec du code C++, il faut
connatre les rgles de dcoration de nom du compilateur C++ utilis (ou
utiliser la technique explique plus bas).
L'tudiant astucieux pourrait se demander si le code de la Figure 7.10
fonctionnera de la faon attendue. Comme le C++ dcore toutes les fonc-

printf sera dcore et le compilateur ne produira pas


un CALL l'tiquette _printf. C'est une question pertinente. Si le prototype
de printf tait simplement plac au dbut du chier, cela arriverait. Son
tions, alors la fonction

prototype est :

int printf ( const char , ...);


DJGPP dcorerait ce nom en

_printf__FPCce (F pour fonction, P pour


et e pour ellipse). Cela n'appelerait pas

pointeur, C pour const, c pour char


la fonction

printf de la bibliothque C standard ! Bien sr, il doit y avoir un

moyen pour que le C++ puisse appeler du code C. C'est trs important car
il existe une

norme

quantit de vieux code C utile. En plus de permettre

l'accs au code hrit de C, le C++ permet galement d'appeler du code


assembleur en utilisant les conventions de dcoration standards du C.
Le C++ tend le mot-cl

extern

pour lui permettre de spcier que la

fonction ou la variable globale qu'il modie utilise les conventions C normales. Dans la terminologie C++, la fonction ou la variable globale utilise
une

dition de liens C. Par exemple, pour dclarer la fonction printf comme

ayant une dition de liens C, utilisez le prototype :

extern "C" int printf ( const char , ... );


5

La correspondance n'a pas tre exacte, le compilateur prendra en compte les correspondances trouves en transtypant les arguments. Les rgles de ce procd sont en dehors
de la porte de ce livre. Consultez un livre sur le C++ pour plus de dtails.

160

1
2
3
4
5
6
7
8
9
10

CHAPITRE 7.

void f ( int \& x )


{ x++; }

STRUCTURES ET C++

// le & indique un paramtre par rfrence

int main()
{
int y = 5;
f(y );
// une rfrence sur y est passe, pas de & ici !
printf ("%d\n", y); // ache 6 !
return 0;
}
Fig. 7.11  Exemple de rfrence

Cela impose au compilateur de ne pas utiliser les rgles de dcoration de nom


du C++ sur la fonction, mais d'utiliser les rgles C la place. Cependant, en
faisant cela, la fonction

printf

ne peut pas tre surcharge. Cela constitue

la faon la plus simple d'interfacer du C++ et de l'assembleur, dnir une


fonction comme utilisant une dition de liens C puis utiliser la convention
d'appel C.
Pour plus de facilit, le C++ permet galement de dnir une dition de
liens C sur un bloc de fonctions et de variables globales. Le bloc est indiqu
en utilisant les accolades habituelles.

extern "C" {
/ variables globales et prototypes des fonction ayant une dition de liens C /
}
Si l'on examine les chiers d'en-tte ANSI C fournis avec les compilateur
C/C++ actuels, on trouve ce qui suit vers le dbut de chaque chier d'entte :

#ifdef __cplusplus
extern "C" {
#endif
Et une construction similaire, vers la n, contenant une accolade fermante.
Les compilateurs C++ dnissent la macro

__cplusplus

(avec

deux

carac-

tres de soulignement au dbut). L'extrait ci-dessus entoure tout le chier


d'en-tte dans un bloc

extern "C" si le chier d'en-tte est compil en C++,

mais ne fait rien s'il est compil en C (puisqu'un compilateur C gnrerait


une erreur de syntaxe sur

extern "C").

La mme technique peut tre utili-

se par n'importe quel programmeur pour crer un chier d'en-tte pour des
routines assembleur pouvant tre utilises en C ou en C++.

7.2.

161

ASSEMBLEUR ET C++

7.2.2 Rfrences
Les

rfrences

sont une autre nouvelle fonctionnalit du C++. Elles per-

mettent de passer des paramtres une fonction sans utiliser explicitement


de pointeur. Par exemple, considrons le code de la Figure 7.11. En fait, les
paramtres par rfrence sont plutt simples, ce sont des pointeurs. Le compilateur masque simplement ce fait aux yeux du programmeur (exactement de
la mme faon que les compilateurs Pascal qui implmentent les paramtres

var

comme des pointeurs). Lorsque le compilateur gnre l'assembleur pour

adresse

l'appel de fonction ligne 7, il passe l'

de

y.

Si l'on crivait la fonction

en assembleur, on ferait comme si le prototype tait

6 :

void f ( int xp);


Les rfrences sont juste une facilit qui est particulirement utile pour la
surcharge d'oprateurs. C'est une autre fonctionnalit du C++ qui permet
de donner une signication aux oprateurs de base lorsqu'ils sont appliqus
des structures ou des classes. Par exemple, une utilisation courante est de
dnir l'oprateur plus (+) de faon ce qu'il concatne les objets string.
Donc, si

et

b.

a et b sont des strings, a + b renverra la concatnation des chanes

Le C++ appelerait en ralit une fonction pour ce faire (en fait,

cette expression pourrait tre rcrite avec une notation fonction sous la
forme

operator +(a,b)).

Pour plus d'ecacit, il est souhaitable de passer

l'adresse des objets string la place de les passer par valeur. Sans rfrence,

operator +(&a,&b), mais cela imposerait


&a + &b. Cela serait trs maladroit et
les rfrences, il est possible d'crire a + b,

cela pourrait tre fait en crivant

d'crire l'oprateur avec la syntaxe


confus. Par contre, en utilisant
ce qui semble trs naturel.

7.2.3 Fonctions inline


Les

fonctions inline

sont encore une autre fonctionnalit du C++ . Les

fonctions inline sont destines remplacer les macros du prprocesseur qui


prennent des paramtres, sources d'erreur. Sourvenez vous en C, une macro
qui lve un nombre au carr ressemble cela :

#dene SQR(x) ((x)(x))


Comme le prprocesseur ne comprend pas le C et ne fait que de simples
substitutions, les parenthses sont requises pour calculer le rsultat correct
dans la plupart des cas. Cependant, mme cette version ne donnerait pas la
bonne rponse pour

SQR(x++).

Bien sr, il faudrait dclarer la fonction avec une dition de liens en C, comme nous
en avons parl dans la Section 7.2.1
7
Les compilateurs supportent souvent cette fonctionnalit comme une extension du C
ANSI.

162

1
2
3
4
5
6
7
8
9
10
11
12
13

CHAPITRE 7.

STRUCTURES ET C++

inline int inline_f ( int x )


{ return xx; }
int f ( int x )
{ return xx; }
int main()
{
int y , x = 5;
y = f(x);
y = inline_f (x);
return 0;
}
Fig. 7.12  Exemple d'inlining

Les macros sont utilises car elles liminent la surcharge d'un appel pour
une fonction simple. Comme le chapitre sur les sous-programmes l'a dmontr, eectuer un appel de fonction implique plusieurs tapes. Pour une fonction trs simple, le temps pass l'appeler peut tre plus grand que celui
pass dans la fonction ! Les fonctions inline sont une faon beaucoup plus
pratique d'crire du code qui ressemble une fonction mais qui n'eectue

pas

de

CALL

un bloc commun. Au lieu de cela, les appels des fonctions

inline sont remplacs par le code de la fonction. Le C++ permet de rendre


une fonction inline en plaant le mot-cl

inline

au dbut de sa dnition.

Par exemple, considrons les fonctions dclares dans la Figure 7.12. L'appel
la fonction

f,

supposant que

1
2
3
4

push
call
pop
mov

ligne 10, est un appel de fonction normal (en assembleur, en

est l'adresse

ebp-8

1
3

mov
imul
mov

en

ebp-4):

dword [ebp-8]
_f
ecx
[ebp-4], eax

Cependant, l'appel la fonction

et

inline_f,

ligne 11 ressemblerait :

eax, [ebp-8]
eax, eax
[ebp-4], eax

Dans ce cas, il y a deux avantages inliner. Tout d'abord, la fonction


inline est plus rapide. Aucun paramtre n'est plac sur la pile, aucun cadre

7.2.

163

ASSEMBLEUR ET C++

de pile n'est cr puis dtruit, aucun branchement n'est eectu. Ensuite,


l'appel la fonction inline utilise moins de code ! Ce dernier point est vrai
pour cet exemple, mais ne reste pas vrai dans tous les cas.
La principal inconvnient de l'inlining est que le code inline n'est pas li
et donc le code d'une fonction inline doit tre disponible pour

tous les chiers

qui l'utilisent. L'exemple de code assembleur prcdent le montre. L'appel


de la fonction non-inline ne ncessite que la connaissance des paramtres, du
type de valeur de retour, de la convention d'appel et du nom de l'tiquette
de la fonction. Toutes ces informations sont disponibles par le biais du prototype de la fonction. Cependant, l'utilisation de la fonction inline ncessite
la connaissance de tout le code de la fonction. Cela signie que si

porte quelle

partie de la fonction change,

tous

n'im-

les chiers source qui utilisent

la fonction doivent tre recompils. Souvenez vous que pour les fonctions
non-inline, si le prototype ne change pas, souvent les chiers qui utilisent la
fonction n'ont pas besoin d'tre recompils. Pour toutes ces raisons, le code
des fonctions inline est gnralement plac dans les chiers d'en-tte. Cette
pratique est contraire la rgle stricte habituelle du C selon laquelle on ne
doit

jamais

placer de code excutable dans les chiers d'en-tte.

7.2.4 Classes
Une classe C++ dcrit un type d'

objet.

Un objet possde la fois des

8
membres donnes et des membres fonctions . En d'autres termes, il s'agit
d'une

struct

laquelle sont associes des donnes et des fonctions. Consi-

drons la classe simple dnie dans la Figure 7.13. Une variable de type

Simple

ressemblerait une

Les fonctions ne sont

pas

struct

C normale avec un seul membre

int.

aectes la structure en mmoire. Cependant, En fait, le C++ utilise le

les fonctions membres sont direntes des autres fonctions. On leur passe un mot cl this pour accparamtre
. Ce paramtre est un pointeur vers l'objet sur lequel agit la der au pointeur vers l'ob-

cach

jet lorsque l'on se trouve

fonction.
Par exemple, considrons la mthode

set_data

de la classe

Simple

de

la Figure 7.13. Si elle tait crite en C, elle ressemblerait une fonction


laquelle on passerait explicitement un pointeur vers l'objet sur lequel elle
agit comme le montre le code de la Figure 7.14. L'option

DJGPP

(et des compilateurs

gcc

-S

du compilateur

et Borland galement) indique au compi-

lateur de produire un chier assembleur contenant l'quivalent assembleur


du code. Pour

DJGPP

et

gcc

le chier assembleur possde une extension

.s

et utilise malheureusement la syntaxe du langage assembleur AT&T qui est


assez dirente des syntaxes NASM et MASM

9 (les compilateurs Borland et

Souvent appels fonctions membres en C++ ou plus gnralement mthodes .


Le compilateur gcc inclut son propre assembleur appel gas . L'assembleur gas utilise la
syntaxe AT&T et donc le compilateur produit un code au format gas. Il y a plusieurs sites
sur le web qui expliquent les dirences entre les formats INTEL et AT&T. Il existe ga9

l'intrieur d'une fonction


membre.

164

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

CHAPITRE 7.

class Simple {
public :
Simple ();
~Simple();
int get_data() const;
void set_data( int );
private :
int data ;
};

STRUCTURES ET C++

// constructeur par dfaut


// destructeur
// fonctions membres
// donnes membres

Simple :: Simple()
{ data = 0; }
Simple::~Simple()
{ / rien / }

int Simple :: get_data() const


{ return data; }
void Simple :: set_data( int x )
{ data = x; }
Fig. 7.13  Une classe C++ simple

MS gnrent un chier avec l'extension


Figure 7.15 montre la sortie de

DJGPP

.asm utilisant la syntaxe MASM.) La


convertie en syntaxe NASM avec des

commentaires supplmentaires pour clarier le but des instructions. Sur la


toute premire ligne, notez que la mthode

set_data reoit une tiquette d-

core qui encode le nom de la mthode, le nom de la classe et les paramtres.


Le nom de la classe est encod car d'autres classes peuvent avoir une mthode
appele

set_data

et les deux mthodes

doivent

recevoir des tiquettes dif-

frentes. Les paramtres sont encods an que la classe puisse surcharger la
mthode

set_data

an qu'elle prenne d'autres paramtres, comme les fonc-

tions C++ normales. Cependant, comme prcdemment, des compilateurs


dirents encoderont ces informations diremment dans l'tiquette.
Ensuite, aux lignes 2 et 3 nous retrouvons le prologue habituel. A la
ligne 5, le premier paramtre sur la pile est stock dans
paramtre

x ! Il s'agit du paramtre cach10

EAX.

Ce n'est

pas

le

qui pointe vers l'objet sur lequel

lement un programme gratuit appel a2i (http://www.multimania.com/placr/a2i.html),


qui passe du format AT&T au format NASM.
10
Comme d'habitude, rien n'est cach dans le code assembleur !

7.2.

165

ASSEMBLEUR ET C++

void set_data( Simple object , int x )


{
object >data = x;
}
Fig. 7.14  Version C de Simple::set_data()

1
2
3

_set_data__6Simplei:
push ebp
mov
ebp, esp

; nom dcor

mov
mov
mov

5
6
7

eax, [ebp + 8] ; eax = pointeur vers l'objet (this)


edx, [ebp + 12] ; edx = paramtre entier
[eax], edx
; data est au dplacement 0

leave
ret

9
10

Fig. 7.15  Traduction de Simple::set_data( int ) par le compilateur

on agit. La ligne 6 stocke le paramtre


dans le double mot sur lequel pointe

Simple

dans

EDX

et la ligne 7 stocke

EDX

EAX. Il s'agit du membre data de l'objet

sur lequel on agit, qui, tant la seule donne de la classe, est stock

au dplacement 0 de la structure

Simple.

Exemple
Cette section utilise les ides de ce chapitre pour crer une classe C++
qui reprsente un entier non sign d'une taille arbitraire. Comme l'entier
peut faire n'importe quelle taille, il sera stock dans un tableau d'entiers
non signs (doubles mots). Il peut faire n'importe quelle taille en utilisant

11

l'allocation dynamique. Les doubles mots sont stocks dans l'ordre inverse

i.e. le double mot le moins signicatif est au dplacement 0). La Figure 7.16

Big_int12 . La taille d'un Big_int est med'unsigned utilis pour stocker les donnes.

montre la dnition de la classe


sure par la taille du tableau
La donne membre
membre

11

number_

size_

de la classe est aecte au dplacement 0 et le

est aect au dplacement 4.

Pourquoi ? Car les oprations d'addition commenceront ainsi toujours par le dbut du
tableau et avanceront.
12
Voyez le code source d'exemple pour obtenir le code complet de cet exemple. Le texte
ne se rfrera qu' certaines parties du code.

166

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

CHAPITRE 7.

class Big_int {
public :
/
Paramtres :
size

initial_value

STRUCTURES ET C++

taille de l ' entier exprime en nombre d'unsigned

int normaux

valeur initiale du Big_int sous forme d'un

unsigned int normal

explicit Big_int( size_t size ,


unsigned initial_value = 0);
/
Paramtres :
size
taille de l ' entier exprime en nombre d'unsigned

int normaux

initial_value valeur initiale du Big_int sous forme d'une chane

contenant une reprsentation hexadcimale de la

valeur .
/
Big_int( size_t
size ,
const char initial_value );
Big_int( const Big_int & big_int_to_copy);
~Big_int();
// renvoie la taille du Big_int (en termes d'unsigned int)
size_t size () const;

const Big_int & operator = ( const Big_int & big_int_to_copy);


friend Big_int operator + ( const Big_int & op1,
const Big_int & op2 );
friend Big_int operator ( const Big_int & op1,
const Big_int & op2);
friend bool operator == ( const Big_int & op1,
const Big_int & op2 );
friend bool operator < ( const Big_int & op1,
const Big_int & op2);
friend ostream & operator << ( ostream &
os,
const Big_int & op );
private :
size_t
size_ ;
// taille du tableau d'unsigned
unsigned number_; // pointeur vers un tableau d'unsigned contenant
la valeur
};
Fig. 7.16  Dnition de la classe Big_int

7.2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

ASSEMBLEUR ET C++

167

// prototypes des routines assembleur


extern "C" {
int add_big_ints( Big_int &
res ,
const Big_int & op1,
const Big_int & op2);
int sub_big_ints( Big_int &
res ,
const Big_int & op1,
const Big_int & op2);
}

inline Big_int operator + ( const Big_int & op1, const Big_int & op2)
{
Big_int result (op1. size ());
int res = add_big_ints(result , op1, op2);
if ( res == 1)
throw Big_int::Overow();
if ( res == 2)
throw Big_int::Size_mismatch();
return result ;
}
inline Big_int operator ( const Big_int & op1, const Big_int & op2)
{
Big_int result (op1. size ());
int res = sub_big_ints(result , op1, op2);
if ( res == 1)
throw Big_int::Overow();
if ( res == 2)
throw Big_int::Size_mismatch();
return result ;
}
Fig. 7.17  Code de l'Arithmtique sur la Classe Big_int

168

CHAPITRE 7.

STRUCTURES ET C++

Pour simplier l'exemple, seuls les objets ayant des tableaux de la mme
taille peuvent tre additionns entre eux.
La classe a trois constructeurs : le premier (ligne 9) initialise l'instance de
la classe en utilisant un entier non sign normal ; le second, (ligne 19) initalise
l'instance en utilisant une chane qui contient une valeur hexadcimale. Le
troisime constructeur (ligne 22) est le

constructeur par copie .

Cette explication se concentre sur la faon dont fonctionnent les oprateurs d'addition et de soustraction car c'est l que l'on utilise de l'assembleur.
La Figure 7.17 montre les parties du chier d'en-tte relatives ces oprateurs. Elles montrent comment les oprateurs sont paramtrs pour appeler
des routines assembleur. Comme des compilateurs dirents utilisent des
rgles de dcoration radicalement direntes pour les fonctions oprateur,
des fonctions oprateur inline sont utilises pour initialiser les appels aux
routines assembleur lies au format C. Cela les rend relativement simples
porter sur des compilateurs dirents et est aussi rapide qu'un appel direct.
Cette technique limine galement le besoin de soulever une exception depuis
l'assembleur !
Pourquoi l'assembleur n'est-il utilis qu'ici ? Souvenez vous que pour effectuer de l'arithmtique en prcision multiple, la retenue doit tre ajoute
au double mot signicatif suivant. Le C++ (et le C) ne permet pas au programmeur d'accder au drapeau de retenue du processeur. On ne pourrait
eectuer l'addition qu'en recalculant indpendamment en C++ la valeur du
drapeau de retenue et en l'ajoutant de faon conditionnelle au double mot
suivant. Il est beaucoup plus ecace d'crire le code en assembleur partir
duquel on peut accder au drapeau de retenue et utiliser l'instruction

ADC

qui ajoute automatiquement le drapeau de retenue.


Par concision, seule la routine assembleur

add_big_ints sera
big_math.asm) :

ici. Voici le code de cette routine (contenu dans

1
2
3
4

big_math.asm
segment .text
global add_big_ints, sub_big_ints
%define size_offset 0
%define number_offset 4

5
6
7
8

%define EXIT_OK 0
%define EXIT_OVERFLOW 1
%define EXIT_SIZE_MISMATCH 2

9
10
11
12
13

; Paramtres des routines add et sub


%define res ebp+8
%define op1 ebp+12
%define op2 ebp+16

explique

7.2.

ASSEMBLEUR ET C++

169

14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

add_big_ints:
push
ebp
mov
ebp, esp
push
ebx
push
esi
push
edi
;
; initialise esi pour pointer vers op1
;
edi pour pointer vers op2
;
ebx pour pointer vers res
mov
esi, [op1]
mov
edi, [op2]
mov
ebx, [res]
;
; s'assure que les 3 Big_int ont la mme taille
;
mov
eax, [esi + size_offset]
cmp
eax, [edi + size_offset]
jne
sizes_not_equal
; op1.size_ != op2.size_
cmp
eax, [ebx + size_offset]
jne
sizes_not_equal
; op1.size_ != res.size_

36
37
38
39
40
41
42
43
44
45
46

mov
ecx,
;
; initialise
;
esi =
;
edi =
;
ebx =
;
mov
ebx,
mov
esi,
mov
edi,

eax

; ecx = taille des Big_int

les registres pour qu'ils pointent vers leurs tableaux respectifs


op1.number_
op2.number_
res.number_
[ebx + number_offset]
[esi + number_offset]
[edi + number_offset]

47
48
49
50
51
52
53
54
55

clc
xor
edx, edx
;
; boucle d'addition
add_loop:
mov
eax, [edi+4*edx]
adc
eax, [esi+4*edx]
mov
[ebx + 4*edx], eax

; met le drapeau de retenue 0


; edx = 0

170

56
57

CHAPITRE 7.

inc
loop

edx
add_loop

STRUCTURES ET C++

; ne modifie pas le drapeau de retenue

58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73

jc
overflow
ok_done:
xor
eax, eax
jmp
done
overflow:
mov
eax, EXIT_OVERFLOW
jmp
done
sizes_not_equal:
mov
eax, EXIT_SIZE_MISMATCH
done:
pop
edi
pop
esi
pop
ebx
leave
ret
big_math.asm

; valeur de retour = EXIT_OK

Heureusement, la majorit de ce code devrait tre comprhensible pour


le lecteur maintenant. Les lignes 25 27 stockent les pointeurs vers les objets

Big_int

passs la fonction via des registres. Souvenez vous que les rf-

rences sont des pointeurs. Les lignes 31 35 vrient que les tailles des trois
objets sont les mmes (Notez que le dplacement de

size_

est ajout au

pointeur pour accder la donne membre). Les lignes 44 46 ajustent les


registres pour qu'ils pointent vers les tableaux utiliss par leurs objets respectifs au lieu des objets eux-mmes (l encore, le dplacement du membre

number_

est ajout au pointeur sur l'objet).

La boucle des lignes 52 57 additionne les entiers stocks dans les tableaux en additionnant le double mot le moins signicatif en premier, puis
les doubles mots suivants,

etc. L'addition doit tre eectue dans cet ordre

pour l'arithmtique en prcision tendue (voir Section 2.1.5). La ligne 59


vrie qu'il n'y a pas de dpassement de capacit, lors d'un dpassement
de capacit, le drapeau de retenue sera allum par la dernire addition du
double mot le plus signicatif. Comme les doubles mots du tableau sont stocks dans l'ordre little endian, la boucle commence au dbut du tableau et
avance jusqu' la n.
La Figure 7.18 montre un court exemple utilisant la classe
que les constantes de

Big_int

Big_int. Notez

doivent tre dclares explicitement comme

la ligne 16. C'est ncessaire pour deux raisons. Tout d'abord, il n'y a
pas de constructeur de conversion qui convertisse un entier non sign en

Big_int.

Ensuite, seuls les

Big_int

de mme taille peuvent tr addition-

ns. Cela rend la conversion problmatique puisqu'il serait dicile de savoir

7.2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

171

ASSEMBLEUR ET C++

#include "big_int.hpp"
#include <iostream>
using namespace std;
int main()
{
try {
Big_int b(5,"8000000000000a00b");
Big_int a(5,"80000000000010230");
Big_int c = a + b;
cout << a << " + " << b << " = " << c << endl;
for ( int i=0; i < 2; i++ ) {
c = c + a;
cout << "c = " << c << endl;
}
cout << "c1 = " << c Big_int(5,1) << endl;
Big_int d(5, "12345678");
cout << "d = " << d << endl;
cout << "c == d " << (c == d) << endl;
cout << "c > d " << (c > d) << endl;
}
catch( const char str ) {
cerr << "Caught : " << str << endl;
}
catch( Big_int::Overow ) {
cerr << "Dpassement de capacit " << endl;
}
catch( Big_int::Size_mismatch ) {
cerr << "Non concordance de taille" << endl;
}
return 0;
}
Fig. 7.18 

Utilisation Simple de

Big_int

172

CHAPITRE 7.

STRUCTURES ET C++

vers quelle taille convertir. Une implmentation plus sophistique de la classe


permettrait d'additionner n'importe quelle taille avec n'importe quelle autre.
L'auteur ne voulait pas compliquer inutilement cet exemple en l'implmentant ici (cependant, le lecteur est encourag le faire).

7.2.5 Hritage et Polymorphisme


hritage

L'

permet une classe d'hriter des donnes et des mthodes

d'une autre. Par exemple, considrons le code de la Figure 7.19. Il montre


deux classes,

et

B,

o la classe

hrite de

A.

La sortie du programme est :

Taille de a : 4 Dplacement de ad : 0
Taille de b : 8 Dplacement de ad: 0 Dplacement de bd: 4
A::m()
A::m()
Notez que les membres

ad

des deux classes (B l'hrite de

A)

sont au mme

dplacement. C'est important puisque l'on peut passer la fonction


un pointeur vers un objet

soit

A soit un pointeur vers un objet de n'importe quel

i.e. qui hrite de) A. La Figure 7.20 montre le code assembleur


(dit) de la fonction (gnr par gcc ).
type driv de (

m de A a t produite la fois par


a et l'objet b. D'aprs l'assembleur, on peut voir que l'appel A::m()

Notez que la sortie de la mthode


l'objet

est cod en dur dans la fonction. Dans le cadre d'une vraie programmation
oriente objet, la mthode appele devrait dpendre du type d'objet pass

polymorphisme. Le C++ dsactive cette


fonctionnalit par dfaut. On utilise le mot-cl virtual pour l'activer. La Fi-

la fonction. On appelle cela le

gure 7.21 montre comment les deux classes seraient modies. Rien dans le
restet du code n'a besoin d'tre chang. Le polymorphisme peut tre implment de beaucoup de manires. Malheureusement, l'implmentation de

gcc

est en transition au moment d'crire ces lignes et devient beaucoup plus

complique que l'implmentation initiale. An de simplier cette explication,


l'auteur ne couvrira que l'implmentation du polymorphisme que les compilateurs Microsoft et Borland utilisent. Cette implmentation n'a pas chang
depuis des annes et ne changera probablement pas dans un futur proche.
Avec ces changements, la sortie du programme change :

Size of a: 8 Dplacement de ad : 4
Size of b: 12 Dplacement de ad : 4 Dplacement de bd : 8
A::m()
B::m()
Maintenant, le second appel
passe un objet

B.

appelle la mthode

B::m()

car on lui

Ce n'est pas le seul changement cependant. La taille d'un

7.2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

173

ASSEMBLEUR ET C++

#include <cstddef>
#include <iostream>
using namespace std;
class A {
public :
void __cdecl m() { cout << "A::m()" << endl; }
int ad;
};
class B : public A {
public :
void __cdecl m() { cout << "B::m()" << endl; }
int bd;
};
void f ( A p )
{
p>ad = 5;
p>m();
}
int main()
{
A a;
B b;
cout << "Taille de a : " << sizeof(a)
<< " Dplacement de ad : " << osetof(A,ad) << endl;
cout << "Taille de b : " << sizeof(b)
<< " Dplacement de ad : " << osetof(B,ad)
<< " Dplacement de bd : " << osetof(B,bd) << endl;
f(&a);
f(&b);
return 0;
}
Fig. 7.19 

Hritage Simple

174

1
2
3
4
5
6
7
8
9
10
11

_f__FP1A:
push
mov
mov
mov
mov
push
call
add
leave
ret

CHAPITRE 7.

ebp
ebp, esp
eax, [ebp+8]
dword [eax], 5
eax, [ebp+8]
eax
_m__1A
esp, 4

STRUCTURES ET C++

; nom de fonction dcor


; eax pointe sur l'objet
; utilisation du dplacement 0 pour ad
; passage de l'adresse de l'objet A::m()
; nom dcor de la mthode A::m()

Fig. 7.20  Code Assembleur pour un Hritage Simple

1
2
3
4
5
6
7
8
9
10
11

class A {
public :
virtual void __cdecl m() { cout << "A::m()" << endl; }
int ad;
};
class B : public A {
public :
virtual void __cdecl m() { cout << "B::m()" << endl; }
int bd;
};
Fig. 7.21 

Hritage Polymorphique

7.2.

1
2
3

175

ASSEMBLEUR ET C++

?f@@YAXPAVA@@@Z:
push ebp
mov
ebp, esp

4
5
6

mov
mov

eax, [ebp+8]
dword [eax+4], 5 ; p->ad = 5;

mov
mov
mov
push
call
add

ecx, [ebp + 8]
edx, [ecx]
eax, [ebp + 8]
eax
dword [edx]
esp, 4

pop
ret

ebp

7
8
9
10
11
12
13

;
;
;
;
;
;

ecx = p
edx = pointeur sur la vtable
eax = p
empile le pointeur "this"
appelle la premire fonction de la vtable
nettoie la pile

14
15
16

Fig. 7.22  Code Assembleur de la Fonction

vaut maintenant 8 (et 12 pour

B).

f()

De plus, le dplacement de

ad

vaut

maintenant 4, plus 0. Qu'y a-t-il au dplacement 0 ? La rponse cette


question est lie la faon dont est implment le polymorphisme.
Une classe C++ qui a une (ou plusieurs) mthode(s) virutelle(s) a un
champ cach qui est un pointeur vers un tableau de pointeurs sur des mthodes

13 . Cette table est souvent appele la

vtable.

Pour les classes

et

ce pointeur est stock au dplacement 0. Les compilateurs Windows placent


toujours ce pointeur au dbut de la classe au sommet de l'arbre d'hritage.
En regardant le code assembleur (Figure 7.22) gnr pour la fonction

(de

la Figure 7.19) dans la version du programme avec les mthodes virtuelles,


on peut voir que l'appel la mthode

ne se fait pas via une tiquette. La

ligne 9 trouve l'adresse de la vtable de l'objet. L'adresse de l'objet est place


sur la pile ligne 11. La ligne 12 appelle la mthode virtuelle en se branchant

14 . Cet appel n'utilise pas d'tiquette, il

la premire adresse dans la vtable

se branche l'adresse du code sur lequel pointe


exemple de

EDX.

Ce type d'appel est un

liaison tardive (late binding). La liaison tardive repousse le choix

de la mthode appeler au moment de l'excution du code. Cela permet

13
Pour les classes sans mthode virtuelle, les compilateurs C++ rendent toujours la
classe compatible avec une structure C normale qui aurait les mmes donnes membres.
14
Bien sr, la valeur est dj dans le registre ECX. Elle y a t place la ligne 8 et
le ligne 10 pourrait tre supprime et la ligne suivante change de faon empiler ECX.
Le code n'est pas trs ecace car il a t gnr en dsactivant les optimisations du
compilateur.

176

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

class B : public A { // B hrite du m2() de A


public :
virtual void __cdecl m1() { cout << "B::m1()" << endl; }
int bd;
};
/ ache la vtable de l ' objet fourni /
void print_vtable ( A pa )
{
// p voit pa comme un tableau de doubles mots
unsigned p = reinterpret_cast<unsigned >(pa);
// vt voit la vtable comme un tableau de pointeurs
void vt = reinterpret_cast<void >(p[0]);
cout << hex << "adresse de la vtable = " << vt << endl;
for ( int i=0; i < 2; i++ )
cout << "dword " << i << " : " << vt[i] << endl;
// appelle les fonctions virtuelle d'une faon ABSOLUMENT non portable
void (m1func_pointer)(A ); // variable pointeur de fonction
m1func_pointer = reinterpret_cast<void ()(A)>(vt[0]);
m1func_pointer(pa);
// appelle m1 via le pointeur de fonction

24
25
26
27
28
29
30
32
33
34
35
36
37
38
39
40
41

STRUCTURES ET C++

class A {
public :
virtual void __cdecl m1() { cout << "A::m1()" << endl; }
virtual void __cdecl m2() { cout << "A::m2()" << endl; }
int ad;
};

23

31

CHAPITRE 7.

void (m2func_pointer)(A ); // variable pointeur de fonction


m2func_pointer = reinterpret_cast<void ()(A)>(vt[1]);
m2func_pointer(pa);
// appelle m2 via le pointeur de fonction

int main()
{
A a; B b1; B b2;
cout << "a: " << endl; print_vtable(&a);
cout << "b1: " << endl; print_vtable(&b1);
cout << "b2: " << endl; print_vtable(&b2);
return 0;
}
Fig. 7.23 

Exemple plus compliqu

7.2.

177

ASSEMBLEUR ET C++

0 vtablep s

0- &B::m1()

ad

4 &A::m2()

bd

vtable

b1
Fig. 7.24  Reprsentation interne de

b1

d'appeler la mthode correspondant l'objet. Le cas normal (Figure 7.20)


code en dur un appel une certaine mthode et est appel

liaison prcoce

(early binding), car la mthode est lie au moment de la compilation.


Le lecteur attentif se demandera pourquoi les mthodes de classe de la
Figure 7.21 sont explicitement dclares pour utiliser la convention d'appel C
en utilisant le mot-cl

__cdecl. Par dfaut, Microsoft utilise une convention

dirente de la convention C standard pour les mthodes de classe C++. Il


passe le pointeur sur l'objet sur lequel agit la mthode via le registre

ECX au

lieu d'utiliser la pile. La pile est toujours utilise pour les autres paramtres
explicites de la mthode. Le modicateur

__cdecl

demande l'utilisation de

la convention d'appel C standard. Borland C++ utilise la convention d'appel


C par dfaut.
Observons maintenant un exemple lgrement plus compliqu (Figure 7.23).
Dans celui-l, les classes

et

ont chacune deux mthodes :

venez vous que comme la classe

hrite de la mthode de classe de


jet

m1

et

m2. Soum2, elle

ne dnit pas sa propre mthode

A.

La Figure 7.24 montre comment l'ob-

apparat en mmoire. La Figure 7.25 montre la sortie du programme.

Tout d'abord, regardons l'adresse de la vtable de chaque objet. Les adresses


des deux objets

sont les mmes, ils partagent donc la mme vtable. Une

vtable appartient une classe pas un objet (comme une donne membre

static).

Ensuite, regardons les adresses dans les vtables. En regardant la

sortie assembleur, on peut dterminer que le pointeur sur la mthode


est au dplacement 0 (ou dword 0) et celui sur
(dword 1). Les pointeurs sur la mthode
des classes

et

car la classe

m2

m2

m1

est au dplacement 4

sont les mmes pour les vtables

hrite de la mthode

m2

de la classe

A.

Les lignes 25 32 montrent comment l'on pourrait appeler une fonction


virtuelle en lisant son adresse depuis la vtable de l'objet

15
gcc

15 . L'adresse de la

Souvenez vous, ce code ne fonctionne qu'avec les compilateurs MS et Borland, pas

178

CHAPITRE 7.

STRUCTURES ET C++

a:
Adresse de la vtable = 004120E8
dword 0: 00401320
dword 1: 00401350
A::m1()
A::m2()
b1:
Adresse de la vtable = 004120F0
dword 0: 004013A0
dword 1: 00401350
B::m1()
A::m2()
b2:
Adresse de la vtable = 004120F0
dword 0: 004013A0
dword 1: 00401350
B::m1()
A::m2()
Fig. 7.25  Sortie du programme de la Figure 7.23

mthode est stocke dans un pointeur de fonction de type C avec un pointeur

this

explicite. D'aprs la sortie de la Figure 7.25, on peut voir comment cela

fonctionne. Cependant, s'il vous plat, n'crivez

pas

de code de ce genre ! Il

n'est utilis que pour illustrer le fait que les mthodes virtuelles utilisent la
vtable.
Il y a plusieurs leons pratiques tirer de cela. Un fait important est
qu'il faut tre trs attentif lorsque l'on crit ou que l'on lit des variables de
type classe depuis un chier binaire. On ne peut pas utiliser simplement une
lecture ou une criture binaire car cela lirait ou crirait le pointeur vtable
depuis le chier ! C'est un pointeur sur l'endroit o la vtable rside dans
la mmoire du programme et il varie d'un programme l'autre. La mme
chose peut arriver avec les structures en C, mais en C, les structures n'ont
des pointeurs que si le programmeur en dnit explicitement. Il n'y a pas de
pointeurs explicites dclars dans les classes

ou

B.

Une fois encore, il est ncessaire de raliser que des compilateurs dirents
implmentent les mthodes virtuelles diremment. Sous Windows, les objets
de la classe COM (Component Object Model) utilisent des vtables pour
implmenter les interfaces COM

16 . Seuls les compilateurs qui implmentent

16
Les classes COM utilisent galement la convention d'appel __stdcall, pas la convention C standard.

7.2.

179

ASSEMBLEUR ET C++

les vtables des mthodes virtuelles comme le fait Microsoft peuvent crer des
classes COM. C'est pourquoi Borland utilise la mme implmentation que
Microsoft et une des raisons pour lesquelles

gcc ne peut pas tre utilis pour

crer des classes COM.


Le code des mthodes virtuelles est identique celui des mthodes nonvirtuelles. Seul le code d'appel est dirent. Si le compilateur peut tre absolument sr de la mthode virtuelle qui sera appele, il peut ignorer la vtable

p.e., en utilisant la liaison prcoce).

et appeler la mthode directement (

7.2.6 Autres fonctionnalits C++


Le fonctionnement des autres fonctionnalits du C++ (

p.e., le RunTime

Type Information, la gestion des exceptions et l'hritage multiple) dpasse

The Annotated C++


Reference Manual de Ellis et Stroustrup et The Design and Evolution of
C++ de Stroustrup constituent un bon point de dpart.
le cadre de ce livre. Si le lecteur veut aller plus loin,

180

CHAPITRE 7.

STRUCTURES ET C++

Annexe A

Instructions 80x86
A.1

Instructions hors Virgule Flottante

Cette section liste et dcrit les actions et les formats des instructions hors
virgule ottante de la famille de processeurs Intel 80x86.
Les formats utilisent les abbrviations suivantes :
R

registre gnral

R8

registre 8 bits

R16

registre 16 bits

R32

registre 32 bits

SR

registre de segment

mmoire

M8

octet

M16

mot

M32

double mot

valeur immdiate

Elles peuvent tre combines pour les instructions plusieurs oprandes. Par
exemple, le format

R,R

signie que l'instruction prend deux oprandes de

type registre. Beaucoup d'instructions deux oprandes acceptent les mmes

O2 est utilise pour reprsenter ces oprandes :


R,R R,M R,I M,R M,I. Si un registre 8 bits ou un octet en mmoire peuvent
tre utiliss comme oprande, l'abbrviation R/M8 est utilise.

oprandes. L'abbrviation

Le tableau montre galement comment les dirents bits du registre


FLAGS sont modis par chaque instruction. Si la colonne est vide, le bit
correspondant n'est pas modi du tout. Si le bit est toujours positionn
une valeur particulire, un 1 ou un 0 gure dans la colonne. Si le bit est po-

C
un ?

sitionn une valeur qui dpend des oprandes de l'instruction, un

gure

dans la colonne. Enn, si le bit est modi de faon indnie,

gure

dans la colonne. Comme les seules instructions qui changent le drapeau de


direction sont

CLD

et

STD,

il ne gure pas parmi les colonnes FLAGS.


181

182

ANNEXE A.

Nom

ADC
ADD
AND
BSWAP
CALL
CBW
CDQ

Description

INSTRUCTIONS 80X86

Formats

Ajout avec Retenue

Flags
O S Z A P C

O2

Addition entire

O2

ET niveau bit

O2

Echange d'Octets

R32

Appel de Routine

R M I

Conversion Octet-Mot
Conversion

DMot-

QMot

CLC
CLD
CMC
CMP
CMPSB
CMPSW
CMPSD

Eteindre la retenue

Eteindre la direction
Inverser la retenue

C
C

Comparaison d'Octets

Comparaison Entire

O2

Comparaison de Mots

Comparaison

R M

Division non Signe

R M

Cration de cadre de

I,0

de

DMots

CWD

Conversion Mot-DMot

CWDE

Conversion Mot-DMot

DEC

Decrmentation d'En-

dans DX:AX
dans EAX
tier

DIV
ENTER

pile

IDIV
IMUL

Division Signe

R M

Multiplication Signe

R16,R/M16
R32,R/M32
R16,I R32,I
R16,R/M16,I
R32,R/M32,I

INC

Incrmentation

d'En-

R M

tier

INT

Gnrer une Interrup-

tion

JA
JAE

Saut si Au-dessus

Saut si Au-Dessus ou

Egal

JB

Saut si En-Dessous

A.1.

INSTRUCTIONS HORS VIRGULE FLOTTANTE

JBE

Nom

Description

Formats

Saut si En-Dessous ou

Egal

JC
JCXZ
JE
JG
JGE

Saut si Retenue

Saut si CX = 0

Saut si Egal

Saut si Suprieur

Saut

si

Suprieur

ou

Egal

JL
JLE

Saut si Infrieur
Saut

si

Infrieur

ou

Egal

JMP
JNA
JNAE

Saut Inconditionnel

R M I

Saut si Non Au-Dessus

Saut si Non Au-Dessus

ou Egal

JNB

Saut

si

Non

En-

Non

En-

Saut si pas de Retenue

Saut si Non Egal

Saut si Non Suprieur

Saut si Non Suprieur

Dessous

JNBE

Saut

si

Dessous ou Egal

JNC
JNE
JNG
JNGE

ou Egal

JNL
JNLE

Saut si Non Infrieur

Saut si Non Infrieur

ou Egal

JNO
JNS
JNZ
JO
JPE
JPO
JS
JZ
LAHF

Saut si Non Overow

Saut si Non Signe

Saut si Non Zro

Saut si Overow

Saut si Pair

Saut si Impair

Saut si Signe

Saut si Zro
Charge

FLAGS

I
dans

AH

LEA

Charge l'Adresse Eective

R32,M

183

Flags
O S Z A P C

184

ANNEXE A.

Nom

LEAVE

Description
Quitter

le

INSTRUCTIONS 80X86

Formats

Cadre

Flags
O S Z A P C

de

Pile

LODSB
LODSW
LODSD

Charger un Octet
Charger un Mot
Charger

un

Double

Mot

LOOP
LOOPE/LOOPZ
LOOPNE/LOOPNZ
MOV

Boucler

Boucler si Egal

Boucler si Non Egal

Dplacement

O2

de

don-

nes

SR,R/M16
R/M16,SR

MOVSB
MOVSW
MOVSD

Dplacement d'Octet
Dplacement de Mot
Dplacement

de

Double Mot

MOVSX

Dplacement Sign

R16,R/M8
R32,R/M8
R32,R/M16

MOVZX

Dplacement

Non

Si-

gn

R16,R/M8
R32,R/M8
R32,R/M16

MUL

Multiplication Non Si-

R M

R M

gne

NEG
NOP
NOT
OR
POP

Inverser
Pas d'Opration
Complment 1

R M

OU niveau bit

O2

Retirer de la pile

R/M16
R/M32

POPA
POPF

Retirer FLAGS de la

PUSH

Empiler

Tout retirer de la pile


pile
R/M16
R/M32 I

PUSHA
PUSHF
RCL

Tout Empiler
Empiler FLAGS
Rotation

avec Retenue

Gauche

R/M,I
R/M,CL

A.1.

185

INSTRUCTIONS HORS VIRGULE FLOTTANTE

RCR

Nom

REP
REPE/REPZ
REPNE/REPNZ
RET
ROL

Description

Formats

Rotation Droite avec

R/M,I

Retenue

R/M,CL

Flags
O S Z A P C
C

Rpter
Rpter Si Egal
Rpter Si Non Egal
Retour
Rotation Gauche

R/M,I
R/M,CL

ROR

Rotation Droite

SAHF

Copie

SAL

Dcalage Arithmtique

R/M,I

Gauche

R/M, CL

Soustraire Avec Rete-

O2

R/M,I
R/M,CL

AH

dans

FLAGS

SBB

C
C

Recherche d'Octet

Recherche de Mot

Recherche de Double-

nue

SCASB
SCASW
SCASD

Mot

SETA
SETAE

Allumer Si Au-Dessus

R/M8

Allumer Si Au-Dessus

R/M8

ou Egal

SETB
SETBE

Allumer Si En-Dessous

R/M8

Allumer Si En-Dessous

R/M8

ou Egal

SETC
SETE
SETG
SETGE

Allumer Si Retenue

R/M8

Allumer Si Egal

R/M8

Allumer Si Plus Grand

R/M8

Allumer Si Plus Grand

R/M8

ou Egal

SETL
SETLE

Allumer Si Plus Petit

R/M8

Allumer Si Plus Petit

R/M8

ou Egal

SETNA

Allumer

Si

Non

Au

R/M8

Au-

R/M8

En-

R/M8

Dessus

SETNAE

Allumer

Si

Non

Dessus ou Egal

SETNB

Allumer
Dessous

Si

Non

186

ANNEXE A.

Nom

SETNBE

Description
Allumer

Si

Non

INSTRUCTIONS 80X86

Formats
En-

R/M8

Allumer Si Pas de Re-

R/M8

Flags
O S Z A P C

Dessous ou Egal

SETNC

tenue

SETNE
SETNG

Allumer Si Non Egal

R/M8

Allumer Si Non Plus

R/M8

Grand

SETNGE

Allumer Si Non Plus

R/M8

Grand ou Egal

SETNL

Allumer Si Non Inf-

R/M8

rieur

SETNLE

Allumer Si Non Inf-

R/M8

rieur ou Egal

SETNO

Allumer Si Non Over-

R/M8

ow

SETNS
SETNZ
SETO
SETPE
SETPO

Allumer Si Non Signe

R/M8

Allumer Si Non Zro

R/M8

Allumer Si Overow

R/M8

Allumer Si Parit Paire

R/M8

Allumer Si Parit Im-

R/M8

paire

SETS
SETZ
SAR
SHR

Allumer Si Signe

R/M8

Allumer Si Zro

R/M8

Dcalage Arithmtique

R/M,I

Droite

R/M, CL

Dcalage

Logique

Droite

SHL

Dcalage

R/M,I

R/M, CL
Logique

Gauche

STC
STD

R/M,I

R/M, CL

Allumer la Retenue

Allumer le Drapeau de
Direction

STOSB
STOSW
STOSD
SUB
TEST

Soustraction

O2

Comparaison Logique

R/M,R

XCHG

Echange

R/M,R

Stocker l'Octet
Stocker le Mot
Stocker le Double-Mot

R/M,I
R,R/M

A.1.

XOR

187

INSTRUCTIONS HORS VIRGULE FLOTTANTE

Nom

Description
Ou Exclusif Niveau Bit

Formats
O2

Flags
O S Z A P C
0

188

ANNEXE A.

A.2

INSTRUCTIONS 80X86

Instruction en Virgule Flottante

Dans cette section, la plupart des instructions du coprocesseur mathmatique du 80x86 son dcrites. La section description dcrit brivement l'opration eectue par l'instruction. Pour conomiser de la place, il n'est pas
prcis si l'instruction dcale la pile ou non.
La colonne format indique le type d'oprande pouvant tre utilis avec
chaque instruction. Les abbrviations suivantes sont utilises :

ST

Registre du coprocesseur

Nombre simple prcision en mmoire

Nombre double prcision en mmoire

Nombre prcision tendue en mmoire

I16

Mot entier en mmoire

I32

Double mot entier en mmoire

I64

Quadruple mot entier en mmoire

Les instructions ncessitant un Pentium Pro ou ou suprieur sont signa-

les par une astrisque( ).

Instruction

FABS
FADD src
FADD dest, ST0
FADDP dest [,ST0]
FCHS
FCOM src
FCOMP src
FCOMPP src
FCOMI src
FCOMIP src
FDIV src
FDIV dest, ST0
FDIVP dest [,ST0]
FDIVR src
FDIVR dest, ST0
FDIVRP dest [,ST0]
FFREE dest
FIADD src
FICOM src
FICOMP src
FIDIV src
FIDIVR src

Description
ST0 = |ST0|
ST0 += src
dest += STO
dest += ST0
ST0 = ST0
Compare ST0 et src
Compare ST0 et src
Compare ST0 et ST1
Compare dans FLAGS
Compare dans FLAGS

ST0 /= src
dest /= STO
dest /= ST0
ST0 = src /ST0
dest = ST0/dest
dest = ST0/dest
Marquer comme vide

ST0 += src
Compare ST0 et src
Compare ST0 et src
STO /= src
STO = src /ST0

Format
n
STn
STn

ST

n
n

F D

ST

F D

ST

F D

n
STn
STn
STn
STn
STn
STn
STn
STn
ST

F D

F D

I16 I32
I16 I32
I16 I32
I16 I32
I16 I32

A.2.

189

INSTRUCTION EN VIRGULE FLOTTANTE

Instruction

FILD src
FIMUL src
FINIT
FIST dest
FISTP dest
FISUB src
FISUBR src
FLD src
FLD1
FLDCW src
FLDPI
FLDZ
FMUL src
FMUL dest, ST0
FMULP dest [,ST0]
FRNDINT
FSCALE
FSQRT
FST dest
FSTP dest
FSTCW dest
FSTSW dest
FSUB src
FSUB dest, ST0
FSUBP dest [,ST0]
FSUBR src
FSUBR dest, ST0
FSUBP dest [,ST0]
FTST
FXCH dest

Place

src

Description
sur la Pile

ST0 *= src

Format
I16 I32 I64
I16 I32

Initialise le Coprocesseur

ST0
Stocke ST0
ST0 -= src
ST0 = src - ST0
Stocke

Place

src

sur la Pile

I16 I32
I16 I32 I64
I16 I32
I16 I32

ST

F D E

Place 1.0 sur la Pile


Charge le Registre du Mot de Contrle
Place

I16

sur la Pile

Place 0.0 sur la Pile

ST0 *= src
dest *= STO
dest *= ST0
Arrondir ST0
bST1c
ST0 = ST0
2
ST0 = STO
Stocke ST0
Stocke ST0

n
n
STn
ST

F D

ST

n
n

ST

F D

ST

F D E

Stocke le Registre du Mot de Contrle

I16

Stocke le Registre du Mot de Statut

I16 AX

ST0 -= src
dest -= STO
dest -= ST0
ST0 = src -ST0
dest = ST0-dest
dest = ST0-dest
Compare ST0 avec 0.0
Echange ST0 et dest

n
n
STn
STn
STn
STn
ST

F D

ST

ST

F D

Index
$, 72

rfrences, 161

dition de liens, 24

virtual, 172
vtable, 175179

ADC, 39, 56

CALL, 7374

ADD, 13, 39

CBW, 33

adressage indirect, 6970

CDQ, 33

tableaux, 102107

CLC, 39

AND, 52

CLD, 110

array1.asm, 103107

CMP, 40

assembleur, 12

CMPSB, 113, 114


CMPSD, 113, 114

big et little endian, 6062

CMPSW, 113, 114

invert_endian, 62

COM, 178

binaire, 12

commentaire, 13

addition, 2

compilateur, 6, 12

boucle do while, 4546

Borland, 23, 24

boucle while, 45

DJGPP, 23, 24

branchement conditionnel, 4144

gcc, 23

BSWAP, 62

__attribute__, 88, 151, 152,

BYTE, 17

155, 156
Microsoft, 23

C++, 157179

pragma pack, 152, 155, 156

dition de liens avec respect des

Watcom, 88

types, 158

complment deux, 3032

classes, 163179

arithmtique, 3539

constructeur par copie, 168

compter les bits, 6367

dcoration de noms, 157160


exemple Big_int, 165172

mthode deux, 6465

extern "C", 159160

mthode trois, 6667

fonctions inline, 161163

mthode une, 63
convention d'appel, 69, 7481, 88

hritage, 172179

89

liaison prcoce, 177


liaison tardive, 175

__cdecl, 89

member functions, voir methods

__stdcall, 89

polymorphisme, 172179

C, 22, 76, 8589


190

191

INDEX

tiquettes, 86

dump_stack, 19

paramtres, 87

print_char, 18

registres, 86

print_int, 18

valeurs de retour, 88

print_nl, 18

Pascal, 76

print_string, 18

registre, 89

read_char, 18

standard call, 89

read_int, 18

stdcall, 76, 88, 178


coprocesseur virgule ottante, 129
145
addition et soustraction, 131
133
charger et stocker des donnes,
130131

endian, 26
entiers, 2940
bit de signe, 29, 32
comparaisons, 40
division, 3637
extension de signe, 3235
multiplication, 3536

comparaisons, 134135

non signs, 29, 40

matriel, 129130

prcision tendue, 39

multiplication et division, 133

reprsentation, 2935

134
CWD, 33

complment deux, 3032


grandeur signe, 29

CWDE, 33

representation

dbogage, 1819

signs, 2932, 40

one's complement, 30
dcimal, 1

excution spculative, 55

DEC, 14
directive, 1416

FABS, 136

%dene, 14

FADD, 132

FADDP, 132

X, 15, 99

DD, 16

FCHS, 136

donnes, 1516

FCOM, 134

DQ, 16

FCOMI, 135

equ, 14

FCOMIP, 135, 146

extern, 82

FCOMP, 134

global, 22, 82, 85

FCOMPP, 134

RES

FDIV, 134

TIMES, 16, 99

FDIVP, 134

X, 15, 99

DIV, 36, 50

FDIVR, 134

DWORD, 17

FDIVRP, 134
FFREE, 131

E/S, 1719
bibliothque asm_io, 1719

FIADD, 132
chier listing, 25

dump_math, 19

chier squelette, 26

dump_mem, 18

FICOM, 134

dump_regs, 18

FICOMP, 134

192

INDEX

FIDIV, 134

JLE, 43

FIDIVR, 134

JMP, 41

FILD, 130

JNC, 42

FIST, 131

JNE, 43

FISUB, 133

JNG, 43

FISUBR, 133

JNGE, 43

FLD, 130

JNL, 43

FLD1, 130

JNLE, 43

FLDCW, 131

JNO, 42

FLDZ, 130

JNP, 42

FMUL, 133

JNS, 42

FMULP, 133

JNZ, 42

FSCALE, 136, 147

JO, 42

FSQRT, 136

JP, 42

FST, 131

JS, 42

FSTCW, 131

JZ, 42

FSTP, 131
FSTSW, 135

label, 1517

FSUB, 133

LAHF, 135

FSUBP, 133

lanceur C, 20

FSUBR, 133

langage assembleur, 1213

FSUBRP, 133

langage machine, 5, 11

FTST, 134

LEA, 8788, 107

FXCH, 131

localit, 149
LODSB, 111

gas, 163
hexadcimal, 34
horloge, 6

LODSD, 111
LODSW, 111
LOOP, 44
LOOPE, 44

IDIV, 37

LOOPNE, 44

immdiat, 13

LOOPNZ, 44

IMUL, 3536

LOOPZ, 44

INC, 14
instruction if, 4445
instructions de chanes, 110120
interfaage avec le C, 8593
interruptions, 11

mmoire, 45
pages, 10
segments, 9, 10
virtuelle, 10
mthodes, 163

JC, 42

math.asm, 3739

JE, 43

memory.asm, 116120

JG, 43

mnmonique, 12

JGE, 43

mode protg

JL, 43

16 bits, 910

193

INDEX

32 bits, 10

rcursivit, 9396

mode rel, 9

RCL, 51

mot, 8

RCR, 51

MOV, 13

read.asm, 139141

MOVSB, 112

register

MOVSD, 112
MOVSW, 112
MOVSX, 33

FLAGS
SF, 40
registre, 5, 78

MOVZX, 33

32 bits, 8

MUL, 3536, 50, 107

EDI, 111

NASM, 12
NEG, 37, 58
NOT, 54

EDX:EAX, 33, 36, 37, 39, 88


EFLAGS, 8
EIP, 8
ESI, 111
FLAGS, 8, 40

octet, 4
oprations sur les bits
assembleur, 5455
C, 5860
dcalages, 4952
dcalages arithmtiques, 50
51
dcalages logiques, 4950
rotations, 51
ET, 52
NOT, 53
OU, 53
XOR, 53
opcode, 12
OR, 53
pile, 7281

CF, 40
DF, 110
OF, 40
PF, 42
ZF, 40
index, 7
IP, 8
pointeur de base, 7, 8
pointeur de pile, 7, 8
segment, 7, 8, 112
REP, 113
REPE, 115
REPNE, 115
REPNZ, voir REPNE
REPZ, voir REPE
RET, 7374, 76

paramtres, 7477

ROL, 51

variables locales, 8081, 8788

ROR, 51

prdiction de branchement, 5556


prime.asm, 4648
prime2.asm, 141145
processeur, 57
80x86, 6
programmes multi-modules, 8285

SAHF, 135
SAL, 50
SAR, 50
SBB, 39
SCASB, 113, 114
SCASD, 113, 114

quad.asm, 136139

SCASW, 113, 114

quadruplet, 4

SCSI, 153155

QWORD, 17

segment bss, 22

194

INDEX

segment de code, 22

reprsentation, 121126

segment de donnes, 22

dnormaliss, 125126

SET

double prcision, 126

xx, 56

SETG, 58

IEEE, 123126

SHL, 49

simple prcision, 124126

SHR, 49

un masqu, 124

sous-programme, 7098
appel, 7381
rentrant, 9394
STD, 110
storage types
volatile, 98
STOSB, 111
STOSD, 111
structures, 149157
alignement, 150152
champs de bits, 153155
osetof(), 150
SUB, 14, 39
subroutine, voir subprogram
tableaux, 99120
accs, 100107
dnition, 99100
statique, 99
variable locale, 100
multidimensionnels, 107110
deux dimensions, 107108
paramtres, 110
TCP/IP, 61
TEST, 54
text segment, voir code segment
TWORD, 17
types de stockage
automatic, 96
global, 96
register, 96
static, 96
UNICODE, 61
virgule ottante, 121145
arithmtique, 126129

WORD, 17
XCHG, 62
XOR, 53

Vous aimerez peut-être aussi