Section courante

A propos

Section administrative du site

Sujets avancés

Cette page vous donne un aperçu de QuickPascal. Vous pouvez accomplir la plupart des tâches de programmation standard en utilisant les techniques présentées dans les autres pages. Cette page vous aide à gérer des situations particulières, telles que le manque de mémoire dynamique, l'analyse des formats de données internes et la liaison au langage d'assemblage.

Les opérateurs au niveau du bit présentent un intérêt particulier pour les programmeurs en langage assembleur, mais sont utiles même si vous n'utilisez pas le langage assembleur. Vous pouvez utiliser les opérateurs au niveau du bit pour masquer des bits dans un entier, manipuler des bits individuels ou tester une variable pour voir quels bits sont activés.

Après avoir présenté les opérateurs au niveau du bit, la page montre une image générale de la façon dont QuickPascal organise la mémoire. Ensuite, la page illustre les formats de données internes et explique comment établir un lien avec le langage d'assemblage.

Les opérateurs au niveau du bit

Vous pouvez accéder aux bits et les manipuler à l'aide des opérateurs booléens standard et des opérateurs de décalage.

Les opérateurs logiques NOT, AND, OR et XOR fonctionnent comme des opérateurs au niveau du bit lorsque vous les utilisez avec des types entiers. Les opérations au niveau du bit prennent deux éléments de données de même taille et comparent chaque bit d'un opérande au bit correspondant de l'autre. Par exemple, considérez l'énoncé suivant :

  1. Result:=$FF00 and $9055;

Le QuickPascal implémente cette instruction en comparant chaque bit de la constante $FF00 à chaque bit correspondant de la constante $9055. L'opérateur AND définit un bit du résultat sur 1, si et seulement si les bits correspondants dans les deux opérandes ont la valeur 1 :

           $FF00 = 1111 1111 0000 0000
AND        $90FF = 1001 0000 1111 1111
Résultat   $9000 = 1001 0000 0000 0000

Le résultat de l'opération est $9000. Dans l'exemple ci-dessus, l'utilisation de l'opérateur AND avec la constante $FF00 masque en fait les 8 bits de poids faible d'un entier de 16 bits. Vous pouvez créer d'autres constantes pour masquer de manière sélective toute combinaison de bits. Par exemple, vous pouvez utiliser l'opérateur AND pour tester si une valeur est un multiple de quatre en masquant tous les bits sauf les deux plus bas et en déterminant si le résultat est zéro :

  1. If(x AND $0003 = 0)Then WriteLn('x est multiple de 4.');

Inversement, vous pouvez utiliser l'opérateur OR pour définir des bits spécifiques sur 1. Tous les opérateurs au niveau du bit fonctionnent de la même manière. La liste suivante montre le fonctionnement de chaque opérateur :

Opérateur Met un bit à 1 si :
NOT Le bit correspondant dans l'opérande est 0. (Cet opérateur ne prend qu'un seul opérande.)
AND Les deux bits correspondants dans les opérandes ont la valeur 1.
OR L'un ou l'autre des bits correspondants dans l'opérande a la valeur 1.
XOR L'un ou l'autre des bits correspondants, mais pas les deux, a la valeur 1.

Les opérateurs AND, OR et XOR prennent tous deux opérandes chacun. Avec chacun de ces opérateurs, les deux nombres entiers que vous spécifiez doivent être du même type. Pour tous les opérateurs au niveau du bit, les opérandes entiers peuvent avoir une longueur de 8, 16 ou 32 bits. (Ainsi, les opérations avec les types LongInt sont valides.)

Les opérateurs SHL et SHR prennent un opérande entier et déplacent les bits du nombre de positions spécifié par le second opérande. Par exemple, le nombre binaire pour 12 est :

$0C = 00001100

Lorsque vous exécutez l'instruction 12 SHR 2, chacun des bits est déplacé de deux positions vers la droite, et le résultat ressemble à ceci :

$03 = 00000011

Le résultat du décalage est le nombre 3. Notez que le décalage de deux à droite équivaut à diviser par 4. Les décalages à gauche et à droite équivalent à multiplier et diviser par une puissance de deux.

Les syntaxes générales pour SHL et SHR sont :

IntVar SHL NumPositions
IntVar SHR NumPositions

Le résultat est toujours du même type que IntVar. Le paramètre NumPositions détermine le nombre de positions de bit à décaler. Si ce nombre est égal ou supérieur au nombre de bits dans IntVar, le résultat est toujours zéro.

Carte mémoire QuickPascal

La carte mémoire décrite dans cette section montre comment un programme QuickPascal utilise la mémoire. Ces informations peuvent vous aider à développer des stratégies pour des programmes très volumineux susceptibles de manquer rapidement de mémoire.

Lorsque vous exécutez un programme QuickPascal, il utilise toute la mémoire disponible (sous réserve des limites imposées par la directive du compilateur {$M} comme décrit ci-dessous). La figure suivante montre la disposition générale de la mémoire dans un programme QuickPascal, et le reste de la section explique la signification des éléments dans cette disposition :

Les lignes pleines de l'image précédentes montrent les démarcations entre les segments. Un «segment» est une zone de mémoire pouvant atteindre 64 Ko de longueur. Ainsi, comme vous pouvez le voir sur la figure précédente, le segment de code du programme principal ne peut jamais dépasser 64 Ko. Cependant, pour les programmes très volumineux, vous pouvez dépasser la limite de 64 Ko en ajoutant simplement des unités supplémentaires. La quantité de code pour chaque unité peut atteindre 64 Ko.

Toutes les variables globales et les constantes typées sont placées dans un seul segment. Ainsi, la taille totale de ces éléments de données ne peut pas dépasser 64 Ko sur toutes les unités.

La pile est placée dans son propre segment. Vous pouvez définir la taille de la pile avec la directive de compilateur {$M}. L'augmentation de la pile permet au programme de prendre en charge davantage d'appels de procédure, mais peut réduire la mémoire disponible pour le tas.

Le tas se compose du reste de la RAM disponible, sauf si vous utilisez la directive du compilateur {$M} pour définir la taille maximale du tas. Le tas contient toutes les variables dynamiques allouées via des appels à New ou GetMem. Si vous savez à l'avance que votre programme doit disposer d'une certaine quantité d'espace de mémoire de tas pour s'exécuter correctement, vous pouvez utiliser la directive de compilateur {$M} pour spécifier une taille de tas minimale. Le DOS ne chargera pas le programme à moins qu'il ne puisse allouer suffisamment de mémoire pour la taille de tas minimale demandée.

Certaines variables apparaissent dans la carte mémoire QuickPascal. Ce sont des variables publiques déclarées dans l'unité System, et votre programme peut y accéder à tout moment (aucune déclaration spéciale n'est nécessaire pour utiliser l'unité System).

La liste suivante décrit les variables publiques apparaissant dans la carte mémoire :

Variable Description
FreePtr Pointeur vers le début de la «liste libre». La liste libre est une série d'enregistrements décrivant la taille et l'emplacement des blocs de mémoire libres à l'intérieur de la mémoire de tas. Au fur et à mesure que la liste libre augmente, FreePtr diminue car la liste libre augmente vers le bas.
HeapPtr Pointeur vers le haut de la mémoire de tas. À mesure que la quantité d'espace nécessaire aux variables dynamiques augmente, HeapPtr augmente. Il ne diminue que lorsque la mémoire est libérée du haut de la mémoire de tas.
HeapOrg Pointeur vers le début de la mémoire de tas. Cette variable a le type générique POINTER, comme les autres pointeurs déclarés dans l'unité System. Vous ne pouvez pas déréférencer des pointeurs de ce type, mais vous pouvez affecter la valeur d'un tel pointeur à n'importe quel autre pointeur, quel que soit son type.
PrefixSeg Variable de mot contenant l'adresse de segment du préfixe de segment de programme (PSP). Lorsque DOS charge votre programme, il construit la PSP pour entreposer les paramètres de la ligne de commande, les vecteurs d'interruption et d'autres informations.

Gestion de la mémoire de tas

Le moyen le plus simple de créer des variables dynamiques consiste à appeler New et GetMem. Dans les petits programmes, vous pouvez généralement appeler ces procédures sans vous soucier de la façon dont le tas est géré. Cependant, si vos programmes utilisent beaucoup la mémoire dynamique, vous pouvez parfois éviter de manquer de mémoire en connaissant le fonctionnement de la mémoire de tas.

Le modèle de base de la mémoire de tas est simple. Initialement, les procédures New et GetMem incrémentent simplement HeapPtr de la taille du bloc de mémoire que vous demandez et renvoient un pointeur au début du bloc.

La structure de la mémoire de tas devient plus compliquée lorsque vous commencez à libérer des blocs de mémoire. Si vous libérez de la mémoire au sommet actuel du tas, HeapPtr est diminué de la quantité de mémoire libérée. Le plus souvent, cependant, le bloc à libérer se trouve quelque part au milieu du tas. Dans ce cas, la procédure de mémoire libre (Dispose ou FreeMem) ajoute un enregistrement à la liste libre pour enregistrer l'existence du bloc libéré.

Le bloc libre est alors comme un trou au milieu de la mémoire de tas. La prochaine fois que vous demanderez de la mémoire, la procédure d'allocation tentera d'allouer de la mémoire à partir d'un bloc libre si elle le peut. De plus, si un bloc est libéré adjacent à un bloc libre existant, les deux blocs forment un bloc libre plus grand, et la liste libre est mise à jour pour refléter ce fait.

Le nombre maximum de blocs libres que les programmes Pascal peuvent gérer est de 8 192. Les programmes atteignent rarement cette limite. Cependant, vous pouvez utiliser diverses techniques pour gérer la mémoire de tas, chacune étant expliquée dans une section ci-dessous :

Utilisation de Mark and Release pour libérer de la mémoire

Les procédures Mark et Release offrent un moyen plus simple et plus efficace de libérer de la mémoire que Dispose ou FreeMem. Cependant, l'utilisation de Mark et Release nécessite que vous libériez de la mémoire dans l'ordre inverse de celui dans lequel vous l'avez allouée. Tous les programmes ne peuvent pas respecter cette exigence.

La procédure Mark définit un pointeur pour pointer vers le haut actuel de la mémoire de tas. Le pointeur peut avoir n'importe quel type de base. Ensuite, si vous allouez plus de mémoire dynamique, la nouvelle mémoire est plus élevée en mémoire que l'emplacement marqué (car le tas grandit vers le haut).

La procédure Release prend un seul pointeur comme argument, tout comme Mark, et libère toute la mémoire dynamique au-dessus du pointeur. En d'autres termes, il libère toute la mémoire allouée après l'appel de la procédure Mark. La procédure Release fonctionne essentiellement en définissant HeapPtr sur la valeur de le paramètre pointeur.

Par exemple, le code suivant alloue cinq variables dynamiques :

  1. New(PtrA);
  2. New(PtrB);
  3. Mark(Ptrs);
  4. New(PtrC);
  5. New(PtrD);

La ligne de code suivante libère les pointeurs déclarés après l'instruction «Mark(Ptrs)». Plus précisément, il libère la mémoire pointée par PtrC et PtrD :

  1. Release(Ptrs);

Les procédures Mark et Release imposent des limitations significatives à la logique du programme car ils ne vous permettent pas de libérer au hasard un bloc de mémoire ; vous ne pouvez libérer que des blocs contigus en haut de la mémoire de tas. Cependant, si vous utilisez ces procédures, vous ne rencontrerez aucun des problèmes survenant parfois avec une liste libre.

Notez que les procédures Mark et Release ne sont pas compatibles avec Dispose et FreeMem. Une fois que vous avez utilisé l'une des deux dernières procédures, vous ne devez pas appeler Release.

Détermination de la mémoire libre et de la taille de la liste libre

Deux fonctions vous permettent de déterminer la quantité de mémoire libre disponible :

Généralement, la fonction MaxAvail est la plus utile des deux fonctions, car elle vous permet de savoir si un appel donné à New ou GetMem aboutira. Les fonctions MemAvail et MaxAvail renvoient toutes deux un résultat LongInt et aucune ne prend de paramètre.

Déterminer la taille de la liste libre vous aide également à découvrir si vous allez manquer de mémoire. La liste libre est déclarée comme suit :

  1. Type
  2.  FreeRec=Record
  3.   OrgPtr,EndPtr:Pointer;
  4.  End;
  5.  
  6.  FreeList=Array[0..8190]of FreeRec;
  7.  FreeListP=^FreeList;

Chaque enregistrement de la liste libre définit un seul bloc de mémoire libre ; les champs OrgPtr et EndPtr définissent respectivement l'adresse de début et de fin du bloc. Le champ EndPtr pointe sur le premier octet après la fin du bloc. Vous pouvez calculer le nombre de blocs libres enregistrés dans la liste libre en utilisant l'instruction suivante :

  1. free_blocks:=(8192-Ofs(FreePtr^) div 8) mod 8192;

Lorsque la partie de déplacement (offset) de l'adresse dans FreePtr est 0, la liste libre est vide. Lorsqu'un bloc libre est ajouté à la liste, FreePtr diminue de huit octets et la liste libre augmente également de huit octets.

Éviter les blocages avec la liste libre

Si le haut de la mémoire de tas est très proche du bas de la liste libre, un interblocage peut se produire lorsque vous essayez de libérer des blocs de mémoire. Si les blocs à libérer sont en haut de la mémoire de tas, il n'y a pas de problème. Sinon, la liste libre ne pourra pas s'étendre.

Ce problème provoque toujours une erreur d'exécution. Vous ne pouvez ni allouer de nouvelle mémoire dynamique ni libérer de mémoire existante. La seule façon d'éviter cette situation est de définir un espace minimum entre les emplacements pointés par HeapPtr et FreePtr.

La variable système FreeMin définit l'espace mémoire libre minimum (entre le haut du tas et le bas de la liste libre). Cette variable est de type Word et représente une distance en octets. Les procédures New et FreeMem n'alloueront pas de bloc de mémoire si cela rendait l'espace entre le tas et la liste libre inférieur à FreeMin. Les fonctions MemAvail et MaxAvail prennent également en compte FreeMin en le soustrayant de la zone mémoire libre.

Écrire une fonction d'erreur de la mémoire de tas

Par défaut, les procédures New et GetMem interrompent simplement l'exécution du programme lorsqu'elles ne peuvent pas renvoyer un bloc mémoire de la taille demandée. Cependant, vous pouvez créer une fonction personnalisée pour répondre aux erreurs de la mémoire de tas.

Votre fonction d'erreur de mémoire de tas est appelée par New et GetMem selon les besoins, et elle peut renvoyer l'une des trois valeurs suivantes :

Valeur Description
0 Échec; abandonner le programme.
1 Échec; renvoie la valeur du pointeur NIL à l'appelant de New ou GetMem, mais n'abandonne pas le programme.
2 Mémoire libérée avec succès ; autoriser New ou GetMem à réessayer.

Votre fonction ne doit renvoyer la valeur 2 que si elle a pu trouver une variable dynamique disponible dans votre programme et libérer la mémoire nécessaire. Si la fonction renvoie 2 sans libérer de mémoire au préalable, elle sera appelée à nouveau.

Pour créer une fonction d'erreur de la mémoire de tas, déclarez d'abord une fonction similaire à celle ci-dessous :

  1. {$F+} Function heap_err(size:Word):Integer; {$F-} Begin
  2.  { Faire en sorte que FreeMem renvoie NIL mais continue l'exécution }
  3.  heap_err:=1;
  4. End;

Vous pouvez donner à la fonction le nom de votre choix et fournir toutes les instructions dans le corps de la fonction. Cependant, le reste de la déclaration devrait être le même. En particulier, la directive de compilation {$F+} est nécessaire pour s'assurer que la fonction est compilée pour les appels long (FAR). L'unique paramètre de type Word donne la taille du bloc mémoire que New ou GetMem n'a pas réussi à allouer.

Après avoir déclaré votre fonction d'erreur de mémoire de tas, définissez la variable système HeapError pour qu'elle pointe vers votre fonction :

  1. HeapError:=@heap_err;

Formats de données internes

La connaissance des formats de données internes est utile si vous souhaitez écrire des procédures en langage assembleur accédant aux données des programmes QuickPascal. De plus, vous pouvez utiliser ces connaissances pour vous aider à déterminer quels types de données entreposent le plus efficacement les informations pour un programme donné.

La section suivante décrit les formats de données pour tous les types sauf les nombres à virgule flottante. Les types de données à virgule flottante sont décrits séparément dans la section la suivant, en raison de leur complexité.

Types de données à virgule non flottante

La liste suivante décrit les formats internes de la plupart des types de données, y compris les variables structurées telles que les tableaux et les enregistrements. Notez que tout type donné est entreposé de la même manière, qu'il s'agisse d'une simple variable ou d'une partie d'un enregistrement.

Lorsqu'une valeur entière est désignée comme signée, le QuickPascal utilise le format de complément à deux pour entreposer les nombres négatifs. Ce format représente le négatif d'un nombre en annulant logiquement chaque bit (comme le fait l'opérateur NOT) puis en ajoutant 1. Par exemple, puisque 00000011 représente 3, alors 11111100 est la négation logique et 11111101 est le complément à deux de 3.

Par conséquent, si vous déclarez une constante Byte comme -3, le QuickPascal entrepose cette valeur comme 11111101. De manière générale, vous n'avez jamais à effectuer vous-même la conversion en complément à deux. Il s'agit simplement d'un format interne compris à la fois par QuickPascal et la famille de processeurs 8086.

En conséquence, tous les nombres non négatifs ont 0 dans le bit le plus significatif ; tous les nombres négatifs ont 1 dans le bit le plus significatif. Les formats non signés ne considèrent aucun nombre comme négatif.

Type de données Format interne
Char Un octet non signé. (Les valeurs vont de 0 à 255.)
Byte Un octet signé. (Les valeurs vont de -128 à 127.)
Integer Un mot signé. (Les valeurs vont de -32768 à 32767.)
Word Un mot non signé. (Les valeurs vont de 0 à 65535.)
LongInt Un double mot signé. (Les valeurs vont de -2147483648 à 2147483647.)
Comp Un entier signé de huit octets. Si le bit le plus significatif est 1 et que les autres bits sont tous 0, ce type a la valeur spéciale NAN (Not a Number). Notez qu'avec la directive {N+}, le QuickPascal utilise le coprocesseur 8087, s'il est installé, pour effectuer des calculs avec des types Comp.
Boolean Un octet prenant la valeur 0 (False) ou 1 (True)
Types énumérés Entreposé en tant qu'octet non signé si le type peut prendre 256 valeurs ou moins. Sinon, le type énuméré est stocké sous la forme d'un mot non signé. Le premier élément d'une définition de type énumération correspond à la valeur 0 et les autres sont numérotés séquentiellement dans l'ordre indiqué.
Types STRING Une séquence d'octets dans laquelle le premier octet entrepose la longueur actuelle de la chaîne de caractères (pouvant être inférieure à sa longueur maximale) et le reste des octets entrepose les données de la chaîne de caractères. Le QuickPascal alloue suffisamment d'espace pour la longueur maximale d'une chaîne de caractères plus un octet supplémentaire (pour l'indicateur de longueur). Le type générique STRING est équivalent à STRING[255], ayant la limite maximale.
CSTRING Semblable aux types STRING, sauf que le premier octet n'est pas un indicateur de longueur et que les données de chaîne de caractères incluent un octet nul de fin. La limite maximale est de 255 + octets nuls. Le type CSTRING générique utilise par défaut cette longueur.
Types SET Entreposé sous forme de tableau de bits, dans lequel chaque bit correspond à un élément spécifique. Si un bit est activé, alors l'élément correspondant est présent. Le QuickPascal alloue un octet pour chaque élément, jusqu'à un maximum de 32 octets (256 éléments). Les éléments d'un ensemble sont stockés dans l'ordre de leur valeur ordinale, dans lequel le premier élément est stocké dans le bit le moins significatif.
Types ARRAY Les éléments du tableau sont entreposés de manière contiguë dans la mémoire, en commençant par le composant indexé le plus bas et en allant dans l'ordre vers le plus élevé. Dans les tableaux multidimensionnels, l'index le plus à droite change le plus rapidement. Ainsi, dans le tableau déclaré comme Grid[Rows,Cols], tous les éléments d'une ligne entière sont entreposés les uns à côté des autres en mémoire.
Types RECORD Une séquence de variables entreposées de manière contiguë en mémoire (les champs apparaissent en mémoire dans le même ordre que dans le code source). Avec les enregistrements de variantes, chaque cas de variante commence à la même adresse.
Types pointeur Un double mot contenant l'adresse de déplacement (offset) dans le mot de poids faible et l'adresse de segment dans le mot de poids fort. La valeur spéciale NIL est un pointeur générique contenant la valeur zéro.

Types de données à virgule flottante

La figure suivante affiche les formats utilisés pour représenter les nombres à virgule flottante de différents niveaux de précision. Le QuickPascal utilise ces formats de données qu'un coprocesseur mathématique soit installé ou non. Si un coprocesseur n'est pas installé, alors QuickPascal fournit des procédures pour exécuter des calculs en virgule flottante. En cas d'arrondi, il n'est pas garanti que ces procédures produiront précisément les mêmes résultats qu'un coprocesseur.

Avec chacun des formats illustrés à la figure précédente, le bit de signe(s) indique un nombre négatif s'il est activé et un nombre non négatif s'il est désactivé. La partie fractionnaire (f) indique la partie fractionnaire de la mantisse. Pour économiser de l'espace, les formats de données supposent tous que le chiffre des unités dans la mantisse est égal à 1. Enfin, l'exposant est ajusté à la baisse par un nombre différent pour chaque format. Cela signifie que pour le type Single à quatre octets, la valeur représentée est égale à :

-1(s)* 1.f * 2(e-127)

Par exemple, supposons qu'une variable de ce type contienne les valeurs suivantes :

s = 0
e = 127 décimal
f = 11110100000000000000000 binaire

La valeur de la variable est 1,111101 binaire. Notez que (contrairement à la figure précédente et à l'exemple ci-dessus), l'entreposage des données sur les processeurs 8086 s'exécute en fait du bit le moins significatif (entreposé le plus bas en mémoire) au bit le plus significatif. Ce fait peut donner l'impression que les octets sont en arrière si vous utilisez un outil de débogage pour vider un mot à la fois.

Les exposants sont automatiquement réduits de 129 pour le format réel à six octets, de 1 023 pour le format double et de 16 383 pour le format étendu. Cette réduction automatique permet au champ exposant de représenter un nombre négatif (produisant une valeur à virgule flottante comprise entre 1 et -1).

Dans chaque format, si tous les bits sont mis à zéro, le nombre à virgule flottante résultant est égal à zéro.

Lien vers le langage d'assemblage

Vous pouvez lier des programmes QuickPascal à des modules écrits avec Macro Assembler et d'autres assembleurs. Cependant, sachez que QuickPascal a un certain nombre de conventions et de restrictions qui diffèrent des autres langages Microsoft. Lorsque vous écrivez une procédure en langage assembleur à lier à QuickPascal, votre module en langage assembleur peut inclure :

Cependant, le module en langage assembleur ne peut pas déclarer de données publiques pour le code QuickPascal à référencer. De plus, un module en langage assembleur doit placer toutes les instructions dans un seul segment nommé CODE et ne peut pas utiliser la directive GROUP. (Les conventions de segment sont décrites dans les sections suivantes.

Les sections suivantes listent les étapes d'écriture d'un module en langage assembleur, à peu près dans l'ordre dans lequel vous les observeriez en écrivant le code. Vous devez lire attentivement toutes ces sections avant d'essayer d'écrire une procédure en langage assemblable (bien que la section sur les valeurs de retour soit facultative et ne s'applique que lorsque vous écrivez une fonction). La section les suivants contient un exemple simple mais complet.

Configuration d'un lien vers le langage d'assemblage

Pour créer un lien vers le langage assembleur, vous devez écrire une déclaration EXTERNAL pour chaque procédure en langage assembleur que vous allez appeler et utiliser la directive de compilateur {$L} pour créer un lien dans le fichier objet.

Pour écrire une déclaration EXTERNAL, écrivez simplement un entête de procédure ou de fonction comme vous le feriez normalement et suivez l'entête avec le mot clef EXTERNAL comme indiqué ci-dessous :

  1. Function compute(a,b,c:Integer):Integer; External;
  2. Procedure switcheroo(Var a,b,c:Byte); External;

Placez la directive de compilation {$L} au début de votre programme principal QuickPascal. La directive {$L} prend un seul paramètre : le nom de fichier de base d'un fichier objet. Notez que vous n'utilisez pas LINK ou tout autre utilitaire pour produire un programme. Au lieu de cela, le QuickPascal configure le lien en copiant le fichier objet dans le programme et en modifiant le fichier dans son propre format de code objet interne. (La copie sur disque du fichier objet n'est cependant pas affectée.)

Conventions de segment et de données

Respectez les conventions suivantes lors de la déclaration de segments et de données :

Entrer dans la procédure

La séquence de saisie des procédures QuickPascal est la même que celle des autres langages Microsoft. Les deux premières instructions configurent le registre BP en tant que le pointeur de cadre", pointant vers la zone de pile de la procédure en cours. Tous les paramètres et variables locales sont disponibles via BP :

  1. PUSH BP
  2. MOV BP, SP
  3. SUB SP,local_size

Dans le code ci-dessus, local_size est un espace réservé pour la taille totale des paramètres locaux que vous souhaitez utiliser. Cette dernière étape est entièrement facultative. Votre procédure peut être en mesure d'utiliser des registres pour contenir toutes les valeurs temporaires. Sinon, vous pouvez utiliser des emplacements sur la pile pour entreposer des données. (Plus précisément, ces emplacements sont en dessous de l'adresse pointée par BP, mais pas plus de local_size octets en dessous de cette adresse.)

Les procédures appelées par QuickPascal doivent conserver les valeurs des registres BP, SP, SS et DS. BP et SP sont conservés par le code standard d'entrée et de sortie. Si vous avez besoin de modifier DS ou SS, vous devez pousser leurs valeurs sur la pile et les faire apparaître juste avant de revenir comme indiqué ci-dessous :

  1. PUSH BP
  2. MOV BP, SP
  3. SUB SP, local
  4. PUSH DS

Accéder aux paramètres

Tous les paramètres et variables locales sont accessibles en tant qu'opérandes de mémoire indirecte à l'aide du registre BP. Pour déterminer l'emplacement de chaque paramètre, vous devez comprendre les conventions d'appel QuickPascal. Cette section résume ces conventions et donne quelques exemples avec des procédures spécifiques.

Le QuickPascal place chaque paramètre sur la pile, dans l'ordre dans lequel les paramètres apparaissent dans le code source, avant d'effectuer l'appel proprement dit. Par conséquent, le premier paramètre est le plus élevé en mémoire. (Rappelez-vous que la pile grandit vers le bas.) Vous pouvez passer des paramètres par valeur ou par référence. En Pascal, les paramètres VAR sont passés par référence ; les autres paramètres sont passés par valeur.

Lorsqu'un paramètre est passé par référence, QuickPascal place un pointeur de quatre octets sur les données. La partie de déplacement du pointeur est toujours enfoncée en premier et est donc plus haute en mémoire.

Lorsqu'un paramètre est passé par valeur, Pascal effectue différentes actions selon le type de données :

Cette convention d'appel pour les paramètres de valeur n'est pas partagée par les autres langages de haut niveau de Microsoft, poussant toujours un paramètre de valeur directement sur la pile.

Notez que l'instruction PUSH ne peut pousser qu'un seul mot de données à la fois. Le QuickPascal pousse toujours la partie la plus significative en premier, conformément à la convention du microprocesseur 8086 selon laquelle la partie la plus significative est entreposée en haut de la mémoire.

Comme indiqué dans la section suivante, le code QuickPascal place parfois un paramètre supplémentaire sur la pile pour les fonctions. (Ce paramètre supplémentaire est un pointeur vers l'emplacement de la valeur de retour.)

Enfin, le code QuickPascal appelle la procédure avec une instruction CALL. Les appels aux procédures externes sont toujours loin, vous devez donc déclarer votre procédure d'assemblage avec le mot-clef FAR.

Par exemple, considérons l'appel de procédure suivant :

  1. Procedure quad(Var x:Real;a,b,c:Integer); External;
  2. quad(result,2,3,4);

Le QuickPascal implémente l'appel en plaçant les éléments suivants sur la pile, et dans cet ordre :

Une instruction CALL FAR place le dernier élément, l'adresse de retour, sur la pile. Après avoir effectué la séquence d'entrée, le registre BP pointe vers l'emplacement juste en dessous de l'adresse de retour. Chaque paramètre est alors accessible avec les équations suivantes :

  1. result EQU <[BP+12]>
  2. a EQU      <[BP+10]>
  3. b EQU      <[BP+8]>
  4. c EQU      <[BP+6]>

Comme autre exemple, considérons l'appel de procédure :

  1. Procedure repeat_it(num:LongInt;stuff:String);External;
  2.  { : }
  3. repeat_it(n,'Ceci est un message.');

Avec cette procédure, QuickPascal implémente l'appel en plaçant les éléments suivants sur la pile :

Une instruction CALL FAR place les deux derniers éléments sur la pile. Après avoir écrit la séquence d'entrée, vous pouvez accéder aux paramètres avec les équations suivantes :

  1. num   EQU <[BP + 10]>
  2. stuff EQU <[BP + 6]>

Renvoyer une valeur

Cette section concerne uniquement les fonctions. Du point de vue du code assembleur, les fonctions ne sont que des procédures ayant une valeur de retour. Pour renvoyer une valeur au code QuickPascal, suivez ces conventions :

Sortir de la procédure

Pour quitter la procédure, commencez par dépiler tous les registres que vous avez conservés sur la pile. Ensuite, écrivez les instructions suivantes :

  1. MOV SP,BP
  2. POP BP
  3. RET param_size

Dans le code ci-dessus, param_size est un espace réservé pour la taille totale des paramètres de votre procédure. Cette dernière instruction est nécessaire pour restaurer complètement la pile.

Un exemple complet

L'exemple suivant de liaison de programmes QuickPascal à des modules écrits en langage assembleur est simple, mais complet. Le code source Pascal contient les instructions suivantes :

  1. {$L SWITCH}
  2. Procedure switch(Var a, b:Word); External;
  3.  { : }
  4. switch(x, y);

Puisque les paramètres sont des paramètres VAR, QuickPascal passe des pointeurs vers les paramètres. Ensuite, le code assembleur doit utiliser des opérandes de registre indirects pour accéder aux données. La procédure suivante échange les valeurs des deux variables.

  1. ; SWITCH.ASM
  2. CODE SEGMENT WORD PUBLIC
  3. ASSUME CS:CODE
  4. PUBLIC switch ; Rendre la procédure publique
  5. a EQU <[BP+10]> ; Paramètres pour le commutateur
  6. b EQU <[BP+6]> ; procédure
  7. switch PROC FAR
  8. PUSH BP ; Séquence d'entrée
  9.  MOV BP,SP
  10.  PUSH DS
  11.  LDS SI,a ; Charger ptr dans DS:SI
  12.  LES DI,b ; Charger ptr dans ES:DI
  13.  MOV BX, ES:[DI] ; temp = b
  14.  XCHG BX,[SI] ; Commutez temp et a
  15.  XCHG BX,ES:[DI] ; Commutez temp et b
  16. POP DS
  17. MOV SP, BP ; Séquence de sortie
  18. POP BP
  19. RET 8 ; Total de 8 octets dans les paramètres
  20. switch ENDP


Dernière mise à jour : Dimanche, le 28 août 2022