Menú English Ukrainian Ruso Inicio

Biblioteca técnica gratuita para aficionados y profesionales. biblioteca técnica gratuita


Notas de clase, hojas de trucos
biblioteca gratis / Directorio / Notas de clase, hojas de trucos

Informática y tecnologías de la información. Apuntes de clase: brevemente, los más importantes

Notas de clase, hojas de trucos

Directorio / Notas de clase, hojas de trucos

Comentarios sobre el artículo Comentarios sobre el artículo

tabla de contenidos

  1. Introducción a la informática (Informática. Información. Presentación y procesamiento de información. Sistemas numéricos. Representación de números en una computadora. Concepto formalizado de algoritmo)
  2. lenguaje pascal (Introducción a Pascal. Procedimientos y funciones estándar. Operadores de Pascal)
  3. Procedimientos y funciones (El concepto de algoritmo auxiliar. Procedimientos en Pascal. Funciones en Pascal. Descripciones anticipadas y conexión de subrutinas. Directiva)
  4. Subrutinas (Parámetros de rutina. Tipos de parámetros de subrutina. Tipo cadena en Pascal. Procedimientos y funciones para variables de tipo cadena. Registros. Conjuntos)
  5. Archivos (Archivos. Operaciones con archivos. Módulos. Tipos de módulos)
  6. memoria dinámica (Tipo de datos de referencia. Memoria dinámica. Variables dinámicas. Trabajar con memoria dinámica. Punteros sin tipo)
  7. Estructuras de datos abstractas (Estructuras de datos abstractas. Pilas. Colas)
  8. Estructuras de datos de árbol (Estructuras de datos de árboles. Operaciones sobre árboles. Ejemplos de implementación de operaciones)
  9. Cuenta (El concepto de gráfico. Métodos de representación de un gráfico. Representación de un gráfico mediante una lista de incidencias. Algoritmo de recorrido en profundidad para un gráfico. Representación de un gráfico como una lista de listas. Algoritmo de recorrido en amplitud para un gráfico. )
  10. Tipo de datos de objeto (Tipo de objeto en Pascal. El concepto de objeto, su descripción y uso. Herencia. Creación de instancias de objetos. Componentes y alcance)
  11. Métodos (Métodos. Constructores y destructores. Destructores. Métodos virtuales. Campos de datos de objetos y parámetros de métodos formales)
  12. Compatibilidad de tipo de objeto (Encapsulación. Objetos extensibles. Compatibilidad de tipos de objetos)
  13. Ensamblador (Acerca del ensamblador. Modelo de software de microprocesador. Registros de usuario. Registros de propósito general. Registros de segmento. Registros de estado y control)
  14. Registros (Registros del sistema de microprocesador. Registros de control. Registros de direcciones del sistema. Registros de depuración)
  15. Programas de montaje (Estructura del programa ensamblador. Sintaxis del ensamblador. Operadores de comparación. Operadores y su precedencia. Directivas de definición de segmentos simplificadas. Identificadores creados por la directiva MODEL. Modelos de memoria. Modificadores del modelo de memoria)
  16. Estructuras de instrucciones de montaje (Estructura de una instrucción de máquina. Métodos para especificar operandos de instrucción. Métodos de direccionamiento)
  17. Equipos (Comandos de transferencia de datos. Comandos aritméticos)
  18. Comandos de transferencia de control (Comandos lógicos. Tabla de verdad para negación lógica. Tabla de verdad para OR lógico inclusivo. Tabla de verdad para AND lógico. Tabla de verdad para OR lógico exclusivo. Significado de las abreviaturas en el nombre del comando jcc. Lista de comandos de salto condicional para el comando. Salto condicional comandos y banderas)

CONFERENCIA N° 1. Introducción a la informática

1. Informática. Información. Representación y tratamiento de la información

La informática se dedica a una representación formalizada de objetos y estructuras de sus relaciones en varios campos de la ciencia, la tecnología y la producción. Se utilizan varias herramientas formales para modelar objetos y fenómenos, como fórmulas lógicas, estructuras de datos, lenguajes de programación, etc.

En informática, un concepto tan fundamental como el de información tiene varios significados:

1) presentación formal de formas externas de información;

2) significado abstracto de la información, su contenido interno, semántica;

3) relación de la información con el mundo real.

Pero, por regla general, la información se entiende como su significado abstracto: la semántica. Al interpretar la representación de la información, obtenemos su significado, la semántica. Por lo tanto, si queremos intercambiar información, necesitamos puntos de vista consistentes para que no se viole la corrección de la interpretación. Para ello, la interpretación de la representación de la información se identifica con unas estructuras matemáticas. En este caso, el procesamiento de la información se puede realizar mediante métodos matemáticos rigurosos.

Una de las descripciones matemáticas de la información es su representación en forma de función y =f(x, t), donde t es el tiempo, x es un punto en un determinado campo en el que se mide el valor de y. Dependiendo de los parámetros de la función chi (la información se puede clasificar.

Si los parámetros son cantidades escalares que toman una serie continua de valores, entonces la información así obtenida se denomina continua (o analógica). Si a los parámetros se les da un cierto paso de cambio, entonces la información se llama discreta. La información discreta se considera universal, ya que para cada parámetro específico es posible obtener un valor de función con un grado de precisión determinado.

La información discreta suele identificarse con la información digital, que es un caso especial de información simbólica de representación alfabética. Un alfabeto es un conjunto finito de símbolos de cualquier naturaleza. Muy a menudo en informática surge una situación en la que los caracteres de un alfabeto deben ser representados por los caracteres de otro, es decir, se debe realizar una operación de codificación. Si el número de caracteres del alfabeto de codificación es menor que el número de caracteres del alfabeto de codificación, entonces la operación de codificación en sí no es complicada; de lo contrario, es necesario utilizar un conjunto fijo de caracteres del alfabeto de codificación para una codificación correcta y sin ambigüedades.

Como ha demostrado la práctica, el alfabeto más simple que le permite codificar otros alfabetos es el binario, que consta de dos caracteres, que generalmente se denotan con 0 y 1. Usando n caracteres del alfabeto binario, puede codificar 2n caracteres, y esto es suficiente para codificar cualquier alfabeto.

El valor que puede ser representado por un símbolo del alfabeto binario se denomina unidad mínima de información o bit. Secuencia de 8 bits - bytes. Un alfabeto que contiene 256 secuencias diferentes de 8 bits se denomina alfabeto de bytes.

Hoy en día, como estándar en informática, se adopta un código en el que cada carácter está codificado por 1 byte. Hay otros alfabetos también.

2. Sistemas numéricos

Un sistema numérico es un conjunto de reglas para nombrar y escribir números. Hay sistemas numéricos posicionales y no posicionales.

El sistema numérico se llama posicional si el valor del dígito del número depende de la ubicación del dígito en el número. De lo contrario, se llama no posicional. El valor de un número está determinado por la posición de estos dígitos en el número.

3. Representación de números en una computadora

Los procesadores de 32 bits pueden funcionar con hasta 232-1 RAM y las direcciones se pueden escribir en el rango 00000000 - FFFFFFFF. Sin embargo, en modo real, el procesador funciona con una memoria de hasta 220-1 y las direcciones se encuentran en el rango 00000 - FFFFF. Los bytes de memoria se pueden combinar en campos de longitud fija y variable. Una palabra es un campo de longitud fija que consta de 2 bytes, una palabra doble es un campo de 4 bytes. Las direcciones de campo pueden ser pares o impares, y las direcciones pares realizan operaciones más rápido.

Los números de punto fijo se representan en las computadoras como números binarios enteros y su tamaño puede ser de 1, 2 o 4 bytes.

Los números enteros binarios se representan en complemento a dos y los números de punto fijo se representan en complemento a dos. Además, si un número ocupa 2 bytes, entonces la estructura del número se escribe de acuerdo con la siguiente regla: el dígito más significativo se asigna al signo del número y el resto, a los dígitos binarios del número. El código complementario de un número positivo es igual al número mismo, y el código complementario de un número negativo se puede obtener usando la siguiente fórmula: x = 10i - \x\, donde n es la capacidad de dígitos del número.

En el sistema numérico binario, se obtiene un código adicional invirtiendo bits, es decir, reemplazando unidades con ceros y viceversa, y sumando uno al bit menos significativo.

El número de bits de la mantisa determina la precisión de la representación de números, el número de bits de orden de máquina determina el rango de representación de números de punto flotante.

4. Concepto formalizado de un algoritmo

Un algoritmo solo puede existir si, al mismo tiempo, existe algún objeto matemático. El concepto formalizado de un algoritmo está conectado con el concepto de funciones recursivas, algoritmos normales de Markov, máquinas de Turing.

En matemáticas, una función se llama de un solo valor si, para cualquier conjunto de argumentos, existe una ley por la cual se determina el valor único de la función. Un algoritmo puede actuar como tal ley; en este caso se dice que la función es computable.

Las funciones recursivas son una subclase de funciones computables, y los algoritmos que definen el cálculo se denominan algoritmos de funciones recursivas complementarias. Primero, se fijan las funciones recursivas básicas, para las cuales el algoritmo que las acompaña es trivial, inequívoco; luego se introducen tres reglas: operadores de sustitución, recursión y minimización, con la ayuda de los cuales se obtienen funciones recursivas más complejas sobre la base de funciones básicas.

Las funciones básicas y los algoritmos que las acompañan pueden ser:

1) una función de n variables independientes, idénticamente igual a cero. Entonces, si el signo de la función es φn, independientemente del número de argumentos, el valor de la función debe establecerse igual a cero;

2) la función identidad de n variables independientes de la forma ψni. Entonces, si el signo de la función es ψni, entonces el valor de la función debe tomarse como el valor del i-ésimo argumento, contando de izquierda a derecha;

3) Λ es una función de un argumento independiente. Entonces, si el signo de la función es λ, entonces el valor de la función debe tomarse como el valor que sigue al valor del argumento. Distintos estudiosos han propuesto sus propios enfoques sobre la formalización

representación del algoritmo. Por ejemplo, el científico estadounidense Church sugirió que la clase de funciones computables se agota en funciones recursivas y, como resultado, sea cual sea el algoritmo que procesa un conjunto de números enteros no negativos en otro, hay un algoritmo que acompaña a la función recursiva que es equivalente al dado. Por lo tanto, si es imposible construir una función recursiva para resolver un problema dado, entonces no existe un algoritmo para resolverlo. Otro científico, Turing, desarrolló una computadora virtual que procesaba una secuencia de entrada de caracteres en una salida. En este sentido, planteó la tesis de que cualquier función computable es computable por Turing.

Lección N° 2. El lenguaje pascual

1. Introducción al lenguaje Pascal

Los símbolos básicos del idioma (letras, números y caracteres especiales) forman su alfabeto. El lenguaje Pascal incluye el siguiente conjunto de símbolos básicos:

1) 26 minúsculas latinas y 26 mayúsculas latinas:

ABCDEFGHIJKLMNOPQRSTUVWXYZ

ABCDEFGHIJKLMNOPQRSTU VWXYZ;

2) _ (guion bajo);

3) 10 dígitos: 0123456789;

4) signos de operaciones:

+ - x / = <> < > <= >= := @;

5) limitadores:

., ' ( ) [ ] (..) { } (* *).. : ;

6) especificadores: ^ # $;

7) palabras de servicio (reservadas):

ABSOLUTO, ENSAMBLADOR, Y, MATRIZ, ASM, COMIENZO, CASO, CONST, CONSTRUCTOR, DESTRUCTOR, DIV, DO, DOWNTO, ELSE, FIN, EXPORTACIÓN, EXTERNO, FAR, ARCHIVO, PARA, ADELANTE, FUNCIÓN, IR A, SI, IMPLEMENTACIÓN, EN, ÍNDICE, HEREDADO, EN LÍNEA, INTERFAZ, INTERRUPCIÓN, ETIQUETA, BIBLIOTECA, MOD, NOMBRE, NIL, CERCA, NO, OBJETO, DE O, EMPAQUETADO, PRIVADO, PROCEDIMIENTO, PROGRAMA, PÚBLICO, REGISTRO, REPETIR, RESIDENTE, CONJUNTO, SHL, SHR, CADENA, ENTONCES, A, TIPO, UNIDAD, HASTA, USOS, VAR, VIRTUAL, MIENTRAS, CON, XOR.

Además de los enumerados, el conjunto de caracteres básicos incluye un espacio. No se pueden utilizar espacios dentro de caracteres dobles y palabras reservadas.

Concepto de tipo para datos

Es habitual en matemáticas clasificar las variables de acuerdo con algunas características importantes. Se hace una distinción estricta entre variables reales, complejas y lógicas, entre variables que representan valores individuales y un conjunto de valores, etc. Cuando se procesan datos en una computadora, esta clasificación es aún más importante. En cualquier lenguaje algorítmico, cada constante, variable, expresión o función es de un tipo particular.

Hay una regla en Pascal: el tipo se especifica explícitamente en la declaración de una variable o función que precede a su uso. El concepto de tipo Pascal tiene las siguientes propiedades principales:

1) cualquier tipo de dato define un conjunto de valores al que pertenece una constante, que puede tomar una variable o expresión, o puede producir una operación o función;

2) el tipo de valor dado por una constante, variable o expresión puede ser determinado por su forma o descripción;

3) cada operación o función requiere argumentos de tipo fijo y produce un resultado de tipo fijo.

De ello se deduce que el compilador puede utilizar información de tipo para comprobar la computabilidad y corrección de varias construcciones.

El tipo define:

1) valores posibles de variables, constantes, funciones, expresiones pertenecientes a un tipo dado;

2) la forma interna de presentación de datos en una computadora;

3) operaciones y funciones que se pueden realizar sobre valores pertenecientes a un tipo dado.

Cabe señalar que la descripción obligatoria del tipo conduce a la redundancia en el texto de los programas, pero dicha redundancia es una herramienta auxiliar importante para desarrollar programas y se considera una propiedad necesaria de los lenguajes algorítmicos modernos de alto nivel.

Hay tipos de datos escalares y estructurados en Pascal. Los tipos escalares incluyen tipos estándar y tipos definidos por el usuario. Los tipos estándar incluyen tipos enteros, reales, de caracteres, booleanos y de direcciones.

Los tipos enteros definen constantes, variables y funciones cuyos valores son realizados por el conjunto de enteros permitidos en una computadora dada.

Los tipos reales definen aquellos datos que son implementados por un subconjunto de números reales que están permitidos en una computadora determinada.

Los tipos definidos por el usuario son enumeración y rango. Los tipos estructurados vienen en cuatro sabores: matrices, conjuntos, registros y archivos.

Además de los enumerados, Pascal incluye dos tipos más: de procedimiento y de objeto.

Una expresión de lenguaje consta de constantes, variables, punteros de función, signos de operador y corchetes. Una expresión define una regla para calcular algún valor. El orden de cálculo está determinado por la precedencia (prioridad) de las operaciones contenidas en él. Pascal tiene la siguiente precedencia de operadores:

1) cálculos entre paréntesis;

2) cálculo de valores de función;

3) operaciones unarias;

4) operaciones *, /, div, mod y;

5) operaciones +, -, o, xor;

6) operaciones relacionales =, <>, <, >, <=, >=.

Las expresiones son parte de muchos operadores del lenguaje Pascal y también pueden ser argumentos para funciones integradas.

2. Procedimientos y funciones estándar

Funciones aritméticas

1.Función Abs(X);

Devuelve el valor absoluto del parámetro.

X es una expresión de tipo real o entero.

2. Función ArcTan(X: Extendido): Extendido;

Devuelve el arco tangente del argumento.

X es una expresión de tipo real o entero.

3. Función exp(X: Real): Real;

Devuelve el exponente.

X es una expresión de tipo real o entero.

4.Frac(X: Real): Real;

Devuelve la parte fraccionaria del argumento.

X es una expresión de tipo real. El resultado es la parte fraccionaria de X, es decir

Frac(X) = X-Int(X).

5. Función Int(X: Real): Real;

Devuelve la parte entera del argumento.

X es una expresión de tipo real. El resultado es la parte entera de X, es decir, X redondeada a cero.

6. Función Ln(X: Real): Real;

Devuelve el logaritmo natural (Ln e = 1) de una expresión de tipo real X.

7. Función Pi: extendida;

Devuelve el valor Pi, que se define como 3.1415926535.

8. Función Sin (X: Extendido): Extendido;

Devuelve el seno del argumento.

X es una expresión de tipo real. Sin devuelve el seno del ángulo X en radianes.

9.Función Sqr(X: Extendido): Extendido;

Devuelve el cuadrado del argumento.

X es una expresión de punto flotante. El resultado es del mismo tipo que X.

10.Función Sqrt(X: Extendido): Extendido;

Devuelve la raíz cuadrada del argumento.

X es una expresión de punto flotante. El resultado es la raíz cuadrada de X.

Procedimientos y funciones de conversión de valores

1. Procedimiento Str(X [: Ancho [: Decimales]]; var S);

Convierte el número X en una representación de cadena según

Opciones de formato de ancho y decimales. X es una expresión de tipo real o entero. Width y Decimals son expresiones de tipo entero. S es una variable de tipo String o una matriz de caracteres terminada en nulo si se permite la sintaxis extendida.

2. Función Chr(X: Byte): Char;

Devuelve el carácter con ordinal X en la tabla ASCII.

3. Función alta (X);

Devuelve el valor más grande en el rango del parámetro.

4. Función Baja (X);

Devuelve el valor más pequeño en el rango del parámetro.

5 FunctionOrd(X): Entero largo;

Devuelve el valor ordinal de una expresión de tipo enumerada. X es una expresión de tipo enumerado.

6. Ronda de función (X: extendida): Entero largo;

Redondea un valor real a un número entero. X es una expresión de tipo real. Round devuelve un valor de Entero largo, que es el valor de X redondeado al número entero más cercano. Si X está exactamente a medio camino entre dos enteros, se devuelve el número con el mayor valor absoluto. Si el valor redondeado de X está fuera del rango Entero largo, se genera un error en tiempo de ejecución que puede controlar mediante la excepción EInvalidOp.

7. Función Trunc (X: Extendido): Entero largo;

Trunca un valor de tipo real a un entero. Si el valor redondeado de X está fuera del rango de Entero largo, se genera un error en tiempo de ejecución que puede controlar mediante la excepción EInvalidOp.

8. Procedimiento Val(S; var V; var Código: Entero);

Convierte un número de un valor de cadena S a un número

representación V. S - expresión de tipo cadena - una secuencia de caracteres que forma un número entero o real. Si la expresión S no es válida, el índice del carácter no válido se almacena en la variable Código. De lo contrario, el código se establece en cero.

Procedimientos y funciones de valor ordinal

1. Procedimiento Dec(varX [; N: LongInt]);

Resta uno o N de la variable X. Dec(X) corresponde a X:= X - 1, y Dec(X, N) corresponde a X:= X - N. X es una variable de tipo enumerado, o de tipo PChar si se permite la sintaxis extendida y N es una expresión de tipo entero. El procedimiento Dec genera código óptimo y es especialmente útil en bucles largos.

2. Procedimiento Inc(varX [; N: LongInt]);

Agrega uno o N a la variable X. X es una variable de tipo enumerado o tipo PChar si se permite la sintaxis extendida, y N es una expresión de tipo integral. Inc (X) coincide con la instrucción X:= X + 1 e Inc (X, N) coincide con la instrucción X:= X + N. El procedimiento Inc genera código óptimo y es especialmente útil en bucles largos.

3. FunctionOdd(X: LongInt): Boolean;

Devuelve True si X es un número impar, False en caso contrario.

4.FunciónPred(X);

Devuelve el valor anterior del parámetro. X es una expresión de tipo enumerado. El resultado es del mismo tipo.

5 Función Suc(X);

Devuelve el siguiente valor del parámetro. X es una expresión de tipo enumerado. El resultado es del mismo tipo.

3. Operadores del lenguaje Pascal

Operador condicional

El formato de la declaración condicional completa se define de la siguiente manera: si B entonces SI sino S2; donde B es una condición de bifurcación (toma de decisiones), una expresión lógica o una relación; SI, S2: una declaración ejecutable, simple o compuesta.

Al ejecutar una declaración condicional, primero se evalúa la expresión B, luego se analiza su resultado: si B es verdadera, entonces se ejecuta la declaración S1, la rama de entonces, y se omite la declaración S2; si B es falso, entonces se ejecuta la instrucción S2: se ejecuta la rama else y se omite la instrucción S1.

También hay una forma abreviada del operador condicional. Se escribe como: Si B entonces S.

Operador de selección

La estructura del operador es la siguiente:

los casos de

c1: instrucción1;

c2: instrucción2;

...

cn: instrucciónN;

otra instrucción

fin;

donde S es una expresión de tipo ordinal cuyo valor se está calculando;

с1, с2..., сп - constantes de tipo ordinal con las que se comparan las expresiones

S; instrucción1,..., instrucciónN - operadores de los cuales se ejecuta aquel cuya constante coincide con el valor de la expresión S;

instrucción: una declaración que se ejecuta si el valor de la expresión Sylq no coincide con ninguna de las constantes c1, c2.... cn.

Este operador es una generalización del operador condicional If para un número arbitrario de alternativas. Hay una forma abreviada de la declaración donde no hay otra rama.

Instrucción de bucle con parámetro

Las sentencias de bucle de parámetros que comienzan con la palabra for hacen que la sentencia, que puede ser una sentencia compuesta, se ejecute repetidamente mientras a la variable de control se le asigna una secuencia ascendente de valores.

Vista general del operador for:

for <contador de bucles> := <valor inicial> to <valor final> do <instrucción>;

Cuando la instrucción for comienza a ejecutarse, los valores inicial y final se determinan una vez, y estos valores se conservan durante la ejecución de la instrucción for. La instrucción contenida en el cuerpo de la instrucción for se ejecuta una vez para cada valor en el rango entre los valores inicial y final. El contador de bucle siempre se inicializa a un valor inicial. Cuando se ejecuta la declaración for, el valor del contador de bucle se incrementa con cada iteración. Si el valor inicial es mayor que el valor final, la instrucción contenida en el cuerpo de la instrucción for no se ejecuta. Cuando se utiliza la palabra clave downto en una declaración de bucle, el valor de la variable de control se reduce en uno en cada iteración. Si el valor de inicio en tal instrucción es menor que el valor final, entonces la instrucción contenida en el cuerpo de la instrucción de bucle no se ejecuta.

Si la declaración contenida en el cuerpo de la declaración for cambia el valor del contador de bucle, entonces esto es un error. Después de la ejecución de la instrucción for, el valor de la variable de control se vuelve indefinido, a menos que la ejecución de la instrucción for haya sido interrumpida por una instrucción de salto.

Declaración de bucle con condición previa

Una declaración de bucle de condición previa (que comienza con la palabra clave while) contiene una expresión que controla la ejecución repetida de la declaración (que puede ser una declaración compuesta). Forma del ciclo:

Mientras que B hace S;

donde B es una condición lógica, cuya verdad se verifica (es una condición para terminar el ciclo);

S - cuerpo de bucle - una declaración.

La expresión que controla la repetición de una instrucción debe ser de tipo booleano. Se evalúa antes de que se ejecute la instrucción interna. La instrucción interna se ejecuta repetidamente siempre que la expresión se evalúe como verdadera. Si la expresión se evalúa como Falsa desde el principio, entonces la declaración contenida dentro de la declaración del bucle de condición previa no se ejecuta.

Instrucción de bucle con condición posterior

En una sentencia de bucle con una condición posterior (que comienza con la palabra repetir), la expresión que controla la ejecución repetida de una secuencia de sentencias está contenida dentro de la sentencia de repetición. Forma del ciclo:

repetir S hasta B;

donde B es una condición lógica, cuya verdad se verifica (es una condición para terminar el bucle);

S: una o más declaraciones de cuerpo de ciclo.

El resultado de la expresión debe ser de tipo booleano. Las declaraciones encerradas entre las palabras clave repetir y hasta se ejecutan secuencialmente hasta que el resultado de la expresión se evalúe como Verdadero. La secuencia de instrucciones se ejecutará al menos una vez porque la expresión se evalúa después de cada ejecución de la secuencia de instrucciones.

Lección № 3. Procedimientos y funciones

1. El concepto de un algoritmo auxiliar

Un algoritmo para resolver un problema se diseña descomponiendo el problema completo en subtareas separadas. Normalmente, las subtareas se implementan como subrutinas.

Una subrutina es un algoritmo auxiliar que se usa repetidamente en el algoritmo principal con diferentes valores de algunas cantidades entrantes, llamadas parámetros.

Una subrutina en lenguajes de programación es una secuencia de instrucciones que se definen y escriben en un solo lugar del programa, pero que pueden llamarse para su ejecución desde uno o más puntos del programa. Cada subrutina se identifica con un nombre único.

Hay dos tipos de subrutinas en Pascal, procedimientos y funciones. Un procedimiento y una función son una secuencia con nombre de declaraciones y sentencias. Cuando se utilizan procedimientos o funciones, el programa debe contener el texto del procedimiento o función y una llamada al procedimiento o función. Los parámetros especificados en la descripción se denominan formales, los especificados en la llamada a la subrutina se denominan reales. Todos los parámetros formales se pueden dividir en las siguientes categorías:

1) parámetros-variables;

2) parámetros constantes;

3) valores de parámetros;

4) parámetros de procedimiento y parámetros de función, es decir, parámetros de tipo procedimental;

5) parámetros variables sin tipo.

Los textos de procedimientos y funciones se colocan en la sección de descripciones de procedimientos y funciones.

Pasar nombres de procedimientos y funciones como parámetros

En muchos problemas, especialmente en matemáticas computacionales, es necesario pasar los nombres de procedimientos y funciones como parámetros. Para hacer esto, TURBO PASCAL introdujo un nuevo tipo de datos: procedimental o funcional, según lo que se describa. (Los tipos de funciones y procedimientos se describen en la sección de declaración de tipos).

Una función y tipo de procedimiento se define como el encabezado de un procedimiento y una función con una lista de parámetros formales pero sin nombre. Es posible definir una función o tipo de procedimiento sin parámetros, por ejemplo:

tipo

Proc = procedimiento;

Después de declarar un tipo procedimental o funcional, se puede usar para describir parámetros formales: los nombres de procedimientos y funciones. Además, es necesario escribir aquellos procedimientos o funciones reales cuyos nombres se pasarán como parámetros reales.

2. Procedimientos en Pascal

Cada descripción de procedimiento contiene un encabezado seguido de un bloque de programa. La forma general del encabezado del procedimiento es la siguiente:

Procedimiento <nombre> [(<lista de parámetros formales>)];

Un procedimiento se activa con una instrucción de procedimiento que contiene el nombre del procedimiento y los parámetros necesarios. Las sentencias que se ejecutarán cuando se ejecute el procedimiento están contenidas en la parte de sentencia del módulo de procedimiento. Si una declaración contenida en un procedimiento usa un identificador de procedimiento dentro de un módulo de procedimiento, entonces el procedimiento se ejecutará recursivamente, es decir, se referirá a sí mismo cuando se ejecute.

3. Funciones en Pascal

Una declaración de función define la parte del programa en la que se calcula y devuelve el valor. La forma general del encabezado de la función es la siguiente:

Función <nombre> [(<lista de parámetros formales>)]: <tipo de retorno>;

La función se activa cuando se llama. Cuando se llama a una función, se especifica el identificador de la función y los parámetros necesarios para su evaluación. Una llamada de función se puede incluir en expresiones como un operando. Cuando se evalúa la expresión, se ejecuta la función y el valor del operando se convierte en el valor devuelto por la función.

La parte del operador del bloque de funciones especifica las declaraciones que deben ejecutarse cuando se activa la función. Un módulo debe contener al menos una instrucción de asignación que asigne un valor a un identificador de función. El resultado de la función es el último valor asignado. Si no existe tal declaración de asignación, o si no se ha ejecutado, entonces el valor de retorno de la función no está definido.

Si se usa un identificador de función al llamar a una función dentro de un módulo, entonces la función se ejecuta recursivamente.

4. Reenviar descripciones y conexión de subrutinas. Directiva

Un programa puede contener varias subrutinas, es decir, la estructura del programa puede ser complicada. Sin embargo, estas subrutinas pueden estar en el mismo nivel de anidamiento, por lo que la declaración de la subrutina debe aparecer primero y luego la llamada, a menos que se use una declaración de avance especial.

Una declaración de procedimiento que contiene una directiva de reenvío en lugar de un bloque de instrucciones se denomina declaración de reenvío. En algún momento después de esta declaración, se debe definir un procedimiento por medio de una declaración de definición. Una declaración definitoria es aquella que usa el mismo identificador de procedimiento pero omite la lista de parámetros formales e incluye un bloque de declaración. La declaración directa y la declaración de definición deben aparecer en la misma parte de las declaraciones de procedimiento y función. Entre ellos, se pueden declarar otros procedimientos y funciones que pueden hacer referencia al procedimiento de declaración directa. Por lo tanto, la recursividad mutua es posible.

La descripción directa y la descripción definitoria son la descripción completa del procedimiento. Se considera que el procedimiento se describe utilizando la descripción directa.

Si el programa contiene muchas subrutinas, el programa dejará de ser visual, será difícil navegar en él. Para evitar esto, algunas rutinas se almacenan como archivos fuente en el disco y, si es necesario, se conectan al programa principal en la etapa de compilación mediante una directiva de compilación.

Una directiva es un comentario especial que se puede colocar en cualquier parte de un programa, donde puede estar un comentario normal. Sin embargo, difieren en que la directiva tiene una notación especial: inmediatamente después del paréntesis de cierre sin espacio, se escribe el signo S, y luego, nuevamente sin espacio, se indica la directiva.

ejemplo

1) {SE+} - emular coprocesador matemático;

2) {SF+} - forma un tipo distante de procedimiento y llamada de función;

3) {SN+} - usar coprocesador matemático;

4) {SR+}: compruebe si los rangos están fuera de los límites.

Algunos conmutadores de compilación pueden contener un parámetro, por ejemplo:

{$1 nombre de archivo}: incluye el archivo con nombre en el texto del programa compilado.

LECCIÓN N° 4. Subrutinas

1. Parámetros del subprograma

La descripción de un procedimiento o función especifica una lista de parámetros formales. Cada parámetro declarado en una lista de parámetros formales es local para el procedimiento o la función descritos y puede ser referenciado en el módulo asociado con ese procedimiento o función por su identificador.

Hay tres tipos de parámetros: valor, variable y variable sin tipo. Se caracterizan de la siguiente manera.

1. Un grupo de parámetros sin una palabra clave anterior es una lista de parámetros de valor.

2. Un grupo de parámetros precedido por la palabra clave const y seguido por un tipo es una lista de parámetros constantes.

3. Un grupo de parámetros precedidos por la palabra clave var y seguidos por un tipo es una lista de parámetros de variables sin tipo.

4. Un grupo de parámetros precedidos por la palabra clave var o const y no seguidos por un tipo es una lista de parámetros de variables sin tipo.

2. Tipos de parámetros de subrutina

Parámetros de valor

Un parámetro de valor formal se trata como una variable local del procedimiento o la función, excepto que deriva su valor inicial del parámetro real correspondiente cuando se invoca el procedimiento o la función. Los cambios que sufre un parámetro de valor formal no afectan el valor del parámetro real. El valor del parámetro de valor real correspondiente debe ser una expresión y su valor no debe ser un tipo de archivo ni ningún tipo de estructura que contenga un tipo de archivo.

El parámetro real debe ser de un tipo cuya asignación sea compatible con el tipo del parámetro de valor formal. Si el parámetro es de tipo cadena, el parámetro formal tendrá un atributo de tamaño de 255.

Parámetros constantes

Los parámetros constantes formales funcionan de manera similar a una variable local de solo lectura que obtiene su valor cuando se invoca un procedimiento o función desde el parámetro real correspondiente. No se permiten asignaciones a un parámetro constante formal. Un parámetro constante formal tampoco se puede pasar como un parámetro real a otro procedimiento o función. Un parámetro constante correspondiente a un parámetro real en una declaración de procedimiento o función debe seguir las mismas reglas que el valor del parámetro real.

En los casos en que un parámetro formal no cambie su valor durante la ejecución de un procedimiento o función, se debe usar un parámetro constante en lugar de un parámetro de valor. Los parámetros constantes permiten la implementación de un procedimiento o función para proteger contra asignaciones accidentales a un parámetro formal. Además, para los parámetros de tipo estructura y cadena, el compilador puede generar un código más eficiente cuando se usa en lugar de parámetros de valor para parámetros constantes.

Parámetros variables

Un parámetro de variable se utiliza cuando se debe pasar un valor de un procedimiento o función al programa de llamada. El parámetro real correspondiente en una declaración de llamada de función o procedimiento debe ser una referencia variable. Cuando se invoca un procedimiento o función, la variable de parámetro formal se reemplaza por la variable real, cualquier cambio en el valor de la variable de parámetro formal se refleja en el parámetro real.

Dentro de un procedimiento o función, cualquier referencia a un parámetro variable formal da como resultado el acceso al parámetro real en sí. El tipo del parámetro real debe coincidir con el tipo del parámetro variable formal, pero esta restricción se puede eludir mediante el uso de un parámetro variable sin tipo).

Parámetros sin tipo

Cuando el parámetro formal es un parámetro variable sin tipo, el parámetro real correspondiente puede ser cualquier referencia a una variable o constante, independientemente de su tipo. Un parámetro sin tipo declarado con la palabra clave var se puede modificar, mientras que un parámetro sin tipo declarado con la palabra clave const es de solo lectura.

En un procedimiento o función, un parámetro de variable sin tipo no tiene tipo, es decir, es incompatible con variables de todos los tipos hasta que se le asigna un tipo específico mediante la asignación de tipo de variable.

Aunque los parámetros sin tipo proporcionan más flexibilidad, existen algunos riesgos asociados con su uso. El compilador no puede verificar la validez de las operaciones en variables sin tipo.

variables de procedimiento

Después de definir un tipo de procedimiento, es posible describir variables de este tipo. Estas variables se denominan variables de procedimiento. Al igual que una variable entera a la que se le puede asignar un valor de un tipo entero, a una variable de procedimiento se le puede asignar un valor de un tipo de procedimiento. Por supuesto, dicho valor podría ser otra variable de procedimiento, pero también podría ser un identificador de procedimiento o función. En este contexto, la declaración de un procedimiento o función puede verse como una descripción de un tipo especial de constante cuyo valor es el procedimiento o la función.

Como con cualquier otra asignación, los valores de la variable del lado izquierdo y del lado derecho deben ser compatibles con la asignación. Los tipos procedimentales, para que sean compatibles con la asignación, deben tener el mismo número de parámetros, y los parámetros en las posiciones correspondientes deben ser del mismo tipo. Los nombres de parámetros en una declaración de tipo procedimental no tienen efecto.

Además, para garantizar la compatibilidad de la asignación, un procedimiento o función, si se va a asignar a una variable de procedimiento, debe cumplir los siguientes requisitos:

1) no debe ser un procedimiento o función estándar;

2) dicho procedimiento o función no se puede anidar;

3) dicho procedimiento no debe ser un procedimiento en línea;

4) no debe ser un procedimiento de interrupción.

Los procedimientos y funciones estándar son los procedimientos y funciones descritos en el módulo Sistema, como Writeln, Readln, Chr, Ord. No se pueden utilizar funciones y procedimientos anidados con variables de procedimiento. Un procedimiento o función se considera anidado cuando se declara dentro de otro procedimiento o función.

El uso de tipos procedimentales no se limita solo a variables procedimentales. Como cualquier otro tipo, un tipo procedimental puede participar en la declaración de un tipo estructural.

Cuando a una variable de procedimiento se le asigna el valor de un procedimiento, lo que sucede en la capa física es que la dirección del procedimiento se almacena en la variable. De hecho, una variable de procedimiento es muy similar a una variable de puntero, solo que en lugar de referirse a datos, apunta a un procedimiento o función. Como un puntero, una variable de procedimiento ocupa 4 bytes (dos palabras) que contienen una dirección de memoria. La primera palabra almacena el desplazamiento, la segunda palabra almacena el segmento.

Parámetros de tipo de procedimiento

Dado que los tipos procedimentales se pueden usar en cualquier contexto, es posible describir procedimientos o funciones que toman procedimientos y funciones como parámetros. Los parámetros de tipo de procedimiento son especialmente útiles cuando necesita realizar alguna acción común en varios procedimientos o funciones.

Si se va a pasar un procedimiento o una función como parámetro, debe seguir las mismas reglas de compatibilidad de tipos que la asignación. Es decir, dichos procedimientos o funciones deben compilarse con la directiva far, no pueden ser funciones integradas, no pueden anidarse y no pueden describirse con los atributos en línea o de interrupción.

LECCIÓN n.º 5. Tipo de datos de cadena

1. Tipo de cadena en Pascal

Una secuencia de caracteres de cierta longitud se denomina cadena. Las variables de tipo cadena se definen especificando el nombre de la variable, la palabra reservada cadena y, opcionalmente, pero no necesariamente, especificando el tamaño máximo, es decir, la longitud de la cadena, entre corchetes. Si no establece el tamaño máximo de cadena, de forma predeterminada será 255, es decir, la cadena constará de 255 caracteres.

Se puede hacer referencia a cada elemento de una cadena por su número. Sin embargo, la entrada y salida de cadenas se realiza como un todo, y no elemento por elemento, como es el caso de las matrices. El número de caracteres ingresados ​​no debe exceder el especificado en el tamaño máximo de la cadena, por lo que si ocurre tal exceso, los caracteres "extra" serán ignorados.

2. Procedimientos y funciones para variables de tipo cadena

1. Función Copiar (S: Cadena; Índice, Recuento: Entero): Cadena;

Devuelve una subcadena de una cadena. S es una expresión de tipo String.

Index y Count son expresiones de tipo entero. La función devuelve una cadena que contiene caracteres de recuento que comienzan en la posición de índice. Si Index es mayor que la longitud de S, la función devuelve una cadena vacía.

2. Eliminar procedimiento (var S: cadena; índice, recuento: entero);

Elimina una subcadena de caracteres de longitud Count de la cadena S, comenzando en la posición Index. S es una variable de tipo String. Index y Count son expresiones de tipo entero. Si el índice es mayor que la longitud de S, no se elimina ningún carácter.

3. Insertar procedimiento (Fuente: Cadena; var S: Cadena; Índice: Entero);

Concatena una subcadena en una cadena, comenzando en una posición especificada. Source es una expresión de tipo String. S es una variable de tipo String de cualquier longitud. El índice es una expresión de tipo entero. Insertar inserta Fuente en S, comenzando en la posición S[Índice].

4. Longitud de la función (S: Cadena): Entero;

Devuelve el número de caracteres realmente utilizados en la cadena S. Tenga en cuenta que cuando se utilizan cadenas terminadas en cero, el número de caracteres no es necesariamente igual al número de bytes.

5. Función Pos(Substr: Cadena; S: Cadena): Entero;

Busca una subcadena en una cadena. Pos busca Substr dentro de S y devuelve un valor entero que es el índice del primer carácter de Substr dentro de S. Si no se encuentra Substr, Pos devuelve nulo.

3. Grabaciones

Un registro es una colección de un número limitado de componentes lógicamente relacionados que pertenecen a diferentes tipos. Los componentes de un registro se denominan campos, cada uno de los cuales se identifica con un nombre. Un campo de registro contiene el nombre del campo, seguido de dos puntos para indicar el tipo de campo. Los campos de registro pueden ser de cualquier tipo permitido en Pascal, con la excepción del tipo de archivo.

La descripción de un registro en lenguaje Pascal se realiza mediante la palabra de servicio RECORD, seguida de la descripción de los componentes del registro. La descripción de la entrada termina con la palabra de servicio FIN.

Por ejemplo, una libreta contiene apellidos, iniciales y números de teléfono, por lo que es conveniente representar una línea separada en una libreta como la siguiente entrada:

tipo Fila = Registro

FIO: Cadena[20];

TEL: Cadena[7];

fin;

var str: Fila;

Las descripciones de registros también son posibles sin usar el nombre del tipo, por ejemplo:

var str : Registro

FIO: Cadena [20];

TEL : Cadena[7];

fin;

Solo se permite hacer referencia a un registro como un todo en declaraciones de asignación donde se usan nombres de registro del mismo tipo a la izquierda y a la derecha del signo de asignación. En todos los demás casos, se operan campos separados de registros. Para hacer referencia a un componente de registro individual, debe especificar el nombre del registro y, a través de un punto, especificar el nombre del campo deseado. Tal nombre se llama nombre compuesto. Un componente de registro también puede ser un registro, en cuyo caso el nombre distinguido contendrá no dos, sino más nombres.

La referencia a los componentes del registro se puede simplificar utilizando el operador with append. Le permite reemplazar los nombres compuestos que caracterizan cada campo con solo nombres de campo y definir el nombre del registro en la declaración de combinación.

A veces, el contenido de un registro individual depende del valor de uno de sus campos. En el lenguaje Pascal, se permite una descripción de registro, que consta de partes comunes y variantes. La parte variante se especifica utilizando el caso P de constructo, donde P es el nombre del campo de la parte común del registro. Los posibles valores aceptados por este campo se enumeran de la misma manera que en la declaración de variante. Sin embargo, en lugar de especificar la acción a realizar, como se hace en una declaración de variante, los campos de variante se especifican entre paréntesis. La descripción de la parte variante termina con la palabra de servicio fin. El tipo de campo P se puede especificar en el encabezado de la parte variante. Los registros se inicializan usando constantes escritas.

4. Conjuntos

El concepto de conjunto en el lenguaje Pascal se basa en el concepto matemático de conjunto: es una colección limitada de diferentes elementos. Un tipo de datos enumerado o de intervalo se utiliza para construir un tipo de conjunto concreto. El tipo de elementos que componen un conjunto se denomina tipo base.

Un tipo múltiple se describe utilizando el Conjunto de palabras de función, por ejemplo:

tipo M = Conjunto de B;

Aquí M es el tipo plural, B es el tipo base.

La pertenencia de las variables a un tipo plural se puede determinar directamente en la sección de declaración de variables.

Las constantes de tipo conjunto se escriben como una secuencia entre paréntesis de elementos o intervalos de tipo base, separados por comas. Una constante de la forma [] significa un subconjunto vacío.

Un conjunto incluye un conjunto de elementos del tipo base, todos los subconjuntos del conjunto dado y el subconjunto vacío. Si el tipo base sobre el que se construye el conjunto tiene K elementos, entonces el número de subconjuntos incluidos en este conjunto es igual a 2 elevado a K. El orden en que se enumeran los elementos del tipo base en las constantes es indiferente . El valor de una variable de tipo múltiple puede estar dado por una construcción de la forma [T], donde T es una variable de tipo base.

Las operaciones de asignación (:=), unión (+), intersección (*) y resta (-) son aplicables a variables y constantes de tipo conjunto. El resultado de estas operaciones es un valor del tipo plural:

1) ['A','B'] + ['A','D'] dará ['A','B','D'];

2) ['A'] * ['A','B','C'] dará ['A'];

3) ['A','B','C'] - ['A','B'] dará ['C'].

Las siguientes operaciones son aplicables a múltiples valores: identidad (=), no identidad (<>), contenida en (<=), contiene (>=). El resultado de estas operaciones tiene un tipo booleano:

1) ['A','B'] = ['A','C'] dará FALSO;

2) ['A','B'] <> ['A','C'] dará VERDADERO;

3) ['B'] <= ['B','C'] dará VERDADERO;

4) ['C','D'] >= ['A'] dará FALSO.

Además de estas operaciones, para trabajar con valores de tipo set, se utiliza la operación in, que comprueba si el elemento del tipo base a la izquierda del signo de operación pertenece al conjunto a la derecha del signo de operación . El resultado de esta operación es un booleano. La operación de verificar si un elemento pertenece a un conjunto se usa a menudo en lugar de las operaciones relacionales.

Cuando se utilizan varios tipos de datos en los programas, las operaciones se realizan en cadenas de bits de datos. Cada valor del tipo múltiple en la memoria de la computadora corresponde a un dígito binario.

Los valores de un tipo múltiple no pueden ser elementos de una lista de E/S. En cada implementación concreta del compilador del lenguaje Pascal, se limita el número de elementos del tipo base sobre los que se construye el conjunto.

La inicialización de múltiples valores de tipo se realiza mediante constantes tipadas.

Estos son algunos procedimientos para trabajar con conjuntos.

1. Procedimiento Excluir(var S: Conjunto de T; I:T);

Elimina el elemento I del conjunto S. S es una variable de tipo "conjunto" e I es una expresión de un tipo compatible con el tipo original de S. Exclude(S, I) es lo mismo que S : = S - [I] , pero genera un código más eficiente.

2. Procedimiento Incluir(var S: Conjunto de T; I:T);

Agrega un elemento I al conjunto S. S es una variable de tipo "conjunto" e I es una expresión de un tipo compatible con el tipo S. La construcción Incluir(S, I) es la misma que S : = S + [ I], pero genera un código más eficiente.

CONFERENCIA N° 6. Archivos

1. Archivos. Operaciones de archivo

La introducción del tipo de archivo en el lenguaje Pascal se debe a la necesidad de brindar la capacidad de trabajar con dispositivos informáticos periféricos (externos) diseñados para la entrada, salida y almacenamiento de datos.

El tipo de datos de archivo (o archivo) define una colección ordenada de un número arbitrario de componentes del mismo tipo. La propiedad común de una matriz, un conjunto y un registro es que el número de sus componentes se determina en la etapa de escritura del programa, mientras que el número de componentes del archivo en el texto del programa no se determina y puede ser arbitrario.

Cuando se trabaja con archivos, se realizan operaciones de E/S. Una operación de entrada significa transferir datos desde un dispositivo externo (desde un archivo de entrada) a la memoria principal de una computadora, una operación de salida es una transferencia de datos desde la memoria principal a un dispositivo externo (a un archivo de salida). Los archivos en dispositivos externos a menudo se denominan archivos físicos. Sus nombres están determinados por el sistema operativo.

En los programas Pascal, los nombres de archivo se especifican mediante cadenas. Para trabajar con archivos en el programa, debe definir una variable de archivo. Pascal admite tres tipos de archivos: archivos de texto, archivos de componentes y archivos sin tipo.

Las variables de archivo que se declaran en un programa se denominan archivos lógicos. Todos los procedimientos y funciones básicos que proporcionan datos de E/S funcionan solo con archivos lógicos. El archivo físico debe asociarse con el archivo lógico antes de que se puedan realizar los procedimientos de apertura de archivos.

Archivos de texto

Un lugar especial en el lenguaje Pascal lo ocupan los archivos de texto, cuyos componentes son de tipo carácter. Para describir archivos de texto, el lenguaje define el tipo estándar Texto:

var TF1, TF2: Texto;

Los archivos de texto son una secuencia de líneas y las líneas son una secuencia de caracteres. Las líneas son de longitud variable, cada línea termina con un terminador de línea.

Archivos de componentes

Un componente o archivo tipado es un archivo con el tipo declarado de sus componentes. Los archivos de componentes consisten en representaciones de máquinas de valores variables; almacenan datos en la misma forma que la memoria de la computadora.

La descripción de los valores del tipo de archivo es:

tipo M = Archivo De T;

donde M es el nombre del tipo de archivo;

T - tipo de componente.

Los componentes del archivo pueden ser de todos los tipos escalares y de tipos estructurados: matrices, conjuntos, registros. En casi todas las implementaciones específicas del lenguaje Pascal, la construcción "archivo de archivos" no está permitida.

Todas las operaciones en los archivos de componentes se realizan mediante procedimientos estándar.

Escribir(f,X1,X2,...XK)

Archivos sin tipo

Los archivos sin tipo le permiten escribir secciones arbitrarias de la memoria de la computadora en el disco y leerlas del disco a la memoria. Los archivos sin tipo se describen a continuación:

varf: Archivo;

Ahora enumeramos los procedimientos y funciones para trabajar con diferentes tipos de archivos.

1. Asignación de procedimiento (var F; FileName: String);

El procedimiento AssignFile asigna un nombre de archivo externo a una variable de archivo.

F es una variable de archivo de cualquier tipo de archivo, FileName es una expresión de cadena o una expresión PChar si se permite la sintaxis extendida. Todas las demás operaciones con F se realizan con un archivo externo.

No puede usar un procedimiento con una variable de archivo ya abierta.

2. Procedimiento Cerrar (varF);

El procedimiento rompe el vínculo entre la variable de archivo y el archivo del disco externo y cierra el archivo.

F es una variable de archivo de cualquier tipo de archivo, abierta por los procedimientos Restablecer, Reescribir o Agregar. El archivo externo asociado con F se modifica por completo y luego se cierra, liberando el descriptor de archivo para su reutilización.

La directiva {SI+} le permite manejar errores durante la ejecución del programa mediante el manejo de excepciones. Con la directiva {$1-} desactivada, debe usar IOResult para comprobar si hay errores de E/S.

3.Función Eof(var F): Booleano;

{Archivos con o sin tipo}

Función Eof[(var F: Texto)]: Booleano;

{archivos de texto}

Comprueba si la posición actual del archivo es o no el final del archivo.

Eof(F) devuelve True si la posición actual del archivo está después del último carácter del archivo, o si el archivo está vacío; de lo contrario, Eof(F) devuelve False.

La directiva {SI+} le permite manejar errores durante la ejecución del programa mediante el manejo de excepciones. Con la directiva {SI-} desactivada, debe utilizar IOResult para comprobar si hay errores de E/S.

4. Procedimiento de borrado (var F);

Elimina el archivo externo asociado con F.

F es una variable de archivo de cualquier tipo de archivo.

Antes de llamar al procedimiento Erase, el archivo debe estar cerrado.

La directiva {SI+} le permite manejar errores durante la ejecución del programa mediante el manejo de excepciones. Con la directiva {SI-} desactivada, debe utilizar IOResult para comprobar si hay errores de E/S.

5. Función FileSize(var F): Integer;

Devuelve el tamaño en bytes del archivo F. Sin embargo, si F es un archivo con tipo, FileSize devolverá el número de registros del archivo. El archivo debe estar abierto antes de usar la función FileSize. Si el archivo está vacío, FileSize(F) devuelve cero. F es una variable de cualquier tipo de archivo.

6. Función FilePos (var F): LongInt;

Devuelve la posición actual de un archivo dentro de un archivo.

Antes de utilizar la función FilePos, el archivo debe estar abierto. La función FilePos no se utiliza con archivos de texto. F es una variable de cualquier tipo de archivo, excepto del tipo Texto.

7. Restablecimiento del procedimiento (var F [: Archivo; RecSize: Word]);

Abre un archivo existente.

F es una variable de cualquier tipo de archivo asociado con un archivo externo mediante AssignFile. RecSize es una expresión opcional que se usa si F es un archivo sin tipo. Si F es un archivo sin tipo, RecSize determina el tamaño de registro que se utiliza al transferir datos. Si se omite RecSize, el tamaño de registro predeterminado es de 128 bytes.

El procedimiento Restablecer abre un archivo externo existente asociado con la variable de archivo F. Si no hay ningún archivo externo con ese nombre, se produce un error de tiempo de ejecución. Si el archivo asociado con F ya está abierto, primero se cierra y luego se vuelve a abrir. La posición actual del archivo se establece al principio del archivo.

8. Reescritura del procedimiento (var F: Archivo [; Recsize: Word]);

Crea y abre un nuevo archivo.

F es una variable de cualquier tipo de archivo asociado con un archivo externo mediante AssignFile. RecSize es una expresión opcional que se usa si F es un archivo sin tipo. Si F es un archivo sin tipo, RecSize determina el tamaño de registro que se utiliza al transferir datos. Si se omite RecSize, el tamaño de registro predeterminado es de 128 bytes.

El procedimiento de Reescritura crea un nuevo archivo externo con el nombre asociado a F. Si ya existe un archivo externo con el mismo nombre, se elimina y se crea un nuevo archivo vacío.

9. Búsqueda de procedimiento (var F; N: LongInt);

Mueve la posición del archivo actual al componente especificado. Solo puede utilizar el procedimiento con archivos abiertos escritos o sin escribir.

La posición actual del archivo F se mueve al número N. El número del primer componente del archivo es 0.

La instrucción Seek(F, FileSize(F)) mueve la posición del archivo actual al final del archivo.

10. Procedimiento Agregar (var F: Texto);

Abre un archivo de texto existente para agregar información al final del archivo (agregar).

Si no existe un archivo externo con el nombre dado, se produce un error de tiempo de ejecución. Si el archivo F ya está abierto, se cierra y se vuelve a abrir. La posición actual del archivo se establece al final del archivo.

11.Función Eoln[(var F: Texto)]: Booleano;

Comprueba si la posición actual del archivo es el final de una línea en un archivo de texto.

Eoln(F) devuelve True si la posición actual del archivo está al final de una línea o archivo; de lo contrario, Eoln(F) devuelve False.

12. Procedimiento Lectura(F, V1 [, V2,..., Vn]);

{Archivos escritos y sin escribir}

Procedimiento Read([var F: Texto;] V1 [, V2,..., Vn]);

{archivos de texto}

Para archivos escritos, el procedimiento lee el componente del archivo en una variable. En cada lectura, la posición actual en el archivo avanza al siguiente elemento.

Para archivos de texto, uno o más valores se leen en una o más variables.

Con las variables de cadena, Read lee todos los caracteres hasta (pero sin incluir) el siguiente marcador de fin de línea, o hasta que Eof(F) se evalúe como True. La cadena de caracteres resultante se asigna a la variable.

En el caso de una variable de tipo entero o real, el procedimiento espera una secuencia de caracteres que forman un número según las reglas de la sintaxis de Pascal. La lectura se detiene cuando se encuentra el primer espacio, tabulador o final de línea, o cuando Eof(F) se evalúa como True. Si la cadena numérica no coincide con el formato esperado, se produce un error de E/S.

13. Procedimiento Readln([var F: Texto;] V1 [, V2..., Vn]);

Es una extensión del procedimiento de lectura y está definido para archivos de texto. Lee una cadena de caracteres en el archivo, incluido el marcador de final de línea, y se mueve al principio de la siguiente línea. Llamar a la función Readln(F) sin parámetros mueve la posición actual del archivo al comienzo de la siguiente línea, si hay una, de lo contrario, salta al final del archivo.

14. Función SeekEof[(var F: Texto)]: Booleano;

Devuelve el final del archivo y solo se puede usar para archivos de texto abiertos. Normalmente se utiliza para leer valores numéricos de archivos de texto.

15. Función SeekEoln[(var F: Texto)]: Booleano;

Devuelve el terminador de línea en un archivo y solo se puede usar para archivos de texto abiertos. Normalmente se utiliza para leer valores numéricos de archivos de texto.

16. Procedimiento Escribir([var F: Texto;] P1 [, P2,..., Pn]);

{archivos de texto}

Escribe uno o más valores en un archivo de texto.

Cada parámetro de entrada debe ser del tipo Char, uno de los tipos enteros (Byte, ShortInt, Word, Longint, Cardinal), uno de los tipos de punto flotante (Single, Real, Double, Extended, Currency), uno de los tipos de cadena ( PChar, AisiString, ShortString), o uno de los tipos booleanos (Boolean, Bool).

Procedimiento Escritura(F, V1,..., Vn);

{Archivos escritos}

Escribe una variable en un componente de archivo. Las variables VI...., Vn deben ser del mismo tipo que los elementos del archivo. Cada vez que se escribe una variable, la posición actual en el archivo se mueve al siguiente elemento.

17. Procedimiento Writeln([var F: Texto;] [P1, P2,..., Pn]);

{archivos de texto}

Realiza una operación de escritura y luego coloca un marcador de fin de línea en el archivo.

Llamar a Writeln(F) sin parámetros escribe un marcador de fin de línea en el archivo. El archivo debe estar abierto para la salida.

2. Módulos. Tipos de módulos

Un módulo (1Ж1Т) en Pascal es una biblioteca de subrutinas especialmente diseñada. Un módulo, a diferencia de un programa, no puede ejecutarse por sí solo, solo puede participar en la construcción de programas y otros módulos. Los módulos le permiten crear bibliotecas personales de procedimientos y funciones y crear programas de casi cualquier tamaño.

Un módulo en Pascal es una unidad de programa compilada de forma independiente y almacenada por separado. En general, un módulo es una colección de recursos de software destinados a ser utilizados por otros programas. Los recursos del programa se entienden como cualquier elemento del lenguaje Pascal: constantes, tipos, variables, subrutinas. El módulo en sí no es un programa ejecutable, sus elementos son utilizados por otras unidades de programa.

Todos los elementos del programa del módulo se pueden dividir en dos partes:

1) elementos de programa destinados a ser utilizados por otros programas o módulos, dichos elementos se denominan visibles fuera del módulo;

2) elementos de software que son necesarios solo para el funcionamiento del módulo en sí, se denominan invisibles (u ocultos).

De acuerdo con esto, el módulo, además del encabezado, contiene tres partes principales, llamadas interfaz, ejecutable e inicializado.

En general, un módulo tiene la siguiente estructura:

unidad <nombre del módulo>; {Título de módulo}

interfaz.

{descripción de los elementos de programa visibles del módulo}

implementación

{descripción de los elementos de programación ocultos del módulo}

comenzar

{declaraciones de inicialización del elemento del módulo}

fin.

En un caso particular, el módulo puede no contener una parte de implementación y una parte de inicialización, entonces la estructura del módulo será la siguiente:

unidad <nombre del módulo>; {Título de módulo}

interfaz.

{descripción de los elementos de programa visibles del módulo}

implementación

fin.

El uso de procedimientos y funciones en módulos tiene sus propias peculiaridades. El encabezado de la subrutina contiene toda la información necesaria para llamarla: nombre, lista y tipo de parámetros, tipo de resultado para funciones. Esta información debe estar disponible para otros programas y módulos. Por otra parte, el texto de una subrutina que implementa su algoritmo no puede ser utilizado por otros programas y módulos. Por lo tanto, los encabezados de procedimientos y funciones se colocan en la parte de interfaz del módulo y el texto se coloca en la parte de implementación.

La parte de la interfaz del módulo contiene solo encabezados visibles (accesibles para otros programas y módulos) de procedimientos y funciones (sin el reenvío de palabra de servicio). El texto completo del procedimiento o función se coloca en la parte de implementación y el encabezado puede no contener una lista de parámetros formales.

El código fuente del módulo debe compilarse utilizando la directiva Make del submenú Compile y escribirse en el disco. El resultado de la compilación del módulo es un archivo con extensión . TPU (Unidad Turbo Pascal). El nombre base del módulo se toma del encabezado del módulo.

Para conectar un módulo al programa, debe especificar su nombre en la sección de descripción del módulo, por ejemplo:

usa Crt, Graph;

En caso de que los nombres de las variables en la parte de la interfaz del módulo y en el programa que usa este módulo sean los mismos, la referencia será a la variable descrita en el programa. Para hacer referencia a una variable declarada en un módulo, debe utilizar un nombre compuesto formado por el nombre del módulo y el nombre de la variable, separados por un punto. El uso de nombres compuestos se aplica no solo a los nombres de variables, sino a todos los nombres declarados en la parte de la interfaz del módulo.

El uso recursivo de módulos está prohibido.

Si un módulo tiene una sección de inicialización, las declaraciones en esa sección se ejecutarán antes de que el programa que usa ese módulo comience a ejecutarse.

Hagamos una lista de los tipos de módulos.

1. Módulo SISTEMA.

El módulo SYSTEM implementa rutinas de soporte de nivel inferior para todas las funciones integradas, como E/S, manipulación de cadenas, operaciones de coma flotante y asignación de memoria dinámica.

El módulo SYSTEM contiene todas las funciones y rutinas Pascal estándar e integradas. Cualquier subrutina Pascal que no sea parte del Pascal estándar y que no se encuentre en ningún otro módulo está contenida en el módulo Sistema. Esta unidad se usa automáticamente en todos los programas y no es necesario especificarla en la declaración de usos.

2. Módulo DOS.

El módulo DOS implementa numerosas rutinas y funciones de Pascal que son equivalentes a las llamadas de DOS más utilizadas, como GetTime, SetTime, DiskSize, etc.

3. Módulo CRT.

El módulo CRT implementa una serie de potentes programas que brindan control total sobre las características de la PC, como el control del modo de pantalla, códigos de teclado extendidos, colores, ventanas y sonidos. El módulo CRT solo se puede usar en programas que se ejecutan en computadoras personales IBM PC, PC AT, PS / 2 de IBM y son totalmente compatibles con ellos.

Una de las principales ventajas de utilizar el módulo CRT es una mayor velocidad y flexibilidad en la realización de operaciones de pantalla. Los programas que no funcionan con el módulo CRT muestran información en la pantalla utilizando el sistema operativo DOS, lo que está asociado con una sobrecarga adicional. Cuando se utiliza el módulo CRT, la información de salida se envía directamente al sistema básico de entrada/salida (BIOS) o, para operaciones aún más rápidas, directamente a la memoria de video.

4. Módulo GRÁFICO.

Usando los procedimientos y funciones incluidos en este módulo, puede crear varios gráficos en la pantalla.

5. Módulo SUPERPOSICIÓN.

El módulo OVERLAY le permite reducir los requisitos de memoria de un programa DOS en modo real. De hecho, es posible escribir programas que excedan la cantidad total de memoria disponible, ya que solo una parte del programa estará en la memoria en un momento dado.

CONFERENCIA N° 7. Memoria dinámica

1. Tipo de datos de referencia. memoria dinámica. Variables dinámicas

Una variable estática (asignada estáticamente) es una variable declarada explícitamente en el programa, se la denomina por su nombre. El lugar en la memoria para colocar variables estáticas se determina cuando se compila el programa. A diferencia de tales variables estáticas, los programas de Pascal pueden crear variables dinámicas. La propiedad principal de las variables dinámicas es que se crean y se les asigna memoria durante la ejecución del programa.

Las variables dinámicas se colocan en un área de memoria dinámica (área de montón). Una variable dinámica no se especifica explícitamente en las declaraciones de variables y no se puede hacer referencia a ella por su nombre. Se accede a dichas variables mediante punteros y referencias.

Un tipo de referencia (puntero) define un conjunto de valores que apuntan a variables dinámicas de un tipo específico, llamado tipo base. Una variable de tipo de referencia contiene la dirección de una variable dinámica en la memoria. Si el tipo base es un identificador no declarado, debe declararse en la misma parte de la declaración de tipo que el tipo de puntero.

La palabra reservada nil denota una constante con un valor de puntero que no apunta a nada.

Pongamos un ejemplo de la descripción de variables dinámicas.

var p1, p2 : ^real;

p3, p4 : ^entero;

2. Trabajo con memoria dinámica. Punteros sin tipo

Procedimientos y funciones de la memoria dinámica

1. Procedimiento Nuevo (var p: Puntero).

Asigna espacio en el área de memoria dinámica para acomodar la variable dinámica pЛ, y asigna su dirección al puntero p.

2. Disposición del procedimiento (varp: puntero).

Libera la memoria asignada para la asignación de variables dinámicas por el procedimiento Nuevo y el valor del puntero p se vuelve indefinido.

3. Procedimiento GetMem(varp: Puntero; tamaño: Palabra).

Asigna una sección de memoria en el área de almacenamiento dinámico, asigna la dirección de su comienzo al puntero p, el tamaño de la sección en bytes se especifica mediante el parámetro de tamaño.

4. Procedimiento FreeMem(var p: Puntero; tamaño: Palabra).

Libera el área de memoria, cuya dirección inicial se especifica mediante el puntero p, y el tamaño se especifica mediante el parámetro de tamaño. El valor del puntero p se vuelve indefinido.

5. Marca de procedimiento (var p: puntero)

Escribe en el puntero p la dirección de inicio de una sección de memoria dinámica libre en el momento de su llamada.

6. Liberación del procedimiento (var p: Puntero)

Libera una parte de la memoria dinámica, a partir de la dirección escrita en el puntero p por el procedimiento Mark, es decir, borra la memoria dinámica que estaba ocupada después de la llamada al procedimiento Mark.

7. Función MaxAvaikLongint

Devuelve la longitud, en bytes, del montón libre más largo.

8. Función MemAvaikLongint

Devuelve la cantidad total de memoria dinámica libre en bytes.

9. Función auxiliar SizeOf(X):Palabra

Devuelve la cantidad de bytes ocupados por X, donde X puede ser un nombre de variable de cualquier tipo o un nombre de tipo.

El puntero de tipo incorporado denota un puntero sin tipo, es decir, un puntero que no apunta a ningún tipo en particular. Las variables de tipo Pointer se pueden desreferenciar: especificar el carácter ^ después de dicha variable provoca un error.

Al igual que el valor denotado por nil, los valores de puntero son compatibles con todos los demás tipos de puntero.

CONFERENCIA № 8. Estructuras abstractas de datos

1. Estructuras de datos abstractas

Los tipos de datos estructurados, como matrices, conjuntos y registros, son estructuras estáticas porque sus tamaños no cambian durante toda la ejecución del programa.

A menudo se requiere que las estructuras de datos cambien de tamaño en el transcurso de la resolución de un problema. Estas estructuras de datos se denominan dinámicas. Estos incluyen pilas, colas, listas, árboles, etc.

La descripción de estructuras dinámicas usando arreglos, registros y archivos conduce a un desperdicio de la memoria de la computadora y aumenta el tiempo para resolver problemas.

Cada componente de cualquier estructura dinámica es un registro que contiene al menos dos campos: un campo de tipo "puntero" y el segundo, para la ubicación de datos. En general, un registro puede contener no uno, sino varios punteros y varios campos de datos. Un campo de datos puede ser una variable, una matriz, un conjunto o un registro.

Si la parte que apunta contiene la dirección de un elemento de la lista, entonces la lista se llama unidireccional (o enlazada individualmente). Si contiene dos componentes, entonces está doblemente conectado. Puede realizar varias operaciones en las listas, por ejemplo:

1) agregar un elemento a la lista;

2) eliminar un elemento de la lista con una clave determinada;

3) buscar un elemento con un valor dado del campo clave;

4) ordenar los elementos de la lista;

5) división de la lista en dos o más listas;

6) combinar dos o más listas en una sola;

7) otras operaciones.

Sin embargo, como regla general, no surge la necesidad de todas las operaciones para resolver varios problemas. Por lo tanto, dependiendo de las operaciones básicas que se deban aplicar, existen diferentes tipos de listas. Los más populares de estos son la pila y la cola.

2. Pilas

Una pila es una estructura de datos dinámica, la adición de un componente al cual y la eliminación de un componente del cual se hacen desde un extremo, llamado la parte superior de la pila. La pila funciona según el principio LIFO (último en entrar, primero en salir) - "Último en entrar, primero en salir".

Por lo general, se realizan tres operaciones en las pilas:

1) formación inicial de la pila (registro del primer componente);

2) agregar un componente a la pila;

3) selección del componente (supresión).

Para formar una pila y trabajar con ella, debe tener dos variables del tipo "puntero", la primera de las cuales determina la parte superior de la pila y la segunda es auxiliar.

Ejemplo. Escriba un programa que forme una pila, le agregue un número arbitrario de componentes y luego lea todos los componentes y los muestre en la pantalla. Tome una cadena de caracteres como datos. Entrada de datos: desde el teclado, una señal del final de la entrada: una cadena de caracteres FIN.

PILA de programas;

utiliza Crt;

tipo

Alfa = Cadena[10];

PComp = ^Comp;

Comp = registro

SD: Alfa

pSiguiente: PComp

fin;

var

pSuperior: PComp;

SC: Alfa;

Crear pila de procedimientos (var pTop: PComp; var sC: Alfa);

comenzar

Nuevo(pArriba);

pArriba^.pSiguiente := NIL;

pArriba^.sD := sC;

fin;

Agregar ProcedimientoComp(var pTop: PComp; var sC: Alfa);

var pAux: PComp;

comenzar

NUEVO(pAux);

pAux^.pSiguiente := pArriba;

pSuperior := pAux;

pArriba^.sD := sC;

fin;

Procedimiento DelComp(var pTop : PComp; var sC : ALFA);

comenzar

sC := pSuperior^.sD;

pSuperior := pSuperior^.pSiguiente;

fin;

comenzar

Clrscr;

writeln('ENTRAR CADENA');

leer(sC);

CreateStack(pArriba, sc);

repetir

writeln('ENTRAR CADENA');

leer(sC);

AddComp(pArriba, sc);

hasta sC = 'FIN';

writeln('****** SALIDA ******');

repetir

DelComp(pSuperior, sc);

escribir(sC);

hasta pTop = NIL;

fin.

3. Colas

Una cola es una estructura de datos dinámica donde se agrega un componente en un extremo y se recupera en el otro extremo. La cola funciona según el principio FIFO (First-In, First-Out) - "Primero en entrar, primero en ser atendido".

Para formar una cola y trabajar con ella, es necesario tener tres variables del tipo de puntero, la primera de las cuales determina el comienzo de la cola, la segunda, el final de la cola, la tercera, auxiliar.

Ejemplo. Escriba un programa que forme una cola, le agregue un número arbitrario de componentes y luego lea todos los componentes y los muestre en la pantalla. Tome una cadena de caracteres como datos. Entrada de datos: desde el teclado, una señal del final de la entrada: una cadena de caracteres FIN.

ProgramaQUEUE;

utiliza Crt;

tipo

Alfa = Cadena[10];

PComp = ^Comp;

Comp = registro

SD: Alfa

pSiguiente: PComp;

fin;

var

pInicio, pFin : PComp;

SC: Alfa;

Create ProcedureQueue(var pBegin,pEnd:PComp; var sC:Alfa);

comenzar

Nuevo(pInicio);

pInicio^.pSiguiente := NIL;

pComienzo^.sD := sC;

pFin := pComienzo;

fin;

Procedimiento Add ProcedureQueue(var pEnd: PComp; var sC: Alfa);

var pAux: PComp;

comenzar

Nuevo(pAux);

pAux^.pSiguiente := NIL;

pFin^.pSiguiente := pAux;

pFin := pAux;

pFin^.sD := sC;

fin;

Procedimiento DelQueue(var pBegin : PComp; var sC : Alfa);

comenzar

sC := pComienzo^.sD;

pInicio := pInicio^.pSiguiente;

fin;

comenzar

Clrscr;

writeln('ENTRAR CADENA');

leer(sC);

CreateQueue(pBegin, pEnd, sc);

repetir

writeln('ENTRAR CADENA');

leer(sC);

AddQueue(pEnd, sc);

hasta sC = 'FIN';

writeln(' ***** MOSTRAR RESULTADOS *****');

repetir

DelQueue(pBegin, sc);

escribir(sC);

hasta pBegin = NIL;

fin.

LECCIÓN No. 9. Estructuras de datos en forma de árbol

1. Estructuras de datos de árbol

Una estructura de datos en forma de árbol es un conjunto finito de elementos, nodos entre los que existen relaciones, la conexión entre la fuente y lo generado.

Si usamos la definición recursiva propuesta por N. Wirth, entonces una estructura de datos de árbol con base tipo t es una estructura vacía o un nodo de tipo t, con lo cual se forma un conjunto finito de estructuras de árbol con base tipo t, llamados subárboles. asociado.

A continuación, damos las definiciones utilizadas cuando se opera con estructuras de árbol.

Si el nodo y está ubicado directamente debajo del nodo x, entonces el nodo y se llama descendiente inmediato del nodo x, y x es el ancestro inmediato del nodo y, es decir, si el nodo x está en el nivel i-ésimo, entonces el nodo y está en consecuencia ubicado en (i+1) - º nivel.

El nivel máximo de un nodo de árbol se denomina altura o profundidad del árbol. Un antepasado no tiene solo un nodo del árbol: su raíz.

Los nodos de árbol que no tienen hijos se denominan nodos hoja (u hojas del árbol). Todos los demás nodos se denominan nodos internos. El número de hijos inmediatos de un nodo determina el grado de ese nodo, y el grado máximo posible de un nodo en un árbol determinado determina el grado del árbol.

Los ascendientes y los descendientes no pueden intercambiarse, es decir, la conexión entre el original y el generado actúa solo en una dirección.

Si va desde la raíz del árbol hasta algún nodo en particular, entonces el número de ramas del árbol que se atravesarán en este caso se denomina longitud del camino para este nodo. Si todas las ramas (nodos) de un árbol están ordenadas, se dice que el árbol está ordenado.

Los árboles binarios son un caso especial de estructuras de árbol. Estos son árboles en los que cada hijo tiene como máximo dos hijos, llamados subárboles izquierdo y derecho. Así, un árbol binario es una estructura de árbol cuyo grado es dos.

El orden de un árbol binario está determinado por la siguiente regla: cada nodo tiene su propio campo clave, y para cada nodo el valor clave es mayor que todas las claves en su subárbol izquierdo y menor que todas las claves en su subárbol derecho.

Un árbol cuyo grado es mayor que dos se llama fuertemente ramificado.

2. Operaciones en árboles

Además, consideraremos todas las operaciones en relación con los árboles binarios.

I. Construcción de árboles

Presentamos un algoritmo para construir un árbol ordenado.

1. Si el árbol está vacío, los datos se transfieren a la raíz del árbol. Si el árbol no está vacío, entonces una de sus ramas desciende de tal manera que no se viola el orden del árbol. Como resultado, el nuevo nodo se convierte en la siguiente hoja del árbol.

2. Para agregar un nodo a un árbol ya existente, puede usar el algoritmo anterior.

3. Al eliminar un nodo del árbol, debe tener cuidado. Si el nodo a eliminar es una hoja o tiene un solo hijo, entonces la operación es simple. Si el nodo a eliminar tiene dos descendientes, será necesario encontrar un nodo entre sus descendientes que se pueda colocar en su lugar. Esto es necesario debido al requisito de que el árbol esté ordenado.

Puede hacer esto: intercambiar el nodo que se eliminará con el nodo con el valor de clave más grande en el subárbol izquierdo, o con el nodo con el valor de clave más pequeño en el subárbol derecho, y luego elimine el nodo deseado como una hoja.

II. Encontrar un nodo con un valor de campo clave dado

Al realizar esta operación, es necesario atravesar el árbol. Es necesario tener en cuenta diferentes formas de notación de árbol: prefijo, infijo y posfijo.

Surge la pregunta: ¿cómo representar los nodos del árbol para que sea más conveniente trabajar con ellos? Es posible representar un árbol usando un arreglo, donde cada nodo es descrito por un valor del tipo combinado, el cual tiene un campo de información de tipo carácter y dos campos de tipo referencia. Pero esto no es muy conveniente, ya que los árboles tienen una gran cantidad de nodos que no están predeterminados. Por lo tanto, es mejor usar variables dinámicas al describir un árbol. Entonces cada nodo está representado por un valor del mismo tipo, que contiene una descripción de un número dado de campos de información, y el número de campos correspondientes debe ser igual al grado del árbol. Es lógico definir la ausencia de descendientes con nil. Entonces, en Pascal, la descripción de un árbol binario podría verse así:

TIPO TreeLink = ^Árbol;

árbol = registro;

Inf: <tipo de datos>;

Izquierda, Derecha: TreeLink;

Fin.

3. Ejemplos de implementación de operaciones

1. Construya un árbol de n nodos de altura mínima, o un árbol perfectamente equilibrado (el número de nodos de los subárboles izquierdo y derecho de dicho árbol no debe diferir en más de uno).

Algoritmo de construcción recursivo:

1) el primer nodo se toma como raíz del árbol.

2) el subárbol izquierdo de nl nodos se construye de la misma manera.

3) el subárbol derecho de nr nodos se construye de la misma manera;

nr = n - nl - 1. Como campo de información tomaremos los números de nodo ingresados ​​desde el teclado. La función recursiva que implementa esta construcción se verá así:

Árbol de funciones (n : Byte) : TreeLink;

Var t: TreeLink; nl,nr,x : Byte;

Comenzar

Si n = 0 entonces Árbol := nil

otro

Comenzar

nl := n div 2;

nr = n - nl - 1;

writeln('Ingrese el número de vértice');

lectura(x);

tritón);

t^.inf := x;

t^.izquierda := Árbol(nl);

t^.derecha := Árbol(nr);

Árbol := t;

End;

{Árbol}

Fin.

2. En el árbol ordenado binario, encuentre el nodo con el valor dado del campo clave. Si no existe tal elemento en el árbol, agréguelo al árbol.

Procedimiento de búsqueda (x: Byte; var t: TreeLink);

Comenzar

Si t = cero entonces

Comenzar

Tritón);

t^inf := x;

t^.izquierda := nil;

t^.derecha := nil;

Fin

De lo contrario, si x < t^.inf entonces

Buscar(x, t^.izquierda)

De lo contrario, si x > t^.inf entonces

Buscar(x, t^.derecha)

otro

Comenzar

{proceso elemento encontrado}

...

End;

Fin.

3. Escribir procedimientos de recorrido de árboles en orden directo, simétrico e inverso, respectivamente.

3.1. Procedimiento Preorder(t : TreeLink);

Comenzar

Si t <> cero entonces

Comenzar

WriteIn(t^.inf);

Reservar(t^.izquierda);

Preordenar(t^.derecha);

End;

End;

3.2. Procedimiento Inorder(t : TreeLink);

Comenzar

Si t <> cero entonces

Comenzar

En orden(t^.izquierda);

WriteIn(t^.inf);

En orden(t^.derecha);

End;

Fin.

3.3. Procedimiento Posorden(t : TreeLink);

Comenzar

Si t <> cero entonces

Comenzar

pedido posterior(t^.izquierda);

pedido posterior(t^.derecho);

WriteIn(t^.inf);

End;

Fin.

4. En el árbol ordenado binario, elimine el nodo con el valor dado del campo clave.

Describamos un procedimiento recursivo que tendrá en cuenta la presencia del elemento requerido en el árbol y el número de descendientes de este nodo. Si el nodo que se va a eliminar tiene dos hijos, se reemplazará por el valor de clave más grande en su subárbol izquierdo y solo entonces se eliminará de forma permanente.

Procedimiento Delete1(x : Byte; var t : TreeLink);

Var p: Enlace de árbol;

Procedimiento Delete2(var q : TreeLink);

Comenzar

Si q^.right <> nil entonces Delete2(q^.right)

otro

Comenzar

p^.inf := q^.inf;

pag := q;

q := q^.izquierda;

End;

End;

Comenzar

Si t = cero entonces

Writeln('ningún elemento encontrado')

De lo contrario, si x < t^.inf entonces

Delete1(x, t^.izquierda)

De lo contrario, si x > t^.inf entonces

Eliminar1(x, t^.derecha)

otro

Comenzar

P := t;

Si p^.left = nil entonces

t := p^.derecha

otro

Si p^.right = nil entonces

t := p^.izquierda

otro

Eliminar2(p^.izquierda);

End;

Fin.

CONFERENCIA N° 10. Cuentas

1. El concepto de grafo. Maneras de representar un gráfico

Un grafo es un par G = (V,E), donde V es un conjunto de objetos de naturaleza arbitraria, llamados vértices, y E es una familia de pares ei = (vil, vi2), vijOV, llamados aristas. En el caso general, el conjunto V y/o la familia E pueden contener un número infinito de elementos, pero consideraremos solo gráficos finitos, es decir, gráficos para los que tanto V como E son finitos. Si el orden de los elementos incluidos en ei importa, entonces el gráfico se llama dirigido, abreviado como dígrafo, de lo contrario se llama no dirigido. Los bordes de un dígrafo se llaman arcos. En lo que sigue, asumimos que el término "grafo", usado sin especificación (dirigido o no dirigido), denota un grafo no dirigido.

Si e = , entonces los vértices v y u se llaman extremos de la arista. Aquí decimos que la arista e es adyacente (incidente) a cada uno de los vértices v y u. Los vértices vy y también se denominan adyacentes (incidentes). En el caso general, las aristas de la forma e = ; tales bordes se llaman bucles.

El grado de un vértice en un gráfico es el número de aristas incidentes en ese vértice, y los bucles se cuentan dos veces. Dado que cada arista incide sobre dos vértices, la suma de los grados de todos los vértices en el gráfico es igual al doble del número de aristas: Sum(deg(vi), i=1...|V|) = 2 * | mi|.

El peso de un nodo es un número (real, entero o racional) asignado a un nodo determinado (interpretado como costo, rendimiento, etc.). Peso, longitud del borde: un número o varios números que se interpretan como longitud, ancho de banda, etc.

Un camino en un gráfico (o una ruta en un dígrafo) es una secuencia alterna de vértices y aristas (o arcos en un dígrafo) de la forma v0, (v0,v1), v1..., (vn - 1,vn ), vn. El número n se llama longitud del camino. Un camino sin aristas repetidas se llama cadena; un camino sin vértices repetidos se llama cadena simple. El camino se puede cerrar (v0 = vn). Un camino cerrado sin aristas repetidas se llama ciclo (o contorno en un dígrafo); sin repetir vértices (excepto el primero y el último): un bucle simple.

Un grafo se dice conexo si hay un camino entre dos de sus vértices y desconectado en caso contrario. Un gráfico desconectado consta de varios componentes conectados (subgráficos conectados).

Hay varias formas de representar gráficos. Consideremos cada uno de ellos por separado.

1. Matriz de incidencia.

Esta es una matriz rectangular de dimensión nx n, donde n es el número de vértices, am es el número de aristas. Los valores de los elementos de la matriz se determinan de la siguiente manera: si la arista xi y el vértice vj son incidentes, entonces el valor del elemento de la matriz correspondiente es igual a uno, en caso contrario el valor es cero. Para gráficos dirigidos, la matriz de incidencia se construye de acuerdo con el siguiente principio: el valor del elemento es igual a - 1 si la arista xi proviene del vértice vj, igual a 1 si la arista xi entra al vértice vj, e igual a XNUMX en caso contrario .

2. Matriz de adyacencia.

Esta es una matriz cuadrada de dimensión nxn, donde n es el número de vértices. Si los vértices vi y vj son adyacentes, es decir, si hay una arista que los conecta, entonces el elemento de la matriz correspondiente es igual a uno; en caso contrario, es igual a cero. Las reglas para construir esta matriz para gráficos dirigidos y no dirigidos no son diferentes. La matriz de adyacencia es más compacta que la matriz de incidencia. Cabe señalar que esta matriz también es muy escasa, pero en el caso de un gráfico no dirigido es simétrica con respecto a la diagonal principal, por lo que no se puede almacenar toda la matriz, sino solo la mitad (una matriz triangular ).

3. Relación de adyacencias (incidencias).

Es una estructura de datos que almacena una lista de vértices adyacentes para cada vértice del gráfico. La lista es una matriz de punteros, cuyo i-ésimo elemento contiene un puntero a la lista de vértices adyacentes al i-ésimo vértice.

Una lista de adyacencia es más eficiente que una matriz de adyacencia porque elimina el almacenamiento de elementos nulos.

4. Lista de listas.

Es una estructura de datos similar a un árbol en la que una rama contiene listas de vértices adyacentes a cada uno de los vértices del gráfico y la segunda rama apunta al siguiente vértice del gráfico. Esta forma de representar el gráfico es la más óptima.

2. Representación de un gráfico por una lista de incidencias. Algoritmo de recorrido de profundidad de gráfico

Para implementar un gráfico como una lista de incidencias, puede utilizar el siguiente tipo:

ListaTipos = ^S;

S=registro;

información: byte;

siguiente: Lista;

fin;

Entonces el gráfico se define de la siguiente manera:

Var Gr: array[1..n] de Lista;

Ahora pasemos al procedimiento de recorrido de grafos. Este es un algoritmo auxiliar que le permite ver todos los vértices del gráfico, analizar todos los campos de información. Si consideramos el recorrido del gráfico en profundidad, entonces hay dos tipos de algoritmos: recursivos y no recursivos.

Con el algoritmo transversal recurrente en profundidad, tomamos un vértice arbitrario y encontramos un vértice arbitrario no visto (nuevo) v adyacente a él. Luego tomamos el vértice v como no nuevo y encontramos cualquier nuevo vértice adyacente a él. Si algún vértice no tiene vértices invisibles más nuevos, entonces consideramos que este vértice se usa y regresamos un nivel más alto al vértice desde el cual llegamos a nuestro vértice usado. El recorrido continúa de esta manera hasta que no haya nuevos vértices no escaneados en el gráfico.

En Pascal, el procedimiento transversal primero en profundidad se vería así:

Procedimiento Obhod(gr : Gráfico; k : Byte);

Var g : Gráfico; l : Lista;

Comenzar

nov[k] := falso;

g := gramo;

mientras g^.inf <> k hacer

g := g^.siguiente;

l := g^.smeg;

Mientras que l <> nil comienzan

Si nov[l^.inf] entonces Obhod(gr, l^.inf);

l := l^.siguiente;

End;

End;

Nota

En este procedimiento, al describir el tipo Graph, nos referimos a la descripción de un gráfico mediante una lista de listas. Array nov[i] es una matriz especial cuyo i-ésimo elemento es True si no se visita el i-th vértice, y False en caso contrario.

También se utiliza a menudo un algoritmo transversal no recursivo. En este caso, la recursividad se reemplaza por una pila. Una vez que se ha visto un vértice, se coloca en la pila y se usa cuando no hay más vértices nuevos adyacentes.

3. Representación de un gráfico por una lista de listas. Algoritmo de recorrido del gráfico de amplitud

Un gráfico se puede definir usando una lista de listas de la siguiente manera:

ListaTipos = ^Tlista;

tlist=registro

información: byte;

siguiente: Lista;

fin;

Gráfico = ^TGpaph;

TGpaph = registro

información: byte;

smeg : Lista;

siguiente : Gráfico;

fin;

Al atravesar el gráfico a lo ancho, seleccionamos un vértice arbitrario y miramos a través de todos los vértices adyacentes a él a la vez. Se utiliza una cola en lugar de una pila. El algoritmo de búsqueda primero en amplitud es muy útil para encontrar la ruta más corta en un gráfico.

Aquí hay un procedimiento para atravesar un gráfico en ancho en pseudocódigo:

Procedimiento Obhod2(v);

{valores spisok, nov - global}

Comenzar

cola = O;

cola <= v;

nov[v] = Falso;

Mientras cola <> O hacer

Comenzar

p <= cola;

Para ti en spisok (p) haz

Si es nuevo[u] entonces

Comenzar

nov[u] := Falso;

cola <= u;

End;

End;

End;

LECCIÓN N° 11. Tipo de datos de objeto

1. Tipo de objeto en Pascal. El concepto de objeto, su descripción y uso

Históricamente, el primer enfoque de la programación ha sido la programación procedimental, también conocida como programación ascendente. Inicialmente, se crearon bibliotecas comunes de programas estándar utilizados en varios campos de aplicación informática. Luego, en base a estos programas, se crearon programas más complejos para resolver problemas específicos.

Sin embargo, la tecnología informática se desarrollaba constantemente, comenzó a usarse para resolver varios problemas de producción, la economía y, por lo tanto, se hizo necesario procesar datos de varios formatos y resolver problemas no estándar (por ejemplo, no numéricos). Por lo tanto, al desarrollar lenguajes de programación, comenzaron a prestar atención a la creación de varios tipos de datos. Esto contribuyó a la aparición de tipos de datos tan complejos como combinados, múltiples, cadenas, archivos, etc. Antes de resolver el problema, el programador llevó a cabo la descomposición, es decir, dividió la tarea en varias subtareas, para cada una de las cuales se escribió un módulo separado. . La principal tecnología de programación incluía tres etapas:

1) diseño de arriba hacia abajo;

2) programación modular;

3) codificación estructural.

Pero a partir de mediados de los años 60 del siglo XX, comenzaron a formarse nuevos conceptos y enfoques, que formaron la base de la tecnología de programación orientada a objetos. En este enfoque, el modelado y descripción del mundo real se realiza a nivel de conceptos de un área temática específica a la que pertenece el problema que se está resolviendo.

La programación orientada a objetos es una técnica de programación que se parece mucho a nuestro comportamiento. Es una evolución natural de innovaciones anteriores en el diseño de lenguajes de programación. La programación orientada a objetos es más estructural que todos los desarrollos anteriores relacionados con la programación estructurada. También es más modular y más abstracto que los intentos anteriores de abstracción de datos y detalles de programación internamente. Un lenguaje de programación orientado a objetos se caracteriza por tres propiedades principales:

1) Encapsulación. La combinación de registros con procedimientos y funciones que manipulan los campos de estos registros forma un nuevo tipo de datos: un objeto;

2) Herencia. Definición de un objeto y su uso posterior para construir una jerarquía de objetos secundarios con la capacidad de que cada objeto secundario relacionado con la jerarquía acceda al código y los datos de todos los objetos principales;

3) Polimorfismo. Dar a una acción un solo nombre, que luego se comparte hacia arriba y hacia abajo en la jerarquía de objetos, con cada objeto en la jerarquía realizando esa acción de la manera que más le convenga.

Hablando del objeto, presentamos un nuevo tipo de datos: objeto. Un tipo de objeto es una estructura que consta de un número fijo de componentes. Cada componente es un campo que contiene datos de un tipo estrictamente definido o un método que realiza operaciones en un objeto. Por analogía con la declaración de variables, la declaración de un campo especifica el tipo de datos de este campo y el identificador que nombra el campo: por analogía con la declaración de un procedimiento o función, la declaración de un método especifica el título de un procedimiento, función, constructor o destructor.

Un tipo de objeto puede heredar componentes de otro tipo de objeto. Si el tipo T2 hereda del tipo T1, entonces el tipo T2 es un hijo del tipo T1 y el tipo T1 en sí mismo es un padre del tipo T2. La herencia es transitiva, es decir, si TK hereda de T2 y T2 hereda de T1, entonces TK hereda de T1. El alcance (dominio) de un tipo de objeto consiste en sí mismo y todos sus descendientes.

El siguiente código fuente es un ejemplo de una declaración de tipo de objeto, tipo

tipo

punto = objeto

X, Y: entero;

fin;

Recto = objeto

A, B: PuntoT;

procedimiento Init(XA, YA, XB, YB: Entero);

procedimiento Copiar (var R: TRectangle);

procedimiento Move(DX, DY: Entero);

procedimiento Crecer(DX, DY: Entero);

procedimiento Intersecar(var R: TRectángulo);

procedimiento Union(var R: TRectangle);

función Contiene (P: Punto): Booleano;

fin;

CadenaPtr = ^Cadena;

FieldPtr = ^TField;

TField = objeto

X, Y, Longitud: Entero;

Nombre: StringPtr;

constructor Copiar(var F: TField);

constructor Init(FX, FY, FLen: entero; FName: cadena);

destructor Hecho; virtual;

Procedimiento Pantalla; virtual;

procedimiento Editar; virtual;

función ObtenerCadena: Cadena; virtual;

función PutStr(S: Cadena): Booleano; virtual;

fin;

StrFieldPtr = ^TStrField;

StrField = objeto (TField)

Valor: PString;

constructor Init(FX, FY, FLen: entero; FName: cadena);

destructor Hecho; virtual;

función ObtenerCadena: Cadena; virtual;

función PutStr(S: Cadena): Booleano;

virtual;

función Obtener:cadena;

procedimiento Poner(S: Cadena);

fin;

NumFieldPtr = ^TNumField;

TNumField = objeto (TField)

privada

Valor, Min, Max: Entero largo;

público

constructor Init(FX, FY, FLen: entero; FName: cadena;

FMín, FMáx: entero largo);

función ObtenerCadena: Cadena; virtual;

función PutStr(S: Cadena): Booleano; virtual;

función Obtener: Entero largo;

función Poner (N: Entero largo);

fin;

ZipFieldPtr = ^TZipField;

ZipField = objeto (TNumField)

función ObtenerCadena: Cadena; virtual;

función PutStr(S: Cadena): Booleano;

virtual;

fin.

A diferencia de otros tipos, los tipos de objetos solo se pueden declarar en la sección de declaración de tipos en el nivel más externo del alcance de un programa o módulo. Por lo tanto, los tipos de objetos no se pueden declarar en una sección de declaración de variables o dentro de un procedimiento, función o bloque de método.

Un tipo de componente de tipo de archivo no puede tener un tipo de objeto ni ningún tipo de estructura que contenga componentes de tipo de objeto.

2. Herencia

El proceso por el cual un tipo hereda las características de otro tipo se llama herencia. El descendiente se denomina tipo derivado (secundario), y el tipo del que hereda el tipo secundario se denomina tipo primario (principal).

Los tipos de registro de Pascal conocidos anteriormente no pueden heredar. Sin embargo, Borland Pascal amplía el lenguaje Pascal para admitir la herencia. Una de estas extensiones es una nueva categoría de estructura de datos relacionada con los registros, pero mucho más poderosa. Los tipos de datos de esta nueva categoría se definen mediante la nueva palabra reservada "objeto". Un tipo de objeto se puede definir como un tipo completo e independiente a la manera de describir las entradas de Pascal, pero también se puede definir como un descendiente de un tipo de objeto existente poniendo el tipo principal entre paréntesis después de la palabra reservada "objeto".

3. Crear instancias de objetos

Una instancia de un objeto se crea declarando una variable o constante de un tipo de objeto, o aplicando el procedimiento New estándar a una variable de tipo "puntero a tipo de objeto". El objeto resultante se denomina instancia del tipo de objeto;

var

F: campo;

Z: TZipCampo;

FP:PFcampo;

ZP: PZipField;

Dadas estas declaraciones de variables, F es una instancia de TField y Z es una instancia de TZipField. De manera similar, después de aplicar New a FP y ZP, FP apuntará a una instancia de TField y ZP apuntará a una instancia de TZipField.

Si un tipo de objeto contiene métodos virtuales, las instancias de ese tipo de objeto deben inicializarse llamando a un constructor antes de llamar a cualquier método virtual.

A continuación se muestra un ejemplo:

var

S: campo de cadena;

comienzo

S.Init(1, 1, 25, 'Nombre');

S.Put('Vladimir');

S. Pantalla;

...

S Listo;

fin.

Si no se llamó a S.Init, llamar a S.Display hará que este ejemplo falle.

La asignación de una instancia de un tipo de objeto no implica la inicialización de la instancia. Un objeto se inicializa mediante un código generado por el compilador que se ejecuta entre la invocación del constructor y el punto en el que la ejecución llega realmente a la primera instrucción en el bloque de código del constructor.

Si la instancia del objeto no se inicializa y la verificación de rango está habilitada (mediante la directiva {SR+}), la primera llamada al método virtual de la instancia del objeto genera un error en tiempo de ejecución. Si la verificación de rango está deshabilitada (por la directiva {SR-}), la primera llamada a un método virtual de un objeto no inicializado puede generar un comportamiento impredecible.

La regla de inicialización obligatoria también se aplica a instancias que son componentes de tipos de estructuras. Por ejemplo:

var

Comentario: matriz [1..5] de TStrField;

yo: entero

comenzar

para yo := 1 a 5 hacer

Comentario [I].Init (1, I + 10, 40, 'first_name');

.

.

.

para I := 1 a 5 hacer comentario [I].Listo;

fin;

Para las instancias dinámicas, la inicialización suele ser la colocación y la limpieza es la eliminación, lo que se logra a través de la sintaxis extendida de los procedimientos estándar New y Dispose. Por ejemplo:

var

SP: StrFieldPtr;

comenzar

Nuevo (SP, Init (1, 1, 25, 'nombre');

SP^.Put('Vladímir');

SP^.Pantalla;

.

.

.

Desechar (SP, Listo);

fin.

Un puntero a un tipo de objeto es compatible con la asignación de un puntero a cualquier tipo de objeto principal, por lo que en tiempo de ejecución un puntero a un tipo de objeto puede apuntar a una instancia de ese tipo o a una instancia de cualquier tipo secundario.

Por ejemplo, un puntero de tipo ZipFieldPtr se puede asignar a punteros de tipo PZipField, PNumField y PField y, en tiempo de ejecución, un puntero de tipo PField puede ser nulo o apuntar a una instancia de TField, TNumField o TZipField, o cualquier instancia de un tipo hijo de TField. .

Estas reglas de compatibilidad de punteros de asignación también se aplican a los parámetros de tipo de objeto. Por ejemplo, al método TField.Cop se le pueden pasar instancias de TField, TStrField, TNumField, TZipField o cualquier otro tipo secundario de TField.

4. Componentes y alcance

El alcance de un identificador de bean se extiende más allá del tipo de objeto. Además, el alcance de un identificador de bean se extiende a través de los bloques de procedimientos, funciones, constructores y destructores que implementan los métodos del tipo de objeto y sus descendientes. En base a estas consideraciones, la ortografía del identificador del componente debe ser única dentro del tipo de objeto y dentro de todos sus descendientes, así como dentro de todos sus métodos.

El alcance del identificador de componente descrito en la parte privada de la declaración de tipo se limita al módulo (programa) que contiene la declaración de tipo de objeto. En otras palabras, los beans identificadores privados actúan como identificadores públicos ordinarios dentro del módulo que contiene la declaración del tipo de objeto, y fuera del módulo, los beans e identificadores privados son desconocidos e inaccesibles. Al colocar tipos de objetos relacionados en el mismo módulo, puede asegurarse de que estos objetos puedan acceder a los componentes privados de los demás, y estos componentes privados serán desconocidos para otros módulos.

En una declaración de tipo de objeto, un encabezado de método puede especificar los parámetros del tipo de objeto que se describe, incluso si la declaración aún no está completa.

CONFERENCIA N° 12. Métodos

1. Métodos

Una declaración de método dentro de un tipo de objeto corresponde a una declaración de método hacia adelante (forward). Por lo tanto, en algún lugar después de una declaración de tipo de objeto, pero dentro del mismo alcance que el alcance de la declaración de tipo de objeto, se debe implementar un método definiendo su declaración.

Para métodos procedimentales y funcionales, la declaración de definición toma la forma de una declaración normal de procedimiento o función, con la excepción de que en este caso el identificador de procedimiento o función se trata como un identificador de método.

Para los métodos constructor y destructor, la declaración de definición toma la forma de una declaración de método de procedimiento, con la excepción de que la palabra reservada procedimiento se reemplaza por la palabra reservada constructor o destructor.

La declaración del método de definición puede, pero no necesariamente, repetir la lista de parámetros formales del encabezado del método en el tipo de objeto. En este caso, el encabezado del método debe coincidir exactamente con el encabezado en el tipo de objeto en orden, tipos y nombres de parámetros, y en el tipo de retorno del resultado de la función si el método es una función.

La descripción definitoria de un método siempre contiene un parámetro implícito con identificador Self correspondiente a un parámetro variable formal de un tipo de objeto. Dentro de un bloque de método, Self representa la instancia cuyo componente de método se especificó para invocar el método. Por lo tanto, cualquier cambio en los valores de los campos Self se refleja en la instancia.

El alcance de un identificador de bean de tipo objeto se extiende a bloques de procedimientos, funciones, constructores y destructores que implementan métodos de ese tipo de objeto. El efecto es el mismo que si se insertara una declaración with de la siguiente forma al comienzo del bloque de método:

con uno mismo hacer

comenzar

...

fin;

Según estas consideraciones, la ortografía de los identificadores de componentes, los parámetros formales del método, Self y cualquier identificador introducido en la parte ejecutable del método debe ser único.

Si se requiere un identificador de método único, se utiliza el identificador de método calificado. Consiste en un identificador de tipo de objeto seguido de un punto y un identificador de método. Al igual que con cualquier otro identificador, un identificador de método calificado puede estar precedido opcionalmente por un identificador de paquete y un punto.

Métodos virtuales

Los métodos son estáticos por defecto, pero a excepción de los constructores, pueden ser virtuales (al incluir la directiva virtual en la declaración del método). El compilador resuelve las referencias a las llamadas a métodos estáticos durante el proceso de compilación, mientras que las llamadas a métodos virtuales se resuelven en tiempo de ejecución. Esto a veces se denomina vinculación tardía.

Si un tipo de objeto declara o hereda algún método virtual, entonces las variables de ese tipo deben inicializarse llamando a un constructor antes de llamar a cualquier método virtual. Por lo tanto, un tipo de objeto que describe o hereda un método virtual también debe describir o heredar al menos un método constructor.

Un tipo de objeto puede anular cualquiera de los métodos que hereda de sus padres. Si una declaración de método en un elemento secundario especifica el mismo identificador de método que una declaración de método en el elemento principal, entonces la declaración en el elemento secundario anula la declaración en el elemento principal. El alcance de un método de reemplazo se expande al alcance del elemento secundario en el que se introdujo el método, y permanecerá así hasta que el identificador del método se vuelva a reemplazar.

Anular un método estático es independiente de cambiar el encabezado del método. Por el contrario, una anulación de método virtual debe conservar el orden, los tipos y nombres de parámetros y los tipos de resultados de funciones, si los hubiera. Además, la redefinición debe incluir nuevamente la directiva virtual.

Métodos dinámicos

Borland Pascal admite métodos enlazados en tiempo de ejecución adicionales denominados métodos dinámicos. Los métodos dinámicos difieren de los métodos virtuales solo en la forma en que se distribuyen en tiempo de ejecución. En todos los demás aspectos, los métodos dinámicos se consideran equivalentes a los métodos virtuales.

Una declaración de método dinámico es equivalente a una declaración de método virtual, pero la declaración de método dinámico debe incluir el índice de método dinámico, que se especifica inmediatamente después de la palabra clave virtual. El índice de un método dinámico debe ser una constante entera entre 1 y 656535 y debe ser único entre los índices de otros métodos dinámicos contenidos en el tipo de objeto o sus ancestros. Por ejemplo:

procedimiento FileOpen(var Msg: TMessage); 100 virtuales;

Una anulación de un método dinámico debe coincidir con el orden, los tipos y los nombres de los parámetros, y debe coincidir exactamente con el tipo de resultado de la función del método principal. La anulación también debe incluir una directiva virtual seguida del mismo índice de método dinámico que se especificó en el tipo de objeto principal.

2. Constructores y destructores

Los constructores y destructores son formas especializadas de métodos. Usados ​​en conexión con la sintaxis extendida de los procedimientos estándar New y Dispose, los constructores y destructores tienen la capacidad de colocar y eliminar objetos dinámicos. Además, los constructores tienen la capacidad de realizar la inicialización requerida de objetos que contienen métodos virtuales. Como todos los métodos, los constructores y destructores se pueden heredar, y los objetos pueden contener cualquier número de constructores y destructores.

Los constructores se utilizan para inicializar objetos recién creados. Normalmente, la inicialización se basa en los valores pasados ​​al constructor como parámetros. Un constructor no puede ser virtual porque el mecanismo de despacho de un método virtual depende del constructor que inicializó el objeto primero.

Estos son algunos ejemplos de constructores:

constructor Field.Copy(var F: Campo);

comenzar

Yo := F;

fin;

constructor Field.Init(FX, FY, FLen: entero; FName: cadena);

comenzar

X := FX;

Y := año fiscal;

GetMem(Nombre, Longitud(FName) + 1);

Nombre^ := FNombre;

fin;

constructor TStrField.Init(FX, FY, FLen: entero; FName: cadena);

comenzar

heredado Init(FX, FY, FLen, FName);

Field.Init(FX, FY, FLen, FName);

GetMem(Valor, Largo);

Valor^ := '';

fin;

La acción principal de un constructor de un tipo derivado (hijo), como el TStr Field anterior. Init es casi siempre una llamada al constructor apropiado de su padre inmediato para inicializar los campos heredados del objeto. Después de ejecutar este procedimiento, el constructor inicializa los campos del objeto que pertenecen solo al tipo derivado.

Los destructores son lo opuesto a los constructores y se usan para limpiar objetos después de que se hayan usado. Normalmente, la limpieza consiste en eliminar todos los campos de puntero del objeto.

Nota

Un destructor puede ser virtual, y con frecuencia lo es. Un destructor rara vez tiene parámetros.

Estos son algunos ejemplos de destructores:

campo destructor hecho;

comenzar

FreeMem(Nombre, Longitud(Nombre^) + 1);

fin;

destructor StrField.Hecho;

comenzar

FreeMem(Valor, Longitud);

Campo Hecho;

fin;

El destructor de un tipo secundario, como el TStrField anterior. Listo, generalmente primero elimina los campos de puntero introducidos en el tipo derivado y luego, como último paso, llama al colector-destructor adecuado del padre inmediato para eliminar los campos de puntero heredados del objeto.

3. Destructores

Borland Pascal proporciona un tipo especial de método llamado recolector de basura (o destructor) para limpiar y eliminar un objeto asignado dinámicamente. El destructor combina el paso de eliminar un objeto con cualquier otra acción o tarea requerida para ese tipo de objeto. Puede definir varios destructores para un solo tipo de objeto.

El destructor se define junto con todos los demás métodos de objeto en la definición de tipo del objeto:

escribe

temployee = objeto

Nombre: cadena[25];

Título: cadena[25];

Tasa: Real;

constructor Init(AName, ATitle: String; ARate: Real);

destructor Hecho; virtual;

función ObtenerNombre: Cadena;

función ObtenerTítulo: Cadena;

función GetRate: Tasa; virtual;

función GetPayAmount: Real; virtual;

fin;

Los destructores se pueden heredar y pueden ser estáticos o virtuales. Dado que los diferentes finalizadores tienden a requerir diferentes tipos de objetos, generalmente se recomienda que los destructores sean siempre virtuales para que se ejecute el destructor correcto para cada tipo de objeto.

No es necesario especificar el destructor de palabras reservadas para cada método de limpieza, incluso si la definición de tipo del objeto contiene métodos virtuales. Los destructores realmente solo funcionan en objetos asignados dinámicamente.

Cuando se limpia un objeto asignado dinámicamente, el destructor realiza una función especial: asegura que siempre se libere el número correcto de bytes en el área de memoria asignada dinámicamente. No puede haber preocupación por usar un destructor con objetos asignados estáticamente; de hecho, al no pasar el tipo de objeto al destructor, el programador priva a un objeto de ese tipo de todos los beneficios de la gestión de memoria dinámica en Borland Pascal.

Los destructores en realidad se convierten en ellos mismos cuando los objetos polimórficos deben borrarse y cuando la memoria que ocupan debe desasignarse.

Los objetos polimórficos son aquellos objetos que han sido asignados a un tipo padre debido a las reglas de compatibilidad de tipos extendidos de Borland Pascal. Una instancia de un objeto de tipo THourly asignado a una variable de tipo TEmployee es un ejemplo de objeto polimórfico. Estas reglas también se pueden aplicar a objetos; un puntero a THourly se puede asignar libremente a un puntero a TEmployee, y el objeto señalado por ese puntero volverá a ser un objeto polimórfico. El término "polimórfico" es apropiado porque el código que procesa un objeto "no sabe" exactamente en el momento de la compilación qué tipo de objeto necesitará procesar eventualmente. Lo único que sabe es que este objeto pertenece a una jerarquía de objetos que son descendientes del tipo de objeto especificado.

Obviamente, los tamaños de los tipos de objetos son diferentes. Entonces, cuando llega el momento de limpiar un objeto polimórfico asignado al montón, ¿cómo sabe Dispose cuántos bytes de espacio de montón liberar? En tiempo de compilación, no se puede extraer información sobre el tamaño del objeto de un objeto polimórfico.

El destructor resuelve este rompecabezas al referirse al lugar donde está escrita esta información, en las variables de implementación de TCM. Cada TBM de un tipo de objeto contiene el tamaño en bytes de ese tipo de objeto. La tabla de métodos virtuales de cualquier objeto está disponible a través del parámetro oculto Self, enviado al método cuando se llama al método. Un destructor es solo un tipo de método, por lo que cuando un objeto lo llama, el destructor obtiene una copia de Self en la pila. Por lo tanto, si un objeto es polimórfico en tiempo de compilación, nunca será polimórfico en tiempo de ejecución debido al enlace tardío.

Para realizar esta desasignación en tiempo de ejecución, se debe llamar al destructor como parte de la sintaxis extendida del procedimiento Dispose:

Desechar (P, Listo);

(Una llamada al destructor fuera del procedimiento Dispose no desasigna ninguna memoria). Lo que realmente sucede aquí es que el recolector de basura del objeto señalado por P se ejecuta como un método normal. Sin embargo, una vez que se completa la última acción, el destructor busca el tamaño de la implementación de su tipo en el TCM y pasa el tamaño al procedimiento Dispose. El procedimiento Dispose finaliza el proceso eliminando el número correcto de bytes del espacio de almacenamiento dinámico que (el espacio) pertenecía previamente a P^. El número de bytes que se liberará será correcto independientemente de si P apuntó a una instancia de tipo TSalaried o si apuntó a uno de los tipos secundarios de tipo TSalaried, como TCommissioned.

Tenga en cuenta que el método destructor en sí mismo puede estar vacío y realizar solo esta función:

destructorAnObject.Done;

comenzar

fin;

Lo que es útil en este destructor no es la propiedad de su cuerpo, sin embargo, el compilador genera código de epílogo en respuesta a la palabra reservada del destructor. Es como un módulo que no exporta nada, pero hace un trabajo invisible al ejecutar su sección de inicialización antes de iniciar el programa. Toda la acción tiene lugar entre bastidores.

4. Métodos virtuales

Un método se vuelve virtual si su declaración de tipo de objeto va seguida de la nueva palabra reservada virtual. Si un método en un tipo principal se declara como virtual, entonces todos los métodos con el mismo nombre en tipos secundarios también deben declararse virtuales para evitar un error de compilación.

Los siguientes son los objetos de la nómina de ejemplo, debidamente virtualizados:

escribe

PEmpleado = ^TEmpleado;

temployee = objeto

Nombre, Título: cadena[25];

Tasa: Real;

constructor Init(AName, ATitle: String; ARate: Real);

función GetPayAmount : Real; virtual;

función ObtenerNombre: Cadena;

función GetTitle: Cadena;

función GetRate : Real;

procedimiento Mostrar; virtual;

fin;

PPor hora = ^Tpor hora;

Cada hora = objeto (Empleado);

Tiempo: Entero;

constructor Init(AName, ATitle: String; ARrate: Real; Time: Integer);

función GetPayAmount : Real; virtual;

función GetTime : Entero;

fin;

PSasalariado = ^TSalariado;

TSalariado = objeto(TEmpleado);

función GetPayAmount : Real; virtual;

fin;

PComisionado = ^TComisionado;

TComisionado = objeto (asalariado);

Comisión : Real;

Monto de Ventas : Real;

constructor Init(AName, ATitle: String; ARate,

AComisión, ASimporte de Ventas: Real);

función GetPayAmount : Real; virtual;

fin;

Un constructor es un tipo especial de procedimiento que realiza algún trabajo de configuración para el mecanismo del método virtual. Además, el constructor debe llamarse antes de llamar a cualquier método virtual. Llamar a un método virtual sin llamar primero al constructor puede bloquear el sistema y el compilador no tiene forma de verificar el orden en que se llama a los métodos.

Cada tipo de objeto que tiene métodos virtuales debe tener un constructor.

advertencia

Se debe llamar al constructor antes de llamar a cualquier otro método virtual. Llamar a un método virtual sin una llamada previa al constructor puede provocar un bloqueo del sistema y el compilador no puede verificar el orden en que se llama a los métodos.

Nota

Para los constructores de objetos, se sugiere utilizar el identificador Init.

Cada instancia de objeto distinta debe inicializarse con una llamada de constructor separada. No es suficiente inicializar una instancia de un objeto y luego asignar esa instancia a otros. Otras instancias, incluso si pueden contener datos válidos, no se inicializarán con un operador de asignación y bloquearán el sistema en cualquier llamada a sus métodos virtuales. Por ejemplo:

var

FBee, GBee: Abeja; {crear dos instancias de Bee}

comenzar

FBee.Init(5, 9) { llamada de constructor para FBee }

GBee := FBee; { ¡Gbee no es válido! }

fin;

¿Qué crea exactamente un constructor? Cada tipo de objeto contiene algo llamado tabla de método virtual (VMT) en el segmento de datos. El TVM contiene el tamaño del tipo de objeto y, para cada método virtual, un puntero al código que ejecuta ese método. Un constructor establece una relación entre la implementación de llamadas del objeto y el tipo TCM del objeto.

Es importante recordar que solo hay una tuneladora para cada tipo de objeto. Las instancias separadas de un tipo de objeto (es decir, las variables de este tipo) contienen solo la conexión con la TBM, pero no la TBM en sí. El constructor establece el valor de esta conexión a TBM. Es por esto que en ninguna parte puede comenzar la ejecución antes de llamar al constructor.

5. Campos de datos de objetos y parámetros de métodos formales

La implicación del hecho de que los métodos y sus objetos comparten un ámbito común es que los parámetros formales de un método no pueden ser idénticos a ninguno de los campos de datos del objeto. Esta no es una nueva limitación impuesta por la programación orientada a objetos, sino las mismas viejas reglas de alcance que Pascal siempre ha tenido. Esto es lo mismo que evitar que los parámetros formales de un procedimiento sean idénticos a las variables locales del procedimiento:

procedimiento CrunchIt(Crunchee: MyDataRec, Crunchby,

código de error: entero);

var

A, B: carbón;

Código de error: entero;

comenzar

.

.

.

Las variables locales de un procedimiento y sus parámetros formales comparten un ámbito común y, por lo tanto, no pueden ser idénticos. Obtendrá "Error 4: identificador duplicado" si intenta compilar algo como esto, el mismo error ocurre cuando intenta establecer un parámetro de método formal para el nombre del campo del objeto al que pertenece el método.

Las circunstancias son algo diferentes, ya que colocar el encabezado del procedimiento dentro de una estructura de datos es un guiño a una innovación en Turbo Pascal, pero los principios básicos del alcance de Pascal no han cambiado.

CONFERENCIA N° 13. Compatibilidad de tipos de objetos

1. Encapsulación

La combinación de código y datos en un objeto se denomina encapsulación. En principio, es posible proporcionar suficientes métodos para que el usuario de un objeto nunca acceda directamente a los campos del objeto. Algunos otros lenguajes orientados a objetos, como Smalltalk, requieren una encapsulación obligatoria, pero Borland Pascal tiene una opción.

Por ejemplo, los objetos TEmployee y THourly están escritos de tal manera que no hay absolutamente ninguna necesidad de acceder directamente a sus campos de datos internos:

tipo

temployee = objeto

Nombre, Título: cadena[25];

Tasa: Real;

procedimiento Init(AName, ATitle: cadena; ARate: Real);

función ObtenerNombre: Cadena;

función GetTitle: Cadena;

función GetRate : Real;

función GetPayAmount : Real;

fin;

Cada hora = objeto (Empleado)

Tiempo: Entero;

procedimiento Init(AName, ATitle: string; ARate:

Real, Atime: Entero);

función GetPayAmount : Real;

fin;

Aquí solo hay cuatro campos de datos: Nombre, Título, Tarifa y Hora. Los métodos GetName y GetTitle muestran el apellido y el cargo del trabajador, respectivamente. El método GetPayAmount utiliza Tasa, y en el caso de una jornada laboral THora y Hora para calcular el importe de los pagos a la jornada laboral. Ya no es necesario referirse directamente a estos campos de datos.

Asumiendo la existencia de una instancia de AnHourly de tipo THourly, podríamos usar un conjunto de métodos para manipular los campos de datos de AnHourly como este:

con un hacer por hora

comenzar

Init (Aleksandr Petrov, Operador de montacargas' 12.95, 62);

{Muestra el apellido, cargo y monto de los pagos}

Espectáculo;

fin;

Cabe señalar que el acceso a los campos de un objeto se realiza solo con la ayuda de los métodos de este objeto.

2. Objetos desplegables

Desafortunadamente, Pascal estándar no proporciona ninguna facilidad para crear procedimientos flexibles que le permitan trabajar con tipos de datos completamente diferentes. La programación orientada a objetos resuelve este problema con la herencia: si se define un tipo derivado, los métodos del tipo principal se heredan, pero se pueden anular si se desea. Para anular un método heredado, simplemente declare un nuevo método con el mismo nombre que el método heredado, pero con un cuerpo diferente y (si es necesario) un conjunto diferente de parámetros.

Definamos un tipo secundario de TEmployee que represente a un empleado al que se le paga una tarifa por hora en el siguiente ejemplo:

const

Períodos de pago = 26; { períodos de pago }

Umbral de horas extras = 80; { para el período de pago }

Factor de tiempo extra = 1.5; { tarifa por hora }

tipo

Cada hora = objeto (Empleado)

Tiempo: Entero;

procedimiento Init(AName, ATitle: string; ARate:

Real, Atime: Entero);

función GetPayAmount : Real;

fin;

procedimiento THourly.Init(AName, ATitle: string;

ARate: Real, Atime: Entero);

comenzar

TEmployee.Init(UNNombre, ATítulo, ARate);

Hora := HoraA;

fin;

función THourly.GetPayAmount: Real;

var

Horas extra: Entero;

comenzar

Horas extras := Tiempo - Umbral de horas extras;

si Horas extra > 0 entonces

GetPayAmount := RoundPay(Overtime Threshold * Rate +

Tasa de tiempo extra * factor de tiempo extra * tasa)

más

GetPayAmount := RoundPay(Tiempo * Tasa)

fin;

Una persona a la que se le paga una tarifa por hora es un trabajador: tiene todo lo que se usa para definir el objeto TEmployee (nombre, puesto, tarifa), y solo la cantidad de dinero que recibe la persona por hora depende de cuántas horas trabajó durante el periodo a pagar. Por lo tanto, THourly también requiere un campo de tiempo.

Debido a que THourly define un nuevo campo de hora, su inicialización requiere un nuevo método Init que inicializa tanto la hora como los campos heredados. En lugar de asignar valores directamente a los campos heredados, como Nombre, Título y Tarifa, ¿por qué no reutilizar el método de inicialización del objeto TEmployee (ilustrado en la primera instrucción THourly Init).

Llamar a un método que está siendo anulado no es el mejor estilo. En general, es posible que TEmployee.Init realice una inicialización importante pero oculta.

Al llamar a un método anulado, debe asegurarse de que el tipo de objeto derivado incluya la funcionalidad del padre. Además, cualquier cambio en el método principal afecta automáticamente a todos los descendientes.

Después de llamar a TEmployee.Init, THourly.Init puede realizar su propia inicialización, que en este caso consiste únicamente en asignar el valor pasado en ATime.

Otro ejemplo de un método anulado es la función THourly.GetPayAmount, que calcula el importe de pago de un empleado por horas. De hecho, cada tipo de objeto TEmployee tiene su propio método GetPayAmount, ya que el tipo de trabajador depende de cómo se realice el cálculo. El método THourly.GetPayAmount debe tener en cuenta cuántas horas trabajó el empleado, si hubo horas extra, cuál fue el factor de aumento de las horas extra, etc.

TMétodo asalariado. GetPayAmount solo debe dividir la tarifa del empleado por la cantidad de pagos en cada año (en nuestro ejemplo).

trabajadores de la unidad;

interfaz.

const

Períodos de pago = 26; {en el año}

Umbral de horas extras = 80; {para cada período de pago}

Factor de horas extras=1.5; {aumento contra el pago normal}

tipo

temployee = objeto

Nombre, Título: cadena[25];

Tasa: Real;

procedimiento Init(AName, ATitle: cadena; ARate: Real);

función ObtenerNombre: Cadena;

función GetTitle: Cadena;

función GetRate : Real;

función GetPayAmount : Real;

fin;

Cada hora = objeto (Empleado)

Tiempo: Entero;

procedimiento Init(AName, ATitle: string; ARate:

Real, Atime: Entero);

función GetPayAmount : Real;

función GetTime : Real;

fin;

TSalariado = objeto(TEmpleado)

función GetPayAmount : Real;

fin;

TComisionado = objeto(TSalariado)

Comisión : Real;

Monto de Ventas : Real;

constructor Init(AName, ATitle: String; ARate,

AComisión, ASimporte de Ventas: Real);

función GetPayAmount : Real;

fin;

implementación

función RoundPay(Salarios: Real) : Real;

{redondear los pagos para ignorar los montos inferiores a

Unidad monetaria}

comenzar

RoundPay := Trunc(Salarios * 100) / 100;

.

.

.

TEmployee es la parte superior de nuestra jerarquía de objetos y contiene el primer método GetPayAmount.

función TEmployee.GetPayAmount : Real;

comenzar

EjecutarError(211); { dar error de tiempo de ejecución }

fin;

Puede ser una sorpresa que el método dé un error en tiempo de ejecución. Si se llama a Employee.GetPayAmount, se produce un error en el programa. ¿Por qué? Porque TEmployee es la parte superior de nuestra jerarquía de objetos y no define a un trabajador real; por lo tanto, ninguno de los métodos TEmployee se llama de una manera particular, aunque pueden ser heredados. Todos nuestros empleados son por hora, asalariados oa destajo. Un error en tiempo de ejecución finaliza la ejecución del programa y genera 211, que corresponde a un mensaje de error asociado con una llamada de método abstracto (si el programa llama a TEmployee.GetPayAmount por error).

A continuación se muestra el método THourly.GetPayAmount, que tiene en cuenta aspectos como el pago de horas extra, las horas trabajadas, etc.

función THourly.GetPayAMount : Real;

var

Tiempo extra: entero;

comenzar

Horas extras := Tiempo - Umbral de horas extras;

si Horas extra > 0 entonces

GetPayAmount := RoundPay(Overtime Threshold * Rate +

Tasa de tiempo extra * factor de tiempo extra * tasa)

más

GetPayAmount := RoundPay(Tiempo * Tasa)

fin;

El método TSalaried.GetPayAmount es mucho más simple; apuesta en ello

dividido por el número de pagos:

función TSalaried.GetPayAmount : Real;

comenzar

GetPayAmount := RoundPay(Tasa / PayPeriods);

fin;

Si observa el método TCommissioned.GetPayAmount, verá que llama a TSalaried.GetPayAmount, calcula la comisión y la suma al valor devuelto por el método TSalaried. ObtenerCantidadPago.

función TCommissioned.GetPayAmount : Real;

comenzar

GetPayAmount := RoundPay(TSalariado.GetPayAmount +

Comisión * Monto de Ventas);

fin;

Nota importante: si bien los métodos se pueden anular, los campos de datos no se pueden anular. Una vez que se ha definido un campo de datos en una jerarquía de objetos, ningún tipo secundario puede definir un campo de datos con exactamente el mismo nombre.

3. Compatibilidad de tipos de objetos

La herencia modifica las reglas de compatibilidad de tipos de Borland Pascal hasta cierto punto. Entre otras cosas, un tipo derivado hereda la compatibilidad de tipos de todos sus tipos principales.

Esta compatibilidad de tipo extendida adopta tres formas:

1) entre implementaciones de objetos;

2) entre punteros a implementaciones de objetos;

3) entre parámetros formales y reales.

Sin embargo, es muy importante recordar que en las tres formas, la compatibilidad de tipos solo se extiende de hijo a padre. En otras palabras, los tipos secundarios se pueden usar libremente en lugar de los tipos principales, pero no al revés.

Por ejemplo, TSalaried es hijo de TEmployee y TSosh-missioned es hijo de TSalaried. Con eso en mente, considere las siguientes descripciones:

escribe

PEmpleado = ^TEmpleado;

PSasalariado = ^TSalariado;

PComisionado = ^TComisionado;

var

UnEmpleado: TEmpleado;

A Asalariado: T Asalariado;

PComisionado: TComisionado;

TEmployeePtr: PEmployee;

TSalariadoPtr: PSalariado;

TComisionadoPtr: PComisionado;

Bajo estas descripciones, los siguientes operadores son válidos

asignaciones:

UnEmpleado :=ASalariado;

ASalariado := AComisionado;

TComisionadoPtr := AComisionado;

Nota

A un objeto principal se le puede asignar una instancia de cualquiera de sus tipos derivados. No se permiten asignaciones anteriores.

Este concepto es nuevo para Pascal, y al principio puede ser difícil recordar en qué tipo de orden entra la compatibilidad. Debe pensar así: la fuente debe poder llenar completamente el receptor. Los tipos derivados contienen todo lo que contienen sus tipos principales debido a la propiedad de herencia. Por lo tanto, el tipo derivado tiene exactamente el mismo tamaño o (que suele ser el caso) es más grande que su padre, pero nunca más pequeño. Asignar un objeto padre (padre) a un hijo (hijo) podría dejar algunos campos del objeto hijo sin definir, lo que es peligroso y, por lo tanto, ilegal.

En las declaraciones de asignación, solo los campos que son comunes a ambos tipos se copiarán del origen al destino. En el operador de asignación:

UnEmpleado:= UnComisionado;

Solo los campos Nombre, Título y Tasa de AComisionado se copiarán a AnEmployee, ya que estos son los únicos campos que son comunes a TComisionado y TEmployee. La compatibilidad de tipos también funciona entre punteros a tipos de objetos y sigue las mismas reglas generales que para las implementaciones de objetos. Un puntero a un hijo se puede asignar a un puntero al padre. Dadas las definiciones anteriores, las siguientes asignaciones de punteros son válidas:

TSalariadoPtr:= TComisionadoPtr;

TEmployeePtr:= TSalariadoPtr;

TEmployeePtr:= PComisionadoPtr;

¡Recuerde que las asignaciones inversas no están permitidas!

Un parámetro formal (ya sea un valor o un parámetro variable) de un tipo de objeto dado puede tomar como parámetro real un objeto de su propio tipo u objetos de todos los tipos secundarios. Si define un encabezado de procedimiento como este:

procedimiento CalcFedTax(Víctima: TSalariado);

entonces los tipos de parámetros reales pueden ser TSalariado o TComisionado, pero no TEmployee. La víctima también puede ser un parámetro variable. En este caso, se siguen las mismas reglas de compatibilidad.

Nota

Hay una diferencia fundamental entre los parámetros de valor y los parámetros variables. Un parámetro de valor es un puntero al objeto real pasado como parámetro, mientras que un parámetro de variable es solo una copia del parámetro real. Además, esta copia incluye solo aquellos campos que están incluidos en el tipo del parámetro de valor formal. Esto significa que el parámetro real se convierte literalmente al tipo del parámetro formal. Un parámetro variable es más como convertir a un patrón, en el sentido de que el parámetro real permanece sin cambios.

De manera similar, si el parámetro formal es un puntero a un tipo de objeto, el parámetro real puede ser un puntero a ese tipo de objeto o a cualquier tipo secundario. Que se dé el título del procedimiento:

procedimiento Trabajador.Añadir(ATrabajador: PSalared);

Los tipos de parámetros reales válidos serían PSalariado o PComisionado, pero no PEmployee.

CLASE N° 14. Ensamblador

1. Sobre el ensamblador

Érase una vez, el ensamblador era un lenguaje sin saberlo, era imposible hacer que una computadora hiciera algo útil. Poco a poco la situación cambió. Aparecieron medios más convenientes de comunicación con una computadora. Pero a diferencia de otros lenguajes, ensamblador no murió; además, no podía hacer esto en principio. ¿Por qué? En busca de una respuesta, intentaremos comprender qué es el lenguaje ensamblador en general.

En resumen, el lenguaje ensamblador es una representación simbólica del lenguaje máquina. Todos los procesos en la máquina en el nivel de hardware más bajo son impulsados ​​​​solo por comandos (instrucciones) del lenguaje de la máquina. De esto queda claro que, a pesar del nombre común, el lenguaje ensamblador para cada tipo de computadora es diferente. Esto también se aplica a la apariencia de los programas escritos en ensamblador, y las ideas que este lenguaje refleja.

Resolver realmente problemas relacionados con el hardware (o, más aún, relacionados con el hardware, como acelerar un programa, por ejemplo) es imposible sin conocimientos de ensamblador.

Un programador o cualquier otro usuario puede usar cualquier herramienta de alto nivel hasta programas para construir mundos virtuales y, tal vez, ni siquiera sospechar que la computadora no está ejecutando realmente los comandos del lenguaje en el que está escrito su programa, sino su representación transformada. en forma de una secuencia aburrida y aburrida de comandos de un lenguaje completamente diferente: lenguaje de máquina. Ahora imagine que ese usuario tiene un problema no estándar. Por ejemplo, su programa debe funcionar con algún dispositivo inusual o realizar otras acciones que requieren el conocimiento de los principios del hardware de la computadora. Por muy bueno que sea el lenguaje en el que el programador escribió su programa, no puede prescindir de conocer el ensamblador. Y no es casualidad que casi todos los compiladores de lenguajes de alto nivel contengan medios para conectar sus módulos con módulos en ensamblador o admitan el acceso al nivel de programación en ensamblador.

Una computadora se compone de varios dispositivos físicos, cada uno de los cuales está conectado a una unidad, llamada unidad del sistema. Para entender su propósito funcional, veamos el diagrama de bloques de una computadora típica (Fig. 1). No pretende una precisión absoluta y solo pretende mostrar el propósito, la interconexión y la composición típica de los elementos de una computadora personal moderna.

Arroz. 1. Diagrama estructural de una computadora personal

2. Modelo de software del microprocesador

En el mercado informático actual, existe una amplia variedad de diferentes tipos de computadoras. Por lo tanto, es posible suponer que el consumidor tendrá una pregunta sobre cómo evaluar las capacidades de un tipo (o modelo) particular de una computadora y sus características distintivas de las computadoras de otros tipos (modelos). Para reunir todos los conceptos que caracterizan a una computadora en términos de sus propiedades funcionales controladas por programa, existe un término especial: arquitectura de la computadora. Por primera vez se empezó a mencionar el concepto de arquitectura de computadores con el advenimiento de las máquinas de 3ra generación para su evaluación comparativa.

Tiene sentido comenzar a aprender el lenguaje ensamblador de cualquier computadora solo después de descubrir qué parte de la computadora queda visible y disponible para programar en este lenguaje. Este es el llamado modelo de programa de computadora, parte del cual es el modelo de programa de microprocesador, que contiene treinta y dos registros, más o menos disponibles para uso del programador.

Estos registros se pueden dividir en dos grandes grupos:

1) 6 registros de usuarios;

2) 16 registros del sistema.

3. Registros de usuarios

Como su nombre lo indica, los registros de usuario se llaman porque el programador puede usarlos al escribir sus programas. Estos registros incluyen (Fig. 2):

1) ocho registros de 32 bits que los programadores pueden usar para almacenar datos y direcciones (también se denominan registros de propósito general (RON)):

eax/ax/ah/al;

ebx/bx/bh/bl;

edx/dx/dh/dl;

ecx/cx/canal/cl;

pb/pb;

esi/si;

editar/di;

esp/esp.

2) registros de seis segmentos: cs, ds, ss, es, fs, gs;

3) registros de estado y control:

registro de banderas eflags/banderas;

registro de puntero de comando eip/ip.

Arroz. 2. Registros de usuarios

Muchos de estos registros se dan con una barra oblicua. Estos no son registros diferentes, son partes de un gran registro de 32 bits. Se pueden utilizar en el programa como objetos separados.

4. Registros de propósito general

Todos los registros de este grupo permiten acceder a sus partes "inferiores". Solo las partes inferiores de 16 y 8 bits de estos registros se pueden utilizar para el autodireccionamiento. Los 16 bits superiores de estos registros no están disponibles como objetos independientes.

Enumeremos los registros pertenecientes al grupo de registros de propósito general. Dado que estos registros están ubicados físicamente en el microprocesador dentro de la unidad aritmético lógica (AL>), también se denominan registros ALU:

1) eax/ax/ah/al (registro acumulador) - batería. Se utiliza para almacenar datos intermedios. En algunos comandos se requiere el uso de este registro;

2) ebx/bx/bh/bl (Registro base) - registro base. Se utiliza para almacenar la dirección base de algún objeto en la memoria;

3) ecx/cx/ch/cl (registro de conteo) - registro de contador. Se utiliza en comandos que realizan algunas acciones repetitivas. Su uso suele estar implícito y oculto en el algoritmo del comando correspondiente.

Por ejemplo, el comando para organizar el bucle loop, además de transferir el control a un comando ubicado en una determinada dirección, analiza y decrementa en uno el valor del registro esx/cx;

4) edx/dx/dh/dl (registro de datos) - registro de datos.

Al igual que el registro eax/ax/ah/al, almacena datos intermedios. Algunos comandos requieren su uso; para algunos comandos esto sucede implícitamente.

Los dos registros siguientes se utilizan para admitir las llamadas operaciones en cadena, es decir, operaciones que procesan secuencialmente cadenas de elementos, cada uno de los cuales puede tener 32, 16 u 8 bits de longitud:

1) esi/si (registro de índice de origen) - índice de origen.

Este registro en operaciones en cadena contiene la dirección actual del elemento en la cadena fuente;

2) edi/di (Registro de índice de destino) - índice del receptor (destinatario). Este registro en operaciones en cadena contiene la dirección actual en la cadena de destino.

En la arquitectura del microprocesador a nivel de hardware y software, se admite una estructura de datos como una pila. Para trabajar con la pila en el sistema de instrucciones del microprocesador hay comandos especiales, y en el modelo de software del microprocesador hay registros especiales para esto:

1) esp/sp (registro de puntero de pila) - registro de puntero de pila. Contiene un puntero a la parte superior de la pila en el segmento de pila actual.

2) ebp/bp (registro de puntero base): registro de puntero base del marco de pila. Diseñado para organizar el acceso aleatorio a los datos dentro de la pila.

El uso de fijaciones fijas de registros para algunas instrucciones hace posible codificar su representación de máquina de manera más compacta. Si es necesario, conocer estas características ahorrará al menos algunos bytes de memoria ocupados por el código del programa.

5. Registros de segmento

Hay seis registros de segmento en el modelo de software del microprocesador: cs, ss, ds, es, gs, fs.

Su existencia se debe a los detalles de la organización y el uso de RAM por parte de los microprocesadores Intel. Se encuentra en el hecho de que el hardware del microprocesador soporta la organización estructural del programa en forma de tres partes, llamadas segmentos. En consecuencia, tal organización de la memoria se llama segmentada.

Para indicar los segmentos a los que el programa tiene acceso en un momento determinado, se pretenden registros de segmento. De hecho (con una ligera corrección) estos registros contienen las direcciones de memoria a partir de las cuales comienzan los segmentos correspondientes. La lógica del procesamiento de una instrucción de máquina se construye de tal manera que cuando se busca una instrucción, se accede a los datos del programa o se accede a la pila, se utilizan implícitamente direcciones en registros de segmentos bien definidos.

El microprocesador admite los siguientes tipos de segmentos.

1. Segmento de código. Contiene comandos de programa. Para acceder a este segmento, se utiliza el registro cs (registro de segmento de código), el registro de código de segmento. Contiene la dirección del segmento de instrucciones de la máquina al que tiene acceso el microprocesador (es decir, estas instrucciones se cargan en la canalización del microprocesador).

2. Segmento de datos. Contiene los datos procesados ​​por el programa. Para acceder a este segmento, se utiliza el registro ds (registro de segmento de datos), un registro de datos de segmento que almacena la dirección del segmento de datos del programa actual.

3. Segmento de pila. Este segmento es una región de memoria llamada pila. El microprocesador organiza el trabajo con la pila de acuerdo con el siguiente principio: el último elemento escrito en esta área se selecciona primero. Para acceder a este segmento, se utiliza el registro ss (registro de segmento de pila), el registro de segmento de pila que contiene la dirección del segmento de pila.

4. Segmento de datos adicional. Implícitamente, los algoritmos para ejecutar la mayoría de las instrucciones de máquina asumen que los datos que procesan están ubicados en el segmento de datos, cuya dirección está en el registro del segmento ds. Si un segmento de datos no es suficiente para el programa, entonces tiene la oportunidad de usar tres segmentos de datos adicionales. Pero a diferencia del segmento de datos principal, cuya dirección está contenida en el registro del segmento ds, cuando se usan segmentos de datos adicionales, sus direcciones deben especificarse explícitamente usando prefijos especiales de redefinición de segmento en el comando. Las direcciones de los segmentos de datos adicionales deben estar contenidas en los registros es, gs, fs (registros de segmentos de datos de extensión).

6. Registros de estado y control

El microprocesador incluye varios registros que contienen constantemente información sobre el estado del propio microprocesador y del programa cuyas instrucciones se cargan actualmente en la tubería. Estos registros incluyen:

1) registro de banderas eflags/banderas;

2) registro de puntero de comando eip/ip.

Con estos registros, puede obtener información sobre los resultados de la ejecución de comandos e influir en el estado del propio microprocesador. Consideremos con más detalle la finalidad y el contenido de estos registros.

1. eflags/flags (registro de banderas) - registro de banderas. La profundidad de bits de eflags/flags es de 32/16 bits. Los bits individuales de este registro tienen un propósito funcional específico y se denominan banderas. La parte inferior de este registro es exactamente igual que el registro de banderas para 18086. La Figura 3 muestra el contenido del registro de banderas electrónicas.

Arroz. 3. El contenido del registro de banderas electrónicas

Dependiendo de cómo se utilicen, las banderas del registro eflags/flags se pueden dividir en tres grupos:

1) ocho banderas de estado.

Estos indicadores pueden cambiar después de que se hayan ejecutado las instrucciones de la máquina. Las banderas de estado del registro de banderas electrónicas reflejan los detalles del resultado de la ejecución de operaciones aritméticas o lógicas. Esto hace posible analizar el estado del proceso computacional y responder a él usando comandos de salto condicional y llamadas a subrutinas. La Tabla 1 enumera los indicadores de estado y su propósito.

2) una bandera de control.

Denotado df (bandera de directorio). Se encuentra en el bit 10 del registro de banderas electrónicas y se usa con comandos encadenados. El valor de la bandera df determina la dirección del procesamiento elemento por elemento en estas operaciones: desde el principio de la cadena hasta el final (df = 0) o viceversa, desde el final de la cadena hasta su principio (df = 1). Hay comandos especiales para trabajar con el indicador df: eld (eliminar el indicador df) y std (establecer el indicador df). El uso de estos comandos le permite ajustar el indicador df de acuerdo con el algoritmo y garantizar que los contadores se incrementen o disminuyan automáticamente al realizar operaciones en cadenas.

3) cinco banderas del sistema.

Controla la E/S, las interrupciones enmascarables, la depuración, el cambio de tareas y el modo virtual 8086. No se recomienda que los programas de aplicación modifiquen estos indicadores innecesariamente, ya que esto hará que el programa finalice en la mayoría de los casos. La Tabla 2 enumera los indicadores del sistema y su propósito.

Tabla 1. Indicadores de estadoTabla 2. Indicadores del sistema

2. eip/ip (registro de puntero de instrucción) - registro de puntero de instrucción. El registro eip/ip tiene 32/16 bits de ancho y contiene el desplazamiento de la siguiente instrucción a ejecutar en relación con el contenido del registro del segmento cs en el segmento de instrucción actual. El programador no puede acceder directamente a este registro, pero su valor se carga y cambia mediante varios comandos de control, que incluyen comandos para saltos condicionales e incondicionales, procedimientos de llamada y retorno de procedimientos. La ocurrencia de interrupciones también modifica el registro eip/ip.

CONFERENCIA N° 15. Registros

1. Registros del sistema del microprocesador

El mismo nombre de estos registros sugiere que realizan funciones específicas en el sistema. El uso de los registros del sistema está estrictamente regulado. Son ellos quienes proporcionan el modo protegido. También se pueden considerar como parte de la arquitectura del microprocesador, que se deja visible deliberadamente para que un programador de sistemas calificado pueda realizar las operaciones de más bajo nivel.

Los registros del sistema se pueden dividir en tres grupos:

1) cuatro registros de control;

2) cuatro registros de direcciones del sistema;

3) ocho registros de depuración.

2. Registros de control

El grupo de registros de control incluye cuatro registros: cr0, cr1, cr2, cr3. Estos registros son para el control general del sistema. Los registros de control solo están disponibles para programas con nivel de privilegio 0.

Aunque el microprocesador tiene cuatro registros de control, solo tres de ellos están disponibles: se excluye cr1, cuyas funciones aún no están definidas (está reservado para uso futuro).

El registro cr0 contiene indicadores del sistema que controlan los modos de funcionamiento del microprocesador y reflejan su estado globalmente, independientemente de las tareas específicas que se realicen.

Propósito de las banderas del sistema:

1) pe (habilitación de protección), bit 0: habilita el modo de operación protegido. El estado de esta bandera muestra en cuál de los dos modos, real (pe = 0) o protegido (pe = 1), el microprocesador está funcionando en un momento dado;

2) mp (Math Present), bit 1 - la presencia de un coprocesador. Siempre 1;

3) ts (Tarea cambiada), bit 3 - cambio de tarea. El procesador establece automáticamente este bit cuando cambia a otra tarea;

4) am (Máscara de alineación), bit 18 - máscara de alineación. Este bit habilita (am = 1) o deshabilita (am = 0) el control de alineación;

5) cd (deshabilitar caché), bit 30: deshabilita la memoria caché.

Usando este bit, puede deshabilitar (cd = 1) o habilitar (cd = 0) el uso del caché interno (el caché de primer nivel);

6) pg (paginación), bit 31: habilitar (pg = 1) o deshabilitar (pg = 0) paginación.

La bandera se utiliza en el modelo de paginación de la organización de la memoria.

El registro cr2 se usa en la paginación de RAM para registrar la situación cuando la instrucción actual accedió a la dirección contenida en una página de memoria que actualmente no está en la memoria.

En tal situación, ocurre una excepción número 14 en el microprocesador y la dirección lineal de 32 bits de la instrucción que provocó esta excepción se escribe en el registro cr2. Con esta información, el manejador de excepciones 14 determina la página deseada, la cambia a memoria y reanuda la operación normal del programa;

El registro cr3 también se usa para paginar la memoria. Este es el llamado registro de directorio de páginas de primer nivel. Contiene la dirección base física de 20 bits del directorio de la página de la tarea actual. Este directorio contiene 1024 descriptores de 32 bits, cada uno de los cuales contiene la dirección de la tabla de páginas de segundo nivel. A su vez, cada una de las tablas de páginas de segundo nivel contiene 1024 descriptores de 32 bits que direccionan los marcos de página en la memoria. El tamaño del marco de la página es de 4 KB.

3. Registros de direcciones del sistema

Estos registros también se denominan registros de gestión de memoria.

Están diseñados para proteger programas y datos en el modo multitarea del microprocesador. Cuando se opera en modo protegido por microprocesador, el espacio de direcciones se divide en:

1) global - común a todas las tareas;

2) local - separado para cada tarea.

Esta separación explica la presencia de los siguientes registros del sistema en la arquitectura del microprocesador:

1) el registro de la tabla de descriptores globales gdtr (Global Descriptor Table Register), que tiene un tamaño de 48 bits y contiene una dirección base de 32 bits (bits 16-47) de la tabla de descriptores globales GDT y una dirección base de 16 bits (bits 0-15) valor límite, que es el tamaño en bytes de la tabla GDT;

2) el registro de tabla de descriptor local ldtr (Registro de tabla de descriptor local), que tiene un tamaño de 16 bits y contiene el llamado selector del descriptor de la tabla de descriptor local LDT Este selector es un puntero en la tabla GDT, que describe el segmento que contiene la tabla de descriptores locales LDT;

3) el registro de la tabla de descriptores de interrupciones idtr (Registro de tabla de descriptores de interrupciones), que tiene un tamaño de 48 bits y contiene una dirección base de 32 bits (bits 16-47) de la tabla de descriptores de interrupciones IDT y una dirección base de 16 bits (bits 0-15). XNUMX-XNUMX) valor límite, que es el tamaño en bytes de la tabla IDT;

4) Registro de tareas de 16 bits tr (Registro de tareas), que, al igual que el registro ldtr, contiene un selector, es decir, un puntero a un descriptor en la tabla GDT. Este descriptor describe el Estado del segmento de tareas (TSS) actual. Este segmento se crea para cada tarea del sistema, tiene una estructura estrictamente regulada y contiene el contexto (estado actual) de la tarea. El objetivo principal de los segmentos TSS es guardar el estado actual de una tarea en el momento de cambiar a otra tarea.

4. ​​Registros de depuración

Este es un grupo muy interesante de registros destinados a la depuración de hardware. Las herramientas de depuración de hardware aparecieron por primera vez en el microprocesador i486. En el hardware, el microprocesador contiene ocho registros de depuración, pero solo se utilizan seis de ellos.

Los registros dr0, dr1, dr2, dr3 tienen un ancho de 32 bits y están diseñados para establecer direcciones lineales de cuatro puntos de interrupción. El mecanismo utilizado en este caso es el siguiente: cualquier dirección generada por el programa actual se compara con las direcciones de los registros dr0... dr3, y si hay coincidencia se genera una excepción de depuración con el número 1.

El registro dr6 se denomina registro de estado de depuración. Los bits en este registro se establecen de acuerdo con las razones que causaron que ocurriera la última excepción número 1.

Enumeramos estos bits y su propósito:

1) b0: si este bit se establece en 1, la última excepción (interrupción) ocurrió como resultado de alcanzar el punto de control definido en el registro dr0;

2) b1: similar a b0, pero para un punto de control en el registro dr1;

3) b2: similar a b0, pero para un punto de control en el registro dr2;

4) bЗ - similar a b0, pero para un punto de control en el registro dr3;

5) bd (bit 13) - sirve para proteger los registros de depuración;

6) bs (bit 14) - establecido en 1 si la excepción 1 fue causada por el estado de la bandera tf = 1 en el registro de banderas electrónicas;

7) bt (bit 15) se establece en 1 si la excepción 1 fue causada por un cambio a una tarea con el bit de trampa establecido en TSS t = 1.

Todos los demás bits de este registro se rellenan con ceros. El controlador de excepciones 1, según el contenido de dr6, debe determinar el motivo de la excepción y tomar las medidas necesarias.

El registro dr7 se denomina registro de control de depuración. Contiene campos para cada uno de los cuatro registros de punto de interrupción de depuración que le permiten especificar las siguientes condiciones bajo las cuales se debe generar una interrupción:

1) ubicación de registro del punto de control: solo en la tarea actual o en cualquier tarea. Estos bits ocupan los 8 bits inferiores del registro dr7 (2 bits para cada punto de interrupción (en realidad un punto de interrupción) establecido por los registros dr0, dr1, dr2, dr3, respectivamente).

El primer bit de cada par es la denominada resolución local; configurarlo le dice al punto de interrupción que tenga efecto si está dentro del espacio de direcciones de la tarea actual.

El segundo bit de cada par define el permiso global, que indica que el punto de interrupción dado es válido dentro de los espacios de direcciones de todas las tareas que residen en el sistema;

2) el tipo de acceso por el cual se inicia la interrupción: solo al buscar un comando, al escribir o al escribir/leer datos. Los bits que determinan la naturaleza de la ocurrencia de una interrupción se encuentran en la parte superior de este registro. La mayoría de los registros del sistema son accesibles mediante programación.

LECCIÓN N° 16. Programas en Ensamblador

1. La estructura del programa en ensamblador

Un programa en lenguaje ensamblador es una colección de bloques de memoria llamados segmentos de memoria. Un programa puede constar de uno o más de estos bloques-segmentos. Cada segmento contiene una colección de oraciones de lenguaje, cada una de las cuales ocupa una línea separada de código de programa.

Las declaraciones de ensamblaje son de cuatro tipos:

1) comandos o instrucciones, que son análogos simbólicos de los comandos de máquina. Durante el proceso de traducción, las instrucciones de ensamblaje se convierten en los comandos correspondientes del conjunto de instrucciones del microprocesador;

2) macros. Son frases del texto del programa que se formalizan de cierta manera y son sustituidas por otras frases durante la emisión;

3) directivas, que son una indicación al traductor del ensamblador para realizar ciertas acciones. Las directivas no tienen equivalentes en la representación de máquinas;

4) líneas de comentarios que contengan cualquier carácter, incluidas las letras del alfabeto ruso. Los comentarios son ignorados por el traductor.

2. Sintaxis de ensamblaje

Las oraciones que componen un programa pueden ser una construcción sintáctica correspondiente a un comando, macro, directiva o comentario. Para que el traductor ensamblador los reconozca, deben estar formados de acuerdo con ciertas reglas sintácticas. Para ello, lo mejor es utilizar una descripción formal de la sintaxis del lenguaje, como las reglas de la gramática. Las formas más comunes de describir un lenguaje de programación de esta manera son los diagramas de sintaxis y las formas extendidas de Backus-Naur. Para uso práctico, los diagramas de sintaxis son más convenientes. Por ejemplo, la sintaxis de las declaraciones en lenguaje ensamblador se puede describir utilizando los diagramas de sintaxis que se muestran en las siguientes figuras.

Arroz. 4. Formato de la oración del ensamblador

Arroz. 5. Formato de directiva

Arroz. 6. Formato de comandos y macros

En estos dibujos:

1) nombre de la etiqueta: un identificador, cuyo valor es la dirección del primer byte de la oración del código fuente del programa que denota;

2) nombre: un identificador que distingue esta directiva de otras directivas del mismo nombre. Como resultado del procesamiento por parte del ensamblador de una determinada directiva, se pueden asignar ciertas características a este nombre;

3) un código de operación (COP) y una directiva son designaciones mnemotécnicas de la correspondiente instrucción de máquina, macroinstrucción o directiva traductora;

4) operandos: partes de las directivas de comando, macro o ensamblador, que denotan objetos en los que se realizan operaciones. Los operandos del ensamblador se describen mediante expresiones con constantes numéricas y de texto, etiquetas de variables e identificadores que utilizan signos de operador y algunas palabras reservadas.

¿Cómo usar diagramas de sintaxis? Es muy simple: todo lo que necesita hacer es encontrar y luego seguir la ruta desde la entrada del diagrama (izquierda) hasta su salida (derecha). Si tal camino existe, entonces la oración o construcción es sintácticamente correcta. Si no existe tal ruta, el compilador no aceptará esta construcción. Cuando trabaje con diagramas de sintaxis, preste atención a la dirección del recorrido indicada por las flechas, ya que entre los caminos puede haber aquellos que se pueden seguir de derecha a izquierda. De hecho, los diagramas sintácticos reflejan la lógica del traductor al analizar las oraciones de entrada del programa.

Los caracteres permitidos al escribir el texto de los programas son:

1) todas las letras latinas: A - Z, a - z. En este caso, las letras mayúsculas y minúsculas se consideran equivalentes;

2) números del 0 al 9;

3) signos ?, @, S, _, &;

4) separadores.

Las oraciones ensambladoras se forman a partir de lexemas, que son secuencias sintácticamente inseparables de símbolos lingüísticos válidos que tienen sentido para el traductor.

Las fichas son las siguientes.

1. Los identificadores son secuencias de caracteres válidos que se utilizan para designar objetos del programa, como códigos de operación, nombres de variables y nombres de etiquetas. La regla para escribir identificadores es la siguiente: un identificador puede constar de uno o más caracteres. Como caracteres, puede usar letras del alfabeto latino, números y algunos caracteres especiales: _, ?, $, @. Un identificador no puede comenzar con un carácter de dígito. La longitud del identificador puede ser de hasta 255 caracteres, aunque el traductor acepta solo los primeros 32 caracteres e ignora el resto. Puede ajustar la longitud de los posibles identificadores utilizando la opción de línea de comando mv. Además, es posible decirle al traductor que distinga entre letras mayúsculas y minúsculas o que ignore su diferencia (lo que se hace de forma predeterminada). Las opciones de línea de comando /mu, /ml, /mx se utilizan para esto.

2. Cadenas de caracteres: secuencias de caracteres entre comillas simples o dobles.

3. Números enteros en uno de los siguientes sistemas numéricos: binario, decimal, hexadecimal. La identificación de números al escribirlos en programas ensambladores se lleva a cabo de acuerdo con ciertas reglas:

1) los números decimales no requieren la identificación de ningún carácter adicional, por ejemplo, 25 o 139;

2) para identificar números binarios en el texto fuente del programa, es necesario poner la "b" latina después de escribir los ceros y unos que los componen, por ejemplo, 10010101 b;

3) Los números hexadecimales tienen más convenciones a la hora de escribir:

a) en primer lugar, se componen de los números 0...9, letras minúsculas y mayúsculas del alfabeto latino a, b, c, d, e, Gili D B, C, D, E, E

b) en segundo lugar, el traductor puede tener dificultades para reconocer los números hexadecimales debido a que pueden constar únicamente de los dígitos 0...9 (por ejemplo, 190845) o comenzar con una letra del alfabeto latino (por ejemplo, efl5). Para "explicar" al traductor que un token dado no es un número decimal o un identificador, el programador debe resaltar el número hexadecimal de una manera especial. Para ello, escribe la letra latina “h” al final de la secuencia de dígitos hexadecimales que forman un número hexadecimal. Es un requisito. Si un número hexadecimal comienza con una letra, se escribe un cero a la izquierda delante: 0 efl5 h.

Por lo tanto, descubrimos cómo se construyen las oraciones de un programa ensamblador. Pero esta es sólo la visión más superficial.

Casi todas las oraciones contienen una descripción del objeto sobre el cual o con la ayuda del cual se realiza alguna acción. Estos objetos se denominan operandos. Se pueden definir de la siguiente manera: los operandos son objetos (algunos valores, registros o celdas de memoria) que se ven afectados por instrucciones o directivas, o son objetos que definen o refinan la acción de instrucciones o directivas.

Los operandos se pueden combinar con operadores aritméticos, lógicos, bit a bit y de atributo para calcular algún valor o determinar una ubicación de memoria que se verá afectada por un comando o directiva determinada.

Consideremos con más detalle las características de los operandos en la siguiente clasificación:

1) operandos constantes o inmediatos: un número, cadena, nombre o expresión que tiene algún valor fijo. El nombre no debe ser reubicable, es decir, no debe depender de la dirección del programa a cargar en memoria. Por ejemplo, se puede definir con los operadores igual o =;

2) operandos de dirección, establezca la ubicación física del operando en la memoria especificando dos componentes de la dirección: segmento y desplazamiento (Fig. 7);

Arroz. 7. Sintaxis de descripción de operandos de dirección

3) operandos reubicables: cualquier nombre simbólico que represente algunas direcciones de memoria. Estas direcciones pueden indicar la ubicación en la memoria de algunas instrucciones (si el operando es una etiqueta) o datos (si el operando es el nombre de una ubicación de memoria en el segmento de datos).

Los operandos reubicables se diferencian de los operandos de dirección en que no están vinculados a una dirección de memoria física específica. El componente de segmento de la dirección del operando que se está moviendo es desconocido y se determinará después de que el programa se cargue en la memoria para su ejecución.

El contador de direcciones es un tipo específico de operando. Se denota con el signo S. La especificidad de este operando es que cuando el traductor del ensamblador encuentra este símbolo en el programa fuente, lo sustituye por el valor actual del contador de direcciones. El valor del contador de direcciones, o contador de ubicación, como a veces se le llama, es el desplazamiento de la instrucción de máquina actual desde el comienzo del segmento de código. En el formato de listado, la segunda o tercera columna corresponde al contador de direcciones (dependiendo de si la columna con el nivel de anidamiento está presente o no en el listado). Si tomamos cualquier listado como ejemplo, se puede ver que cuando el traductor procesa la siguiente instrucción del ensamblador, el contador de direcciones aumenta según la longitud de la instrucción de máquina generada. Es importante entender este punto correctamente. Por ejemplo, procesar directivas de ensamblador no cambia el contador. Las directivas, a diferencia de los comandos del ensamblador, son solo instrucciones para que el compilador realice ciertas acciones para formar la representación de máquina del programa, y ​​para ellas el compilador no genera ninguna construcción en la memoria.

Cuando utilice una expresión de este tipo para saltar, tenga en cuenta la longitud de la instrucción en la que se utiliza esta expresión, ya que el valor del contador de dirección corresponde al desplazamiento en el segmento de instrucción de esta instrucción, y no a la instrucción que le sigue. . En nuestro ejemplo, el comando jmp ocupa 2 bytes. Pero tenga cuidado, la longitud de una instrucción depende de los operandos que utilice. Una instrucción con operandos de registro será más corta que una instrucción con uno de sus operandos ubicado en la memoria. En la mayoría de los casos, esta información se puede obtener conociendo el formato de la instrucción de la máquina y analizando la columna de listado con el código objeto de la instrucción;

4) el operando de registro es solo un nombre de registro. En un programa ensamblador, puede usar los nombres de todos los registros de propósito general y la mayoría de los registros del sistema;

5) operandos base e índice. Este tipo de operando se utiliza para implementar una base indirecta, direccionamiento de índice indirecto o combinaciones y extensiones de los mismos;

6) Los operandos estructurales se utilizan para acceder a un elemento específico de un tipo de datos complejo llamado estructura.

Los registros (similares a un tipo de estructura) se utilizan para acceder a un campo de bits de algún registro.

Los operandos son componentes elementales que forman parte de una instrucción de máquina, denotando los objetos sobre los que se realiza la operación. En un caso más general, los operandos se pueden incluir como componentes en formaciones más complejas llamadas expresiones. Las expresiones son combinaciones de operandos y operadores, considerados como un todo. El resultado de la evaluación de expresiones puede ser la dirección de alguna celda de memoria o algún valor constante (absoluto).

Ya hemos considerado los posibles tipos de operandos. Ahora listamos los posibles tipos de operadores ensambladores y las reglas sintácticas para la formación de expresiones ensambladoras, y damos una breve descripción de los operadores.

1. Operadores aritméticos. Éstos incluyen:

1) "+" y "-" unarios;

2) "+" y "-" binarios;

3) multiplicación "*";

4) división entera "/";

5) obtener el resto de la división "mod".

Estos operadores están ubicados en los niveles de precedencia 6,7,8 en la Tabla 4.

Arroz. 8. Sintaxis de las operaciones aritméticas

2. Los operadores de desplazamiento desplazan la expresión por el número especificado de bits (Fig. 9).

Arroz. 9. Sintaxis de los operadores de desplazamiento

3. Los operadores de comparación (devuelven el valor "verdadero" o "falso") están destinados a la formación de expresiones lógicas (Fig. 10 y Tabla 3). El valor lógico "verdadero" corresponde a una unidad digital y "falso" a cero.

Arroz. 10. Sintaxis de los operadores de comparación

Tabla 3. Operadores de comparación

4. Los operadores lógicos realizan operaciones bit a bit en expresiones (Fig. 11). Las expresiones deben ser absolutas, es decir, cuyo valor numérico puede ser calculado por el traductor.

Arroz. 11. Sintaxis de operadores lógicos

5. Operador de índice []. Los paréntesis también son un operador, y el traductor percibe su presencia como una instrucción para agregar el valor de expresión_1 detrás de estos corchetes con expresión_2 entre corchetes (Fig. 12).

Arroz. 12. Sintaxis del operador de índice

Obsérvese que en la literatura sobre ensamblador se adopta la siguiente designación: cuando el texto se refiere al contenido de un registro, su nombre se toma entre paréntesis. También nos adherimos a esta notación.

6. El operador de redefinición de tipo ptr se utiliza para redefinir o calificar el tipo de una etiqueta o variable definida por una expresión (Fig. 13).

El tipo puede tomar uno de los siguientes valores: byte, word, dword, qword, tbyte, near, far.

Arroz. 13. Sintaxis del operador de redefinición de tipo

7. El operador de redefinición de segmento ":" (dos puntos) hace que la dirección física se calcule en relación con un componente de segmento específico: "nombre de registro de segmento", "nombre de segmento" de la directiva SEGMENT correspondiente o "nombre de grupo" (Fig. 14). Cuando discutimos la segmentación, hablamos sobre el hecho de que el microprocesador a nivel de hardware admite tres tipos de segmentos: código, pila y datos. ¿Qué es este soporte de hardware? Por ejemplo, para seleccionar la ejecución del siguiente comando, el microprocesador necesariamente debe mirar el contenido del registro de segmento cs y solo él. Y este registro, como sabemos, contiene la dirección física (todavía no cambiada) del comienzo del segmento de instrucción. Para obtener la dirección de una instrucción en particular, el microprocesador necesita multiplicar el contenido de cs por 16 (lo que significa un cambio de cuatro bits) y agregar el valor resultante de 20 bits al contenido de 16 bits del registro ip. Aproximadamente lo mismo sucede cuando el microprocesador procesa los operandos en la instrucción de máquina. Si ve que el operando es una dirección (una dirección efectiva que es solo una parte de la dirección física), entonces sabe en qué segmento buscarlo; de forma predeterminada, es el segmento cuya dirección de inicio se almacena en el registro de segmento ds .

Pero, ¿qué pasa con el segmento de pila? En el contexto de nuestra consideración, estamos interesados ​​en los registros sp y bp. Si el microprocesador ve uno de estos registros como un operando (o parte de él, si el operando es una expresión), por defecto forma la dirección física del operando, usando el contenido del registro ss como su componente de segmento. Este es un conjunto de microprogramas en la unidad de control de microprogramas, cada uno de los cuales ejecuta una de las instrucciones en el sistema de instrucciones de la máquina del microprocesador. Cada microprograma funciona según su propio algoritmo. Por supuesto, no puede cambiarlo, pero puede corregirlo ligeramente. Esto se hace usando el campo de prefijo de comando de máquina opcional. Si estamos de acuerdo en cómo funciona el comando, entonces falta este campo. Si queremos hacer una enmienda (si, por supuesto, está permitido para un comando en particular) al algoritmo del comando, entonces es necesario formar un prefijo apropiado.

Un prefijo es un valor de un byte cuyo valor numérico determina su propósito. El microprocesador reconoce por el valor especificado que este byte es un prefijo, y el trabajo adicional del microprograma se realiza teniendo en cuenta la instrucción recibida para corregir su trabajo. Ahora estamos interesados ​​​​en uno de ellos: el prefijo de reemplazo (redefinición) de segmento. Su finalidad es indicarle al microprocesador (y de hecho, al firmware) que no queremos usar el segmento por defecto. Las posibilidades de tal redefinición son, por supuesto, limitadas. El segmento de comando no se puede redefinir, la dirección del siguiente comando ejecutable está determinada únicamente por el par cs: ip. Y aquí los segmentos de la pila y los datos - es posible. Para eso está el operador ":". El traductor del ensamblador, al procesar esta declaración, genera el prefijo de reemplazo del segmento de un byte correspondiente.

Arroz. 14. Sintaxis del operador de redefinición de segmento

8. El operador de denominación del tipo de estructura "."(punto) también obliga al compilador a realizar ciertos cálculos si aparece en una expresión.

9. El operador para obtener el segmento componente de la dirección de la expresión seg devuelve la dirección física del segmento para la expresión (Fig. 15), que puede ser una etiqueta, variable, nombre de segmento, nombre de grupo o algún nombre simbólico .

Arroz. 15. Sintaxis del operador receptor del componente de segmento

10. El operador para obtener el desplazamiento de la expresión desplazamiento permite obtener el valor del desplazamiento de la expresión (Fig. 16) en bytes relativo al inicio del segmento en el que se define la expresión.

Arroz. 16. Sintaxis del operador get compensado

Al igual que en los lenguajes de alto nivel, la ejecución de operadores ensambladores al evaluar expresiones se realiza de acuerdo con sus prioridades (Tabla 4). Las operaciones con la misma prioridad se ejecutan secuencialmente de izquierda a derecha. Es posible cambiar el orden de ejecución colocando paréntesis que tengan la prioridad más alta.

Tabla 4. Operadores y su precedencia

3. Directivas de segmentación

En el curso de la discusión anterior, descubrimos todas las reglas básicas para escribir instrucciones y operandos en un programa en lenguaje ensamblador. La cuestión de cómo formatear adecuadamente la secuencia de comandos para que el traductor pueda procesarlos y el microprocesador pueda ejecutarlos permanece abierta.

Al considerar la arquitectura del microprocesador, aprendimos que tiene seis registros de segmento, a través de los cuales puede trabajar simultáneamente:

1) con un segmento de código;

2) con un segmento de pila;

3) con un segmento de datos;

4) con tres segmentos de datos adicionales.

Recuerde nuevamente que un segmento es físicamente un área de memoria ocupada por comandos y (o) datos cuyas direcciones se calculan en relación con el valor en el registro de segmento correspondiente.

La descripción sintáctica de un segmento en ensamblador es la construcción que se muestra en la Figura 17:

Arroz. 17. Sintaxis de descripción de segmento

Es importante tener en cuenta que la funcionalidad de un segmento es algo más amplia que simplemente dividir el programa en bloques de código, datos y pila. La segmentación es parte de un mecanismo más general relacionado con el concepto de programación modular. Implica la unificación del diseño de los módulos de objetos creados por el compilador, incluidos los de diferentes lenguajes de programación. Esto le permite combinar programas escritos en diferentes idiomas. Los operandos en la directiva SEGMENT están destinados a la implementación de varias opciones para tal unión.

Permítanos considerarlos con más detalle.

1. El atributo de alineación del segmento (tipo de alineación) le dice al enlazador que se asegure de que el comienzo del segmento se coloque en el límite especificado. Esto es importante porque la alineación adecuada hace que el acceso a los datos sea más rápido en los procesadores i80x86. Los valores válidos para este atributo son los siguientes:

1) BYTE: no se realiza la alineación. Un segmento puede comenzar en cualquier dirección de memoria;

2) PALABRA: el segmento comienza en una dirección que es un múltiplo de dos, es decir, el último bit (menos significativo) de la dirección física es 0 (alineado con el límite de la palabra);

3) DWORD: el segmento comienza en una dirección que es un múltiplo de cuatro, es decir, los dos últimos bits (menos significativos) son 0 (alineación de límite de palabra doble);

4) PARA: el segmento comienza en una dirección que es un múltiplo de 16, es decir, el último dígito hexadecimal de la dirección debe ser Oh (alineación con el límite del párrafo);

5) PÁGINA: el segmento comienza en una dirección que es un múltiplo de 256, es decir, los dos últimos dígitos hexadecimales deben ser 00h (alineados con el límite de una página de 256 bytes);

6) MEMPAGE: el segmento comienza en una dirección que es un múltiplo de 4 KB, es decir, los últimos tres dígitos hexadecimales deben ser OOOh (dirección de la siguiente página de memoria de 4 KB). El tipo de alineación predeterminado es PARA.

2. El atributo de segmento de combinación (tipo combinatorio) le dice al enlazador cómo combinar segmentos de diferentes módulos que tienen el mismo nombre. Los valores de atributo de combinación de segmento pueden ser:

1) PRIVADO: el segmento no se fusionará con otros segmentos con el mismo nombre fuera de este módulo;

2) PÚBLICO: hace que el enlazador conecte todos los segmentos con el mismo nombre. El nuevo segmento fusionado será completo y continuo. Todas las direcciones (desplazamientos) de los objetos, y esto puede depender del tipo de comando y segmento de datos, se calcularán en relación con el comienzo de este nuevo segmento;

3) COMÚN: coloca todos los segmentos con el mismo nombre en la misma dirección. Todos los segmentos con el nombre dado se superpondrán y compartirán memoria. El tamaño del segmento resultante será igual al tamaño del segmento más grande;

4) AT xxxx: ubica el segmento en la dirección absoluta del párrafo (el párrafo es la cantidad de memoria, un múltiplo de 16; por lo tanto, el último dígito hexadecimal de la dirección del párrafo es 0). La dirección absoluta de un párrafo viene dada por xxx. El enlazador coloca el segmento en una dirección de memoria dada (esto puede usarse, por ejemplo, para acceder a la memoria de video o un área ROM), teniendo en cuenta el atributo de combinación. Físicamente, esto significa que el segmento, al cargarlo en memoria, se ubicará a partir de esta dirección absoluta del párrafo, pero para acceder a él, se debe cargar en el registro del segmento correspondiente el valor especificado en el atributo. Todas las etiquetas y direcciones en un segmento así definido son relativas a la dirección absoluta dada;

5) PILA - definición de un segmento de pila. Hace que el enlazador conecte todos los segmentos con el mismo nombre y calcule las direcciones en estos segmentos en relación con el registro ss. El tipo combinado STACK (pila) es similar al tipo combinado PUBLIC, excepto que el registro ss es el registro de segmento estándar para los segmentos de pila. El registro sp se establece al final del segmento de pila concatenado. Si no se especifica ningún segmento de pila, el vinculador emitirá una advertencia de que no se encontró ningún segmento de pila. Si se crea un segmento de pila y no se usa el tipo STACK combinado, el programador debe cargar explícitamente la dirección del segmento en el registro ss (similar al registro ds).

El atributo de combinación predeterminado es PRIVADO.

3. Un atributo de clase de segmento (tipo de clase) es una cadena entre comillas que ayuda al enlazador a determinar el orden de segmento adecuado al ensamblar un programa a partir de varios segmentos de módulo. El enlazador combina todos los segmentos con el mismo nombre de clase en la memoria (el nombre de clase generalmente puede ser cualquier cosa, pero es mejor si refleja la funcionalidad del segmento). Un uso típico de un nombre de clase es agrupar todos los segmentos de código de un programa (generalmente se usa la clase "código" para esto). Con el mecanismo de escritura de clases, también puede agrupar segmentos de datos inicializados y no inicializados.

4. Atributo de tamaño de segmento. Para procesadores i80386 y superiores, los segmentos pueden ser de 16 o 32 bits. Esto afecta principalmente el tamaño del segmento y el orden en que se forma la dirección física dentro de él. El atributo puede tomar los siguientes valores:

1) USE16: esto significa que el segmento permite el direccionamiento de 16 bits. Al formar una dirección física, solo se puede usar un desplazamiento de 16 bits. En consecuencia, dicho segmento puede contener hasta 64 KB de código o datos;

2)USE32: el segmento será de 32 bits. Al formar una dirección física, se puede utilizar un desplazamiento de 32 bits. Por lo tanto, dicho segmento puede contener hasta 4 GB de código o datos.

Todos los segmentos son iguales en sí mismos, ya que las directivas SEGMENT y ENDS no contienen información sobre el propósito funcional de los segmentos. Para usarlos como código, datos o segmentos de la pila, es necesario informar al traductor sobre esto con anticipación, para lo cual se usa una directiva especial ASSUME, que tiene el formato que se muestra en la Fig. 18. Esta directiva le dice al traductor qué segmento está vinculado a qué registro de segmento. A su vez, esto permitirá al traductor enlazar correctamente los nombres simbólicos definidos en los segmentos. La vinculación de segmentos a registros de segmento se lleva a cabo utilizando los operandos de esta directiva, en la que segment_name debe ser el nombre del segmento, definido en el texto fuente del programa por la directiva SEGMENT o la palabra clave nothing. Si solo se usa la palabra clave nada como operando, entonces las asignaciones de registro de segmento anteriores se cancelan y para los seis registros de segmento a la vez. Pero la palabra clave nada se puede usar en lugar del argumento del nombre del segmento; en este caso, la conexión entre el segmento con el nombre de segmento de nombre y el registro de segmento correspondiente se romperá selectivamente (ver Fig. 18).

Arroz. 18. Directiva ASUME

Para programas simples que contienen un segmento para código, datos y pila, nos gustaría simplificar su descripción. Para hacer esto, los traductores MASM y TASM introdujeron la capacidad de usar directivas de segmentación simplificadas. Pero aquí surgió un problema relacionado con el hecho de que era necesario compensar de alguna manera la incapacidad de controlar directamente la colocación y combinación de segmentos. Para ello, junto con las directivas de segmentación simplificadas, comenzaron a utilizar la directiva para especificar el modelo de memoria MODEL, que parcialmente comenzó a controlar la ubicación de los segmentos y a realizar las funciones de la directiva ASSUME (por lo tanto, al usar directivas de segmentación simplificadas, el Se puede omitir la directiva ASSUME). Esta directiva vincula segmentos, que en el caso de usar directivas de segmentación simplificadas, tienen nombres predefinidos, con registros de segmento (aunque aún debe inicializar ds explícitamente).

La sintaxis de la directiva MODEL se muestra en la Figura 19.

Arroz. 19. Sintaxis de la directiva MODEL

El parámetro obligatorio de la directiva MODEL es el modelo de memoria. Este parámetro define el modelo de segmentación de memoria para la POU. Se supone que un módulo de programa puede tener solo ciertos tipos de segmentos, que están definidos por las directivas de descripción de segmento simplificadas que mencionamos anteriormente. Estas directivas se muestran en la Tabla 5.

Tabla 5. Directivas de definición de segmento simplificadas

La presencia del parámetro [nombre] en algunas directivas indica que es posible definir varios segmentos de este tipo. Por otro lado, la existencia de varios tipos de segmentos de datos se debe al requisito de asegurar la compatibilidad con algunos compiladores de lenguajes de alto nivel, que crean diferentes segmentos de datos para datos inicializados y no inicializados, así como constantes.

Al usar la directiva MODEL, el traductor pone a disposición varios identificadores a los que se puede acceder durante la operación del programa para obtener información sobre ciertas características de un modelo de memoria dado (Tabla 7). Hagamos una lista de estos identificadores y sus valores (Tabla 6).

Tabla 6. Identificadores creados por la directiva MODEL

Ahora podemos completar nuestra discusión de la directiva MODEL. Los operandos de la directiva MODEL se utilizan para especificar un modelo de memoria que define el conjunto de segmentos de programa, los tamaños de los segmentos de datos y códigos, y el método de vinculación de segmentos y registros de segmentos. La Tabla 7 muestra algunos valores del parámetro "modelo de memoria" de la directiva MODEL.

Tabla 7. Modelos de memoria

El parámetro "modificador" de la directiva MODEL le permite especificar algunas características del uso del modelo de memoria seleccionado (Tabla 8).

Tabla 8. Modificadores del modelo de memoria

Los parámetros opcionales "idioma" y "modificador de idioma" definen algunas características de las llamadas a procedimientos. La necesidad de usar estos parámetros surge al escribir y vincular programas en varios lenguajes de programación.

Las directivas de segmentación estándar y simplificada que hemos descrito no se excluyen mutuamente. Las directivas estándar se utilizan cuando el programador desea tener un control completo sobre la colocación de segmentos en la memoria y su combinación con segmentos de otros módulos.

Las directivas simplificadas son útiles para programas simples y programas destinados a vincularse con módulos de programa escritos en lenguajes de alto nivel. Esto permite que el enlazador vincule de manera eficiente módulos de diferentes idiomas al estandarizar la vinculación y la administración.

LECCIÓN N° 17. Estructuras de comando en Assembler

1. Estructura de instrucciones de la máquina

Un comando de máquina es una indicación al microprocesador, codificada según ciertas reglas, para realizar alguna operación o acción. Cada comando contiene elementos que definen:

1) ¿Qué hacer? (La respuesta a esta pregunta la da el elemento de comando llamado código de operación (COP).);

2) objetos en los que se debe hacer algo (estos elementos se denominan operandos);

3) ¿cómo hacer? (Estos elementos se denominan tipos de operandos, generalmente especificados implícitamente).

El formato de instrucción de máquina que se muestra en la Figura 20 es el más general. La longitud máxima de una instrucción máquina es de 15 bytes. Un comando real puede contener un número mucho menor de campos, hasta uno, solo KOP.

Arroz. 20. Formato de instrucción de máquina

Describamos el propósito de los campos de instrucción de máquina.

1. Prefijos.

Elementos de instrucción de máquina opcionales, cada uno de los cuales es de 1 byte o puede omitirse. En la memoria, los prefijos preceden al comando. El propósito de los prefijos es modificar la operación realizada por el comando. Una aplicación puede utilizar los siguientes tipos de prefijos:

1) prefijo de reemplazo de segmento. Especifica explícitamente qué registro de segmento se usa en esta instrucción para direccionar la pila o los datos. El prefijo anula la selección de registro de segmento predeterminado. Los prefijos de reemplazo de segmento tienen los siguientes significados:

a) 2eh - reemplazo del segmento cs;

b) 36h - sustitución del segmento ss;

c) 3eh - sustitución del segmento ds;

d) 26h - sustitución de segmentos;

e) 64h - sustitución del segmento fs;

e) 65h - sustitución del segmento gs;

2) el prefijo de bitness de la dirección especifica el bitness de la dirección (32 o 16 bits). A cada instrucción que usa un operando de dirección se le asigna el ancho de bit de la dirección de ese operando. Esta dirección puede ser de 16 o 32 bits. Si la longitud de la dirección para este comando es de 16 bits, esto significa que el comando contiene un desplazamiento de 16 bits (Fig. 20), corresponde a un desplazamiento de 16 bits del operando de dirección con respecto al comienzo de algún segmento. En el contexto de la Figura 21, este desplazamiento se denomina dirección efectiva. Si la dirección es de 32 bits, esto significa que el comando contiene un desplazamiento de 32 bits (Fig. 20), corresponde a un desplazamiento de 32 bits del operando de dirección con respecto al comienzo del segmento, y su valor forma un 32 -compensación de bits en el segmento. El prefijo de bitness de dirección se puede utilizar para cambiar el bitness de dirección predeterminado. Este cambio solo afectará al comando precedido por el prefijo;

Arroz. 21. El mecanismo de formación de una dirección física en modo real

3) El prefijo de ancho de bit de operando es similar al prefijo de ancho de bit de dirección, pero indica la longitud de bit de operando (32 bits o 16 bits) con la que opera la instrucción. ¿Cuáles son las reglas para configurar los atributos de dirección y ancho de bits del operando de forma predeterminada?

En modo real y modo virtual 18086, los valores de estos atributos son de 16 bits. En modo protegido, los valores de los atributos dependen del estado del bit D en los descriptores del segmento ejecutable. Si D = 0, los valores de atributo predeterminados son 16 bits; si D = 1, entonces 32 bits.

Valores de prefijo para ancho de operando 66h y ancho de dirección 67h. Con el prefijo de bit de dirección de modo real, puede usar direccionamiento de 32 bits, pero tenga en cuenta el límite de tamaño de segmento de 64 KB. Similar al prefijo de ancho de dirección, puede usar el prefijo de ancho de operando en modo real para trabajar con operandos de 32 bits (por ejemplo, en instrucciones aritméticas);

4) el prefijo de repetición se usa con comandos en cadena (comandos de procesamiento de línea). Este prefijo "recorre" el comando para procesar todos los elementos de la cadena. El sistema de comandos admite dos tipos de prefijos:

a) incondicional (rep - OOh), obligando a repetir el comando encadenado un determinado número de veces;

b) condicional (repe/repz - OOh, repne/repnz - 0f2h), que, al hacer un bucle, comprueba algunas banderas y, como resultado de la comprobación, es posible salir antes del bucle.

2. Código de operación.

Elemento obligatorio que describe la operación realizada por el comando. Muchos comandos corresponden a varios códigos de operación, cada uno de los cuales determina los matices de la operación. Los campos subsiguientes de la instrucción de la máquina determinan la ubicación de los operandos involucrados en la operación y los detalles de su uso. La consideración de estos campos está relacionada con las formas de especificar operandos en una instrucción de máquina y, por lo tanto, se realizará más adelante.

3. Modo de direccionamiento byte modr/m.

El valor de este byte determina la forma de dirección del operando utilizada. Los operandos pueden estar en la memoria en uno o dos registros. Si el operando está en la memoria, entonces el byte modr/m especifica los componentes (registros de desplazamiento, base e índice) usados ​​para calcular su dirección efectiva (Figura 21). En modo protegido, el byte sib (Scale-Index-Base) también se puede usar para determinar la ubicación del operando en la memoria. El byte modr/m consta de tres campos (Fig. 20):

1) el campo mod determina el número de bytes ocupados por la dirección del operando en el comando (Fig. 20, el campo de desplazamiento en el comando). El campo mod se usa junto con el campo r/m, que especifica cómo modificar la dirección del operando de "compensación de instrucción". Por ejemplo, si mod = 00, esto significa que no hay un campo de compensación en el comando, y la dirección del operando está determinada por el contenido de la base y (o) el registro de índice. Los registros que se utilizarán para calcular la dirección efectiva están determinados por el valor de este byte. Si mod = 01, esto significa que el campo de compensación está presente en el comando, ocupa 1 byte y es modificado por el contenido del registro base y (o) índice. Si mod = 10, esto significa que el campo de desplazamiento está presente en el comando, ocupa 2 o 4 bytes (dependiendo del tamaño de dirección predeterminado o definido por prefijo) y es modificado por el contenido del registro base y/o índice. Si mod = 11, significa que no hay operandos en memoria: están en registros. El mismo valor del byte mod se usa cuando se usa un operando inmediato en la instrucción;

2) el campo reg/cop determina el registro ubicado en el comando en lugar del primer operando, o una posible extensión del código de operación;

3) el campo r/m se usa junto con el campo mod y determina el registro ubicado en el comando en el lugar del primer operando (si mod = 11), o los registros base e índice usados ​​para calcular la dirección efectiva (junto con el campo de desplazamiento en el comando).

4. Escala de bytes - índice - base (byte sib).

Se utiliza para ampliar las posibilidades de direccionamiento de operandos. La presencia del byte sib en una instrucción máquina se indica mediante una combinación de uno de los valores 01 o 10 del campo mod y el valor del campo r/m = 100. El byte sib consta de tres campos:

1) campos de escala ss. Este campo contiene el factor de escala para el índice del componente de índice, que ocupa los siguientes 3 bits del byte sib. El campo ss puede contener uno de los siguientes valores: 1, 2, 4, 8.

Al calcular la dirección efectiva, el contenido del registro de índice se multiplicará por este valor;

2) campos de índice. Se utiliza para almacenar el número de registro de índice que se utiliza para calcular la dirección efectiva del operando;

3) campos base. Se utiliza para almacenar el número de registro base, que también se utiliza para calcular la dirección efectiva del operando. Casi todos los registros de propósito general se pueden usar como registros base e índice.

5. Comando de campo compensado.

Un entero con signo de 8, 16 o 32 bits que representa, en su totalidad o en parte (sujeto a las consideraciones anteriores), el valor de la dirección efectiva del operando.

6. El campo del operando inmediato.

Un campo opcional que es un operando inmediato de 8 bits, 16 bits o 32 bits. La presencia de este campo, por supuesto, se refleja en el valor del byte modr/m.

2. Métodos para especificar operandos de instrucción

El operando se establece implícitamente en el nivel de firmware

En este caso, la instrucción explícitamente no contiene operandos. El algoritmo de ejecución de comandos utiliza algunos objetos predeterminados (registros, banderas en eflags, etc.).

Por ejemplo, los comandos cli y sti funcionan implícitamente con el indicador de interrupción if en el registro eflags, y el comando xlat accede implícitamente al registro al ya una línea en la memoria en la dirección especificada por el par de registros ds:bx.

El operando se especifica en la propia instrucción (operando inmediato)

El operando está en el código de instrucción, es decir, es parte de él. Para almacenar dicho operando, se asigna un campo de hasta 32 bits de longitud en el comando (Figura 20). El operando inmediato solo puede ser el segundo operando (fuente). El operando de destino puede estar en la memoria o en un registro.

Por ejemplo: mov ax,0ffffti mueve la constante hexadecimal ffff al registro ax. El comando add sum, 2 suma el contenido del campo en la dirección sum con el número entero 2 y escribe el resultado en el lugar del primer operando, es decir, en la memoria.

El operando está en uno de los registros.

Los operandos de registro se especifican mediante nombres de registro. Los registros se pueden utilizar:

1) registros de 32 bits EAX, EBX, ECX, EDX, ESI, EDI, ESP, EUR;

2) registros de 16 bits AX, BX, CX, DX, SI, DI, SP, BP;

3) registros de 8 bits AH, AL, BH, BL, CH, CL, DH, DL;

4) registros de segmento CS, DS, SS, ES, FS, GS.

Por ejemplo, la instrucción add ax,bx suma los contenidos de los registros ax y bx y escribe el resultado en bx. El comando dec si decrementa el contenido de si en 1.

El operando está en la memoria.

Esta es la forma más compleja y al mismo tiempo más flexible de especificar operandos. Le permite implementar los siguientes dos tipos principales de direccionamiento: directo e indirecto.

A su vez, el direccionamiento indirecto tiene las siguientes variedades:

1) direccionamiento base indirecto; su otro nombre es registro de direccionamiento indirecto;

2) direccionamiento indirecto de base con desplazamiento;

3) direccionamiento de índice indirecto con desplazamiento;

4) direccionamiento de índice base indirecto;

5) direccionamiento de índice base indirecto con desplazamiento.

El operando es un puerto de E/S.

Además del espacio de direcciones de RAM, el microprocesador mantiene un espacio de direcciones de E/S, que se utiliza para acceder a los dispositivos de E/S. El espacio de direcciones de E/S es de 64 KB. Las direcciones se asignan para cualquier dispositivo informático en este espacio. Un valor de dirección particular dentro de este espacio se denomina puerto de E/S. Físicamente, el puerto de E/S corresponde a un registro de hardware (que no debe confundirse con un registro de microprocesador), al que se accede mediante instrucciones especiales de entrada y salida del ensamblador.

Por ejemplo:

en al,60h; ingrese un byte desde el puerto 60h

Los registros direccionados por un puerto de E/S pueden tener 8,16, 32 o XNUMX bits de ancho, pero el ancho de bit de registro es fijo para un puerto en particular. Los comandos de entrada y salida operan en un rango fijo de objetos. Los llamados registros acumuladores EAX, AX, AL se utilizan como fuente de información o como destinatario. La elección del registro está determinada por el bitness del puerto. El número de puerto se puede especificar como un operando inmediato en las instrucciones de entrada y salida, o como un valor en el registro DX. El último método le permite determinar dinámicamente el número de puerto en el programa.

El operando está en la pila.

Las instrucciones pueden no tener operandos, pueden tener uno o dos operandos. La mayoría de las instrucciones requieren dos operandos, uno de los cuales es el operando de origen y el otro es el operando de destino. Es importante que un operando pueda ubicarse en un registro o memoria, y el segundo operando debe estar en un registro o directamente en la instrucción. Un operando inmediato solo puede ser un operando fuente. En una instrucción de máquina de dos operandos, son posibles las siguientes combinaciones de operandos:

1) registro - registro;

2) registro - memoria;

3) memoria - registro;

4) operando inmediato - registro;

5) operando inmediato - memoria.

Hay excepciones a esta regla con respecto a:

1) cadena de comandos que pueden mover datos de una memoria a otra;

2) comandos de pila que pueden transferir datos desde la memoria a una pila que también está en la memoria;

3) comandos del tipo de multiplicación que, además del operando especificado en el comando, también utilizan un segundo operando implícito.

De las combinaciones enumeradas de operandos, el registro - memoria y la memoria - registro se usan con mayor frecuencia. En vista de su importancia, los consideraremos con más detalle. Acompañaremos la discusión con ejemplos de comandos de ensamblador que mostrarán cómo cambia el formato de un comando de ensamblador cuando se aplica uno u otro tipo de direccionamiento. En este sentido, mire nuevamente la Figura 21, que muestra el principio de formar una dirección física en el bus de direcciones del microprocesador. Se puede ver que la dirección del operando se forma como la suma de dos componentes: el contenido del registro del segmento desplazado en 4 bits y la dirección efectiva de 16 bits, que generalmente se calcula como la suma de tres componentes: base, compensación e índice.

3. Métodos de direccionamiento

Enumeramos y luego consideramos las características de los principales tipos de operandos de direccionamiento en la memoria:

1) direccionamiento directo;

2) direccionamiento básico indirecto (registro);

3) direccionamiento básico indirecto (registro) con desplazamiento;

4) direccionamiento de índice indirecto con desplazamiento;

5) direccionamiento de índice base indirecto;

6) direccionamiento de índice base indirecto con desplazamiento.

Direccionamiento directo

Esta es la forma más sencilla de direccionar un operando en la memoria, ya que la dirección efectiva está contenida en la instrucción misma y no se utilizan fuentes o registros adicionales para formarla. La dirección efectiva se toma directamente del campo de compensación de instrucción de la máquina (consulte la Figura 20), que puede tener un tamaño de 8, 16 o 32 bits. Este valor identifica de forma única el byte, la palabra o la palabra doble que se encuentra en el segmento de datos.

El direccionamiento directo puede ser de dos tipos.

Direccionamiento directo relativo

Se utiliza para instrucciones de salto condicional para indicar la dirección de salto relativa. La relatividad de tal transición radica en el hecho de que el campo de compensación de la instrucción de la máquina contiene un valor de 8, 16 o 32 bits que, como resultado de la operación de la instrucción, se agregará al contenido de el registro de puntero de instrucción ip/eip. Como resultado de esta adición, se obtiene la dirección a la que se realiza la transición.

Direccionamiento directo absoluto

En este caso, la dirección efectiva es parte de la instrucción de la máquina, pero esta dirección se forma solo a partir del valor del campo de compensación en la instrucción. Para formar la dirección física del operando en la memoria, el microprocesador agrega este campo con el valor del registro del segmento desplazado en 4 bits. Se pueden usar varias formas de este direccionamiento en una instrucción del ensamblador.

Pero tal direccionamiento rara vez se usa: a las celdas de uso común en el programa se les asignan nombres simbólicos. Durante la traducción, el ensamblador calcula y sustituye los valores de desplazamiento de estos nombres en la instrucción de máquina que genera en el campo "desplazamiento de instrucción". Como resultado, resulta que la instrucción máquina direcciona directamente su operando, teniendo, de hecho, en uno de sus campos el valor de la dirección efectiva.

Otros tipos de direccionamiento son indirectos. La palabra "indirecto" en el nombre de estos tipos de direccionamiento significa que solo una parte de la dirección efectiva puede estar en la instrucción misma, y ​​sus componentes restantes están en registros, que se indican por su contenido mediante el byte modr/m y , posiblemente, por el byte sib.

Direccionamiento indirecto básico (registro)

Con este direccionamiento, la dirección efectiva del operando puede estar en cualquiera de los registros de propósito general, excepto sp/esp y bp/ebp (son registros específicos para trabajar con un segmento de pila). Sintácticamente en un comando, este modo de direccionamiento se expresa encerrando el nombre del registro entre corchetes []. Por ejemplo, la instrucción mov ax, [ecx] coloca en los registros ax el contenido de la palabra en la dirección del segmento de datos con el desplazamiento almacenado en el registro esx. Dado que el contenido del registro se puede cambiar fácilmente durante el curso del programa, este método de direccionamiento le permite asignar dinámicamente la dirección de un operando para alguna instrucción de máquina. Esta propiedad es muy útil, por ejemplo, para organizar cálculos cíclicos y para trabajar con diversas estructuras de datos como tablas o matrices.

Direccionamiento base indirecto (registro) con desplazamiento

Este tipo de direccionamiento es una adición al anterior y está diseñado para acceder a datos con un desplazamiento conocido en relación con alguna dirección base. Este tipo de direccionamiento es conveniente para acceder a los elementos de las estructuras de datos, cuando el desplazamiento de los elementos se conoce de antemano, en la etapa de desarrollo del programa, y ​​la dirección base (de inicio) de la estructura debe calcularse dinámicamente, en la etapa de ejecución del programa. La modificación del contenido del registro base le permite acceder a los elementos del mismo nombre en diferentes instancias del mismo tipo de estructuras de datos.

Por ejemplo, la instrucción mov ax,[edx+3h] transfiere las palabras del área de memoria a los registros ax en la dirección: el contenido de edx + 3h.

La instrucción mov ax,mas[dx] mueve una palabra al registro ax en la dirección: el contenido de dx más el valor del identificador mas (recuerde que el compilador asigna a cada identificador un valor igual al desplazamiento de este identificador del principio del segmento de datos).

Direccionamiento de índice indirecto con desplazamiento

Este tipo de direccionamiento es muy similar al direccionamiento indirecto de base con un desplazamiento. Aquí también se utiliza uno de los registros de propósito general para formar la dirección efectiva. Pero el direccionamiento de índices tiene una característica interesante que es muy conveniente para trabajar con arreglos. Está conectado con la posibilidad del llamado escalado de los contenidos del registro de índice. ¿Lo que es?

Mire la Figura 20. Estamos interesados ​​en el byte sib. Al discutir la estructura de este byte, notamos que consta de tres campos. Uno de estos campos es el campo de escala ss, por el que se multiplican los contenidos del registro de índice.

Por ejemplo, en la instrucción mov ax,mas[si*2], el valor de la dirección efectiva del segundo operando se calcula mediante la expresión mas+(si)*2. Debido al hecho de que el ensamblador no tiene los medios para organizar la indexación de matrices, el programador tiene que organizarla por su cuenta.

La capacidad de escalar ayuda significativamente a resolver este problema, pero siempre que el tamaño de los elementos de la matriz sea de 1, 2, 4 u 8 bytes.

Direccionamiento de índice base indirecto

Con este tipo de direccionamiento, la dirección efectiva se forma como la suma del contenido de dos registros de propósito general: base e índice. Estos registros pueden ser cualquier registro de propósito general y, a menudo, se usa la escala del contenido de un registro de índice.

Direccionamiento de índice base indirecto con desplazamiento

Este tipo de direccionamiento es el complemento del direccionamiento indirecto indexado. La dirección efectiva se forma como la suma de tres componentes: el contenido del registro base, el contenido del registro de índice y el valor del campo de compensación en el comando.

Por ejemplo, la instrucción mov eax,[esi+5] [edx] mueve una palabra doble al registro eax en la dirección: (esi) + 5 + (edx).

El comando add ax,array[esi] [ebx] agrega el contenido del registro ax al contenido de la palabra en la dirección: el valor del identificador array + (esi) + (ebx).

CONFERENCIA N° 18. Equipos

1. Comandos de transferencia de datos

Para la conveniencia de la aplicación práctica y la reflexión de sus especificidades, es más conveniente considerar los comandos de este grupo de acuerdo con su propósito funcional, según el cual se pueden dividir en los siguientes grupos de comandos:

1) transferencias de datos de propósito general;

2) entrada-salida al puerto;

3) trabajar con direcciones y punteros;

4) transformaciones de datos;

5) trabajar con la pila.

Comandos generales de transferencia de datos

Este grupo incluye los siguientes comandos:

1) mov es el comando básico de transferencia de datos. Implementa una amplia variedad de opciones de envío. Tenga en cuenta los detalles de este comando:

a) el comando mov no se puede usar para transferir de un área de memoria a otra. Si surge tal necesidad, cualquier registro de propósito general actualmente disponible debe usarse como un búfer intermedio;

b) es imposible cargar un valor directamente desde la memoria en un registro de segmento. Por lo tanto, para realizar dicha carga, debe usar un objeto intermedio. Puede ser un registro de propósito general o una pila;

c) no puede transferir el contenido de un registro de segmento a otro registro de segmento. Esto se debe a que no hay un código de operación correspondiente en el sistema de comando. Pero a menudo surge la necesidad de tal acción. Puede realizar dicha transferencia utilizando los mismos registros de propósito general que los intermedios;

d) no puede utilizar el registro de segmento CS como operando de destino. La razón es simple. El hecho es que en la arquitectura del microprocesador, el par cs:ip siempre contiene la dirección del comando que debe ejecutarse a continuación. Cambiar el contenido del registro CS con el comando mov en realidad significaría una operación de salto, no una transferencia, lo cual es inaceptable. 2) xchg: se utiliza para la transferencia de datos bidireccional. Para esta operación, por supuesto, puede usar una secuencia de varias instrucciones mov, pero debido al hecho de que la operación de intercambio se usa con bastante frecuencia, los desarrolladores del sistema de instrucciones del microprocesador consideraron necesario introducir una instrucción de intercambio xchg por separado. Naturalmente, los operandos deben ser del mismo tipo. No está permitido (como para todas las instrucciones del ensamblador) intercambiar el contenido de dos celdas de memoria entre sí.

Comandos de E/S de puerto

Mire la Figura 22. Muestra un diagrama conceptual muy simplificado del control del hardware de la computadora.

Arroz. 22. Diagrama conceptual del control del hardware de la computadora

Como puede ver en la Figura 22, el nivel más bajo es el nivel del BIOS, donde el hardware se maneja directamente a través de los puertos. Esto implementa el concepto de independencia del equipo. Al reemplazar el hardware, solo será necesario corregir las funciones correspondientes del BIOS, reorientándolas a nuevas direcciones y la lógica de los puertos.

Fundamentalmente, administrar dispositivos directamente a través de los puertos es fácil. La información sobre los números de puerto, su profundidad de bits, el formato de información de control se proporciona en la descripción técnica del dispositivo. Solo necesita saber el objetivo final de sus acciones, el algoritmo según el cual funciona un dispositivo en particular y el orden de programación de sus puertos, es decir, de hecho, necesita saber qué y en qué secuencia debe enviar a el puerto (al escribir en él) o leerlo (al leer) y cómo debe interpretarse esta información. Para hacer esto, solo dos comandos que están presentes en el sistema de comando del microprocesador son suficientes:

1) en acumulador, port_number - entrada al acumulador desde el puerto con número port_number;

2) puerto de salida, acumulador: envía el contenido del acumulador al puerto con el número port_number.

Comandos para trabajar con direcciones y punteros de memoria

Al escribir programas en ensamblador, se realiza un trabajo intensivo con las direcciones de los operandos que se encuentran en la memoria. Para soportar este tipo de operaciones, existe un grupo especial de comandos, que incluye los siguientes comandos:

1) destino de lea, fuente - carga de dirección efectiva;

2) Ids destino, fuente: cargar el puntero en el registro de segmento de datos ds;

3) destino de archivos, fuente: cargar el puntero en el registro de los segmentos de datos adicionales es;

4) destino lgs, fuente: cargar el puntero en el registro del segmento de datos adicional gs;

5) lfs destino, fuente: cargar el puntero en el registro del segmento de datos adicional fs;

6) lss destino, fuente: carga el puntero en el registro de segmento de pila ss.

El comando lea es similar al comando mov en que también realiza un movimiento. Sin embargo, la instrucción lea no transfiere datos, sino la dirección efectiva de los datos (es decir, el desplazamiento de los datos desde el comienzo del segmento de datos) al registro indicado por el operando de destino.

A menudo, para realizar alguna acción en un programa, no es suficiente conocer el valor de la dirección de datos efectiva solo, sino que es necesario tener un puntero completo a los datos. Un puntero de datos completo consta de un componente de segmento y un desplazamiento. Todos los demás comandos de este grupo le permiten obtener un puntero completo a un operando en la memoria en un par de registros. En este caso, el nombre del registro de segmento, en el que se coloca el componente de segmento de la dirección, está determinado por el código de operación. En consecuencia, el desplazamiento se coloca en el registro general indicado por el operando de destino.

Pero no todo es tan sencillo con el operando fuente. De hecho, en el comando como fuente, no puede especificar directamente el nombre del operando en la memoria, al que nos gustaría recibir un puntero. Primero, debe obtener el valor del puntero completo en algún área de memoria y especificar la dirección completa del nombre de esta área en el comando obtener. Para realizar esta acción, debe recordar las directivas para reservar e inicializar memoria.

Al aplicar estas directivas, es posible un caso especial cuando el nombre de otra directiva de definición de datos (de hecho, el nombre de una variable) se especifica en el campo de operando. En este caso, la dirección de esta variable se forma en la memoria. La dirección que se generará (efectiva o completa) depende de la directiva aplicada. Si es dw, entonces solo se forma en la memoria el valor de 16 bits de la dirección efectiva; si es dd, la dirección completa se escribe en la memoria. La ubicación de esta dirección en la memoria es la siguiente: la palabra baja contiene el desplazamiento, la palabra alta contiene el componente de segmento de 16 bits de la dirección.

Por ejemplo, al organizar el trabajo con una cadena de caracteres, es conveniente colocar su dirección inicial en un registro determinado y luego modificar este valor en un bucle para acceder secuencialmente a los elementos de la cadena.

La necesidad de usar comandos para obtener un puntero de datos completo en la memoria, es decir, la dirección del segmento y el valor de desplazamiento dentro del segmento, surge, en particular, cuando se trabaja con cadenas.

Comandos de conversión de datos

Muchas instrucciones de microprocesador se pueden atribuir a este grupo, pero la mayoría de ellas tienen ciertas características que requieren que se atribuyan a otros grupos funcionales. Por lo tanto, de todo el conjunto de comandos del microprocesador, solo un comando se puede atribuir directamente a los comandos de conversión de datos: xlat [dirección_de_la_tabla_de_transcodificación]

Este es un equipo muy interesante y útil. Su efecto es que reemplaza el valor en el registro al con otro byte de la tabla de memoria ubicada en la dirección especificada por el operando recoding_table_address.

La palabra "tabla" es muy condicional, de hecho, es solo una cadena de bytes. La dirección del byte en la cadena que reemplazará el contenido del registro al está determinada por la suma (bx) + (al), es decir, el contenido de al actúa como un índice en la matriz de bytes.

Cuando trabaje con el comando xlat, preste atención al siguiente punto sutil. Aunque el comando especifica la dirección de la cadena de bytes de la que se recuperará el nuevo valor, esta dirección debe cargarse previamente (por ejemplo, mediante el comando lea) en el registro bx. Por lo tanto, el operando lookup_table_address no es realmente necesario (la opcionalidad del operando se muestra entre corchetes). En cuanto a la cadena de bytes (tabla de transcodificación), es un área de memoria de 1 a 255 bytes de tamaño (el rango de un número sin signo en un registro de 8 bits).

Comandos de pila

Este grupo es un conjunto de comandos especializados enfocados en organizar un trabajo flexible y eficiente con la pila.

La pila es un área de memoria especialmente asignada para el almacenamiento temporal de datos de programas. La importancia de la pila está determinada por el hecho de que se proporciona un segmento separado en la estructura del programa. En caso de que el programador se olvide de declarar un segmento de pila en su programa, el enlazador tlink emitirá un mensaje de advertencia.

Hay tres registros para trabajar con la pila:

1) ss - registro de segmento de pila;

2) sp/esp - registro de puntero de pila;

3) bp/ebp - registro de puntero base del marco de pila.

El tamaño de la pila depende del modo operativo del microprocesador y está limitado a 64 KB (o 4 GB en modo protegido).

Solo una pila está disponible a la vez, cuya dirección de segmento está contenida en el registro SS. Esta pila se llama la pila actual. Para hacer referencia a otra pila ("cambiar la pila"), es necesario cargar otra dirección en el registro ss. El procesador usa automáticamente el registro SS para ejecutar todas las instrucciones que funcionan en la pila.

Enumeramos algunas características más de trabajar con la pila:

1) la escritura y lectura de datos en la pila se realiza de acuerdo con el principio LIFO,

2) a medida que se escriben datos en la pila, esta última crece hacia direcciones más bajas. Esta función está integrada en el algoritmo de comandos para trabajar con la pila;

3) cuando se utilizan los registros esp/sp y ebp/bp para el direccionamiento de la memoria, el ensamblador considera automáticamente que los valores contenidos en él son compensaciones relativas al registro del segmento ss.

En general, la pila está organizada como se muestra en la Figura 23.

Arroz. 23. Diagrama conceptual de la organización de la pila

Los registros SS, ESP/SP y EUR/BP están diseñados para funcionar con la pila. Estos registros se utilizan de forma compleja y cada uno de ellos tiene su propio propósito funcional.

El registro ESP/SP siempre apunta a la parte superior de la pila, es decir, contiene el desplazamiento en el que se colocó el último elemento en la pila. Las instrucciones de la pila cambian implícitamente este registro para que siempre apunte al último elemento colocado en la pila. Si la pila está vacía, el valor de esp es igual a la dirección del último byte del segmento asignado a la pila. Cuando se coloca un elemento en la pila, el procesador reduce el valor del registro esp y luego escribe el elemento en la dirección del nuevo vértice. Al extraer datos de la pila, el procesador copia el elemento ubicado en la dirección superior y luego incrementa el valor del registro del puntero de la pila, especialmente. Por tanto, resulta que la pila crece hacia abajo, en la dirección de direcciones decrecientes.

¿Qué pasa si necesitamos acceder a los elementos no en la parte superior, sino dentro de la pila? Para hacer esto, use el registro EBP.El registro EBP es el registro de puntero base del marco de pila.

Por ejemplo, un truco típico al ingresar una subrutina es pasar los parámetros deseados empujándolos a la pila. Si la subrutina también está trabajando activamente con la pila, el acceso a estos parámetros se vuelve problemático. La salida es guardar la dirección de la parte superior de la pila en el puntero del marco (base) de la pila después de escribir los datos necesarios en la pila: el registro EUR. El valor en EUR se puede usar más adelante para acceder a los parámetros pasados.

El comienzo de la pila se encuentra en las direcciones de memoria más altas. En la Figura 23, esta dirección se indica con el par ss:fffF. El cambio de wT se da aquí condicionalmente. En realidad, este valor está determinado por el valor que especifica el programador al describir el segmento de la pila en su programa.

Para organizar el trabajo con la pila, existen comandos especiales para escribir y leer.

1. empujar fuente: escribir el valor de la fuente en la parte superior de la pila.

De interés es el algoritmo de este comando, que incluye las siguientes acciones (Fig. 24):

1) (sp) = (sp) - 2; el valor de sp se reduce en 2;

2) el valor de la fuente se escribe en la dirección especificada por el par ss: sp.

Arroz. 24. Cómo funciona el comando push

2. asignación pop: escribir el valor desde la parte superior de la pila hasta la ubicación especificada por el operando de destino. Por lo tanto, el valor se "elimina" de la parte superior de la pila. El algoritmo del comando pop es el inverso del algoritmo del comando push (Fig. 25):

1) escribir el contenido de la parte superior de la pila en la ubicación indicada por el operando de destino;

2) (sp) = (sp) + 2; aumentando el valor de sp.

Arroz. 25. Cómo funciona el comando emergente

3. pusha: un comando de escritura grupal en la pila. Mediante este comando, los registros ax, cx, dx, bx, sp, bp, si, di se escriben secuencialmente en la pila. Tenga en cuenta que el contenido original de sp está escrito, es decir, el contenido que estaba antes de que se emitiera el comando pusha (Fig. 26).

Arroz. 26. Cómo funciona el comando pusha

4. pushaw es casi sinónimo del comando pusha ¿Cuál es la diferencia? El atributo bitness puede ser use16 o use32. Veamos cómo funcionan los comandos pusha y pushaw con cada uno de estos atributos:

1) use16 - el algoritmo pushaw es similar al algoritmo pusha;

2) use32: pushaw no cambia (es decir, es insensible al ancho del segmento y siempre funciona con registros del tamaño de una palabra: ax, cx, dx, bx, sp, bp, si, di). El comando pusha es sensible al ancho del segmento establecido y cuando se especifica un segmento de 32 bits, funciona con los registros de 32 bits correspondientes, es decir, eax, esx, edx, ebx, esp, ebp, esi, edi.

5. pushad: se realiza de manera similar al comando pusha, pero hay algunas peculiaridades.

Los siguientes tres comandos realizan el reverso de los comandos anteriores:

1) rorá;

2) papaya;

3) pop.

El grupo de instrucciones que se describe a continuación le permite guardar el registro de bandera en la pila y escribir una palabra o palabra doble en la pila. Tenga en cuenta que las instrucciones enumeradas a continuación son las únicas en el conjunto de instrucciones del microprocesador que permiten (y requieren) acceso a todo el contenido del registro de bandera.

1. pushf - guarda el registro de banderas en la pila.

El funcionamiento de este comando depende del atributo de tamaño del segmento:

1) use 16: el registro de banderas de 2 bytes de tamaño se escribe en la pila;

2) use32 - el registro eflags de 4 bytes se escribe en la pila.

2. pushfw: guarda un registro de banderas del tamaño de una palabra en la pila. Siempre funciona como pushf con el atributo use16.

3. pushfd - guardar el registro flags o eflags flags en la pila dependiendo del atributo de ancho de bits del segmento (es decir, lo mismo que pushf).

De manera similar, los siguientes tres comandos realizan el reverso de las operaciones discutidas anteriormente:

1) popf;

2) pop ftv;

3) popfd.

Y en conclusión, notamos los principales tipos de operaciones cuando el uso de la pila es casi inevitable:

1) subrutinas de llamada;

2) almacenamiento temporal de valores de registro;

3) definición de variables locales.

2. Comandos aritméticos

El microprocesador puede realizar operaciones con números enteros y coma flotante. Para ello, su arquitectura cuenta con dos bloques separados:

1) un dispositivo para realizar operaciones con números enteros;

2) un dispositivo para realizar operaciones de punto flotante.

Cada uno de estos dispositivos tiene su propio sistema de comando. En principio, un dispositivo de enteros puede hacerse cargo de muchas de las funciones de un dispositivo de punto flotante, pero esto será computacionalmente costoso. Para la mayoría de los problemas que utilizan lenguaje ensamblador, la aritmética de enteros es suficiente.

Descripción general de un grupo de instrucciones y datos aritméticos

Un dispositivo de cálculo de números enteros admite un poco más de una docena de instrucciones aritméticas. La Figura 27 muestra la clasificación de los comandos en este grupo.

Arroz. 27. Clasificación de los comandos aritméticos

El grupo de instrucciones aritméticas enteras trabaja con dos tipos de números:

1) números binarios enteros. Los números pueden o no tener un dígito con signo, es decir, ser números con o sin signo;

2) números enteros decimales.

Considere los formatos de máquina en los que se almacenan estos tipos de datos.

números binarios enteros

Un entero binario de punto fijo es un número codificado en el sistema numérico binario.

La dimensión de un entero binario puede ser de 8, 16 o 32 bits. El signo de un número binario está determinado por cómo se interpreta el bit más significativo en la representación del número. Esto es 7,15 o 31 bits para números de la dimensión correspondiente. Al mismo tiempo, es interesante que entre los comandos aritméticos solo hay dos comandos que realmente toman en cuenta este bit más significativo como signo uno, estos son los comandos de multiplicación y división de enteros imul e idiv. En otros casos, la responsabilidad de las acciones con números con signo y, en consecuencia, con un bit de signo recae en el programador. El rango de valores de un número binario depende de su tamaño y de la interpretación del bit más significativo ya sea como el bit más significativo del número o como el bit de signo del número (Tabla 9).

Tabla 9. Rango de números binarios Números decimales

Los números decimales son un tipo especial de representación de información numérica, que se basa en el principio de codificar cada dígito decimal de un número por un grupo de cuatro bits. En este caso, cada byte del número contiene uno o dos dígitos decimales en el llamado código decimal codificado en binario (BCD - Binary-Coded Decimal). El microprocesador almacena números BCD en dos formatos (Fig. 28):

1) formato empaquetado. En este formato, cada byte contiene dos dígitos decimales. Un dígito decimal es un valor binario de 0 bits entre 9 y 4. En este caso, el código del dígito más alto del número ocupa los 4 bits más altos. Por lo tanto, el rango de representación de un número empaquetado decimal en 1 byte es de 00 a 99;

2) formato sin envasar. En este formato, cada byte contiene un dígito decimal en los cuatro bits menos significativos. Los 4 bits superiores se ponen a cero. Esta es la llamada zona. Por lo tanto, el rango de representación de un número decimal desempaquetado en 1 byte es de 0 a 9.

Arroz. 28. Representación de números BCD

¿Cómo describir números decimales binarios en un programa? Para hacer esto, puede usar solo dos directivas de inicialización y descripción de datos: db y dt. La posibilidad de usar solo estas directivas para describir números BCD se debe a que el principio de "byte bajo en dirección baja" también es aplicable a dichos números, lo cual es muy conveniente para su procesamiento. Y, en general, cuando se utiliza un tipo de datos como números BCD, el orden en que se describen estos números en el programa y el algoritmo para procesarlos es una cuestión de gusto y preferencias personales del programador. Esto quedará claro después de que veamos los conceptos básicos para trabajar con números BCD a continuación.

Operaciones aritméticas con enteros binarios

Adición de números binarios sin signo

El microprocesador realiza la suma de operandos de acuerdo con las reglas para sumar números binarios. No hay problemas siempre que el valor del resultado no supere las dimensiones del campo del operando. Por ejemplo, al agregar operandos de tamaño de byte, el resultado no debe exceder el número 255. Si esto sucede, entonces el resultado es incorrecto. Consideremos por qué sucede esto.

Por ejemplo, hagamos la suma: 254 + 5 = 259 en binario. 11111110 + 0000101 = 1 00000011. El resultado superó los 8 bits y su valor correcto cabe en 9 bits, y el valor 8 permaneció en el campo de 3 bits del operando, lo que, por supuesto, no es cierto. En el microprocesador, se predice este resultado de la adición y se proporcionan medios especiales para solucionar tales situaciones y procesarlas. Entonces, para corregir la situación de ir más allá de la cuadrícula de bits del resultado, como en este caso, se pretende el indicador de acarreo cf. Se encuentra en el bit 0 del registro de banderas EFLAGS/FLAGS. Es la configuración de esta bandera la que fija el hecho de la transferencia de uno desde el orden superior del operando. Naturalmente, el programador debe tener en cuenta la posibilidad de tal resultado de la operación de suma y proporcionar medios para la corrección. Esto implica incluir secciones de código después de la operación de adición en la que se analiza el indicador cf. Esta bandera se puede analizar de varias maneras.

El más fácil y accesible es usar el comando de rama condicional jcc. Esta instrucción tiene como operando el nombre de la etiqueta en el segmento de código actual. La transición a esta etiqueta se lleva a cabo si, como resultado de la operación del comando anterior, el indicador cf se establece en 1. Hay tres comandos de suma binaria en el sistema de comando del microprocesador:

1) operando inc - operación de incremento, es decir, aumenta el valor del operando en 1;

2) agregar operando_1, operando_2 - instrucción de suma con el principio de operación: operando_1 = operando_1 + operando_2;

3) adc operando_1, operando_2 - instrucción de adición teniendo en cuenta la bandera de acarreo cf. Principio de funcionamiento del comando: operando_1 = operando_1 + operando_2 + valor_sG.

Preste atención al último comando: este es el comando de suma, que tiene en cuenta la transferencia de uno desde el orden superior. Ya hemos considerado el mecanismo para la aparición de tal unidad. Por lo tanto, la instrucción adc es una herramienta de microprocesador para sumar números binarios largos, cuyas dimensiones superan las longitudes de los campos estándar admitidos por el microprocesador.

Adición binaria firmada

De hecho, el microprocesador "no es consciente" de la diferencia entre números con y sin signo. En cambio, tiene los medios para fijar la ocurrencia de situaciones características que se desarrollan en el proceso de los cálculos. Cubrimos algunos de ellos cuando discutimos la adición sin firmar:

1) la bandera de acarreo cf, establecerla en 1 indica que los operandos estaban fuera de rango;

2) el comando adc, que tiene en cuenta la posibilidad de tal salida (carry desde el bit menos significativo).

Otro medio es registrar el estado del bit de orden superior (signo) del operando, lo que se hace usando el indicador de desbordamiento en el registro EFLAGS (bit 11).

Por supuesto, recuerda cómo se representan los números en una computadora: positivo, en binario, negativo, en complemento a dos. Considere varias opciones para sumar números. Los ejemplos pretenden mostrar el comportamiento de los dos bits más significativos de los operandos y la corrección del resultado de la operación de suma.

ejemplo

30566 = 0111011101100110

+

00687 = 00000010 10101111 XNUMX

=

31253 = 01111010 00010101 XNUMX

Supervisamos las transferencias desde los dígitos 14 y 15 y la corrección del resultado: no hay transferencias, el resultado es correcto.

ejemplo

30566 = 0111011101100110

+

30566 = 0111011101100110

=

1132 = 11101110 11001100 XNUMX

Hubo un traspaso de la 14ª categoría; no hay traspaso a partir de la 15ª categoría. El resultado es incorrecto, porque hay un desbordamiento: el valor del número resultó ser mayor que lo que puede tener un número con signo de 16 bits (+32 767).

ejemplo

-30566 = 10001000 10011010

+

-04875 = 11101100 11110101

=

-35441 = 01110101 10001111

Hubo una transferencia desde el dígito 15, no hay transferencia desde el dígito 14. El resultado es incorrecto, porque en lugar de un número negativo, resultó ser positivo (el bit más significativo es 0).

ejemplo

-4875 = 11101100 11110101

+

-4875 = 11101100 11110101

=

09750 = 11011001 11101010 XNUMX

Hay transferencias desde los bits 14 y 15. El resultado es correcto.

Por lo tanto, examinamos todos los casos y descubrimos que la situación de desbordamiento (establecer el indicador OF en 1) ocurre durante la transferencia:

1) desde el dígito 14 (para números positivos con signo);

2) a partir del dígito 15 (para números negativos).

Por el contrario, no se produce desbordamiento (es decir, la bandera OF se restablece a 0) si hay un acarreo de ambos bits o si no hay acarreo en ambos bits.

Entonces el desbordamiento se registra con el indicador de desbordamiento de. Además del indicador de, cuando se transfiere desde el bit de orden superior, el indicador de transferencia CF se establece en 1. Dado que el microprocesador no conoce la existencia de números con y sin signo, el programador es el único responsable de las acciones correctas con los números resultantes. Puede analizar los indicadores CF y OF con las instrucciones de salto condicional JC\JNC y JO\JNO, respectivamente.

En cuanto a los comandos para sumar números con signo, son los mismos que para números sin signo.

Resta de números binarios sin signo

Al igual que en el análisis de la operación de suma, discutiremos la esencia de los procesos que ocurren al realizar la operación de resta. Si el minuendo es mayor que el sustraendo, entonces no hay problema: la diferencia es positiva, el resultado es correcto. Si el minuendo es menor que el restado, hay un problema: el resultado es menor que 0, y este ya es un número con signo. En este caso, el resultado debe estar envuelto. ¿Qué significa esto? Con la resta habitual (en una columna), hacen un préstamo de 1 del orden más alto. El microprocesador hace lo mismo, es decir, toma 1 del dígito que sigue al más alto en la cuadrícula de bits del operando. Vamos a explicar con un ejemplo.

ejemplo

05 = 00000000 00000101 XNUMX

-10 = 00000000 00001010

Para hacer la resta, hagamos

préstamo imaginario senior:

100000000 00000101

-

00000000 00001010

=

11111111 11111011

Así, en esencia, la acción

(65 + 536) - 5 = 10

0 aquí es, por así decirlo, equivalente al número 65536. El resultado, por supuesto, es incorrecto, pero el microprocesador considera que todo está bien, aunque corrige el hecho de tomar prestada una unidad configurando la bandera de acarreo cf. Pero mire de nuevo cuidadosamente el resultado de la operación de resta. ¡Es -5 en complemento a dos! Realicemos un experimento: representa la diferencia como una suma de 5 + (-10).

ejemplo

5 = 00000000 00000101 XNUMX

+

(-10)= 11111111 11110110

=

11111111 11111011

es decir, obtuvimos el mismo resultado que en el ejemplo anterior.

Por lo tanto, después del comando para restar números sin signo, es necesario analizar el estado de la bandera CE.Si se establece en 1, esto indica que hubo un préstamo del orden superior y el resultado se obtuvo en un código adicional .

Al igual que las instrucciones de suma, el grupo de instrucciones de resta consiste en el conjunto más pequeño posible. Estos comandos realizan la resta de acuerdo con los algoritmos que ahora estamos considerando, y las excepciones deben ser tenidas en cuenta por el propio programador. Los comandos de resta incluyen:

1) operando dec - operación de decremento, es decir, disminuye el valor del operando en 1;

2) sub operando_1, operando_2 - comando de resta; su principio de funcionamiento: operando_1 = operando_1 - operando_2;

3) sbb operando_1, operando_2 - comando de resta teniendo en cuenta el préstamo (bandera ci): operando_1 = operando_1 - operando_2 - valor_sG.

Como puede ver, entre los comandos de resta hay un comando sbb que tiene en cuenta la bandera de acarreo cf. Este comando es similar a adc, pero ahora el indicador cf actúa como un indicador de tomar prestado 1 del dígito más significativo al restar números.

resta binaria con signo

Aquí todo es algo más complicado. El microprocesador no necesita tener dos dispositivos: suma y resta. Es suficiente tener solo uno: el dispositivo de adición. Pero para la resta mediante la suma de números con un signo en un código adicional, es necesario representar ambos operandos, tanto el reducido como el restado. El resultado también debe tratarse como un valor de complemento a dos. Pero aquí surgen dificultades. En primer lugar, están relacionados con el hecho de que el bit más significativo del operando se considera un bit de signo. Considere el ejemplo de restar 45 - (-127).

ejemplo

Resta de números con signo 1

45 = 0010 1101 XNUMX

-

-127 = 1000 0001

=

-44 = 1010 1100

A juzgar por el bit de signo, el resultado resultó ser negativo, lo que, a su vez, indica que el número debe considerarse como un complemento igual a -44. El resultado correcto debería ser 172. Aquí, como en el caso de la suma con signo, nos encontramos con un desbordamiento de mantisa, cuando el bit significativo del número cambiaba el bit de signo del operando. Puede realizar un seguimiento de esta situación por el contenido de la bandera de desbordamiento de. Establecerlo en 1 indica que el resultado está fuera del rango de números con signo (es decir, el bit más significativo ha cambiado) para un operando de este tamaño, y el programador debe tomar medidas para corregir el resultado.

ejemplo

Resta de números con signo 2

-45-45 = -45 + (-45) = -90.

-45=11010011

+

-45=11010011

=

-90 = 1010 0110

Todo está bien aquí, el indicador de desbordamiento de se restablece a 0, y 1 en el bit de signo indica que el valor del resultado es un número de complemento a dos.

Resta y suma de operandos grandes

Si te fijas, las instrucciones de suma y resta funcionan con operandos de dimensión fija: 8, 16, 32 bits. Pero, ¿qué sucede si necesita agregar números de una dimensión mayor, por ejemplo, 48 bits, utilizando operandos de 16 bits? Por ejemplo, agreguemos dos números de 48 bits:

Arroz. 29. Agregar operandos grandes

La Figura 29 muestra la tecnología para sumar números largos paso a paso. Se puede ver que el proceso de agregar números de varios bytes ocurre de la misma manera que cuando se agregan dos números "en una columna", con la implementación, si es necesario, de transferir 1 al bit más alto. Si logramos programar este proceso, ampliaremos significativamente el rango de números binarios en los que podemos realizar operaciones de suma y resta.

El principio de restar números con un rango de representación que excede las cuadrículas de bits de operandos estándar es el mismo que para la suma, es decir, se usa la bandera de acarreo cf. Solo necesita imaginar el proceso de restar en una columna y combinar correctamente las instrucciones del microprocesador con la instrucción sbb.

Para concluir nuestra discusión sobre las instrucciones de suma y resta, además de cf y de flags, hay algunas otras flags en el registro eflags que se pueden usar con instrucciones aritméticas binarias. Estas son las siguientes banderas:

1) zf - indicador cero, que se establece en 1 si el resultado de la operación es 0, y en 1 si el resultado no es igual a 0;

2) sf - indicador de signo, cuyo valor después de las operaciones aritméticas (y no solo) coincide con el valor del bit más significativo del resultado, es decir, con el bit 7, 15 o 31. Por lo tanto, este indicador se puede usar para operaciones en números firmados.

Multiplicación de números sin signo

El comando para multiplicar números sin signo es

mulfactor_1

Como puede ver, el comando contiene solo un operando multiplicador. El segundo operando factor_2 se especifica implícitamente. Su ubicación es fija y depende del tamaño de los factores. Dado que, en general, el resultado de una multiplicación es mayor que cualquiera de sus factores, su tamaño y ubicación también deben determinarse de manera única. Las opciones para los tamaños de los factores y la ubicación del segundo operando y el resultado se muestran en la Tabla 10.

Tabla 10. Ordenación de operandos y resultado en la multiplicación

En la tabla se puede ver que el producto consta de dos partes y, según el tamaño de los operandos, se coloca en dos lugares: en lugar de factor_2 (parte inferior) y en el registro adicional ah, dx, edx (parte superior parte). Entonces, ¿cómo saber dinámicamente (es decir, durante la ejecución del programa) que el resultado es lo suficientemente pequeño para caber en un registro, o que excedió la dimensión del registro y la parte más alta terminó en otro registro? Para hacer esto, usamos los indicadores cf y overflow que ya conocemos de la discusión anterior:

1) si la parte inicial del resultado es cero, luego de la operación del producto, las banderas cf = 0 y of = 0;

2) si estas banderas son distintas de cero, significa que el resultado ha ido más allá de la parte más pequeña del producto y consta de dos partes, que deben tenerse en cuenta en el trabajo posterior.

Multiplica números con signo

El comando para multiplicar números con signo es

[imul operando_1, operando_2, operando_3]

Este comando se ejecuta de la misma forma que el comando mul. Una característica distintiva del comando imul es solo la formación del signo.

Si el resultado es pequeño y cabe en un registro (es decir, si cf = of = 0), entonces el contenido del otro registro (la parte alta) es extensión de signo: todos sus bits son iguales al bit alto (bit de signo ) de la parte baja del resultado. De lo contrario (si cf = of = 1), el signo del resultado es el bit de signo de la parte alta del resultado, y el bit de signo de la parte baja es el bit significativo del código de resultado binario.

División de números sin signo

El comando para dividir números sin signo es

divisor div

El divisor puede estar en la memoria o en un registro y tener un tamaño de 8, 16 o 32 bits. La ubicación del dividendo es fija y, como en la instrucción de multiplicación, depende del tamaño de los operandos. El resultado del comando de división son los valores del cociente y el resto.

Las opciones para la ubicación y el tamaño de los operandos de la operación de división se muestran en la Tabla 11.

Tabla 11. Ordenación de operandos y resultado en división

Después de que se ejecuta la instrucción dividir, el contenido de las banderas no está definido, pero puede ocurrir la interrupción número 0, llamada "dividir por cero". Este tipo de interrupción pertenece a las llamadas excepciones. Este tipo de interrupción ocurre dentro del microprocesador debido a algunas anomalías durante el proceso de cómputo. Interrumpir O, "dividir por cero", mientras se ejecuta el comando div puede ocurrir por una de las siguientes razones:

1) el divisor es cero;

2) el cociente no está incluido en la cuadrícula de bits asignada para él, lo que puede ocurrir en los siguientes casos:

a) al dividir un dividendo con valor de una palabra por un divisor con valor de bytes, y el valor del dividendo es más de 256 veces mayor que el valor del divisor;

b) cuando se divida un dividendo con valor de una palabra doble por un divisor con valor de una palabra, y el valor del dividendo sea más de 65 veces mayor que el valor del divisor;

c) al dividir el dividendo de cuádruple valor nominal por un divisor de doble valor nominal, y el valor del dividendo sea más de 4 veces mayor que el valor del divisor.

División con un signo

El comando para dividir números con signo es

divisor idiv

Para este mando son válidas todas las disposiciones consideradas en cuanto a mandos y números con signo. Solo notamos las características de la ocurrencia de la excepción 0, "división por cero", en el caso de números con signo. Ocurre al ejecutar el comando idiv por una de las siguientes razones:

1) el divisor es cero;

2) el cociente no está incluido en la cuadrícula de bits asignada para él.

Esto último a su vez puede suceder:

1) al dividir un dividendo con un valor de palabra con signo por un divisor con un valor de byte con signo, y el valor del dividendo es más de 128 veces el valor del divisor (por lo tanto, el cociente no debe estar fuera del rango de -128 a + 127);

2) al dividir el dividendo por un valor de palabra doble con signo por el divisor por un valor de palabra con signo, y el valor del dividendo es más de 32 veces el valor del divisor (por lo tanto, el cociente no debe estar fuera del rango de - 768 a +32);

3) al dividir el dividendo por un valor de cuatro palabras con signo por un divisor de dos palabras con signo, y el valor del dividendo es más de 2 veces el valor del divisor (por lo tanto, el cociente no debe estar fuera del rango de -147 a + 483 648 2 147).

Instrucciones auxiliares para operaciones con enteros

Hay varias instrucciones en el conjunto de instrucciones del microprocesador que pueden facilitar la programación de algoritmos que realizan cálculos aritméticos. En ellos pueden surgir varios problemas, para cuya resolución los desarrolladores de microprocesadores han proporcionado varios comandos.

Comandos de conversión de tipos

¿Qué pasa si los tamaños de los operandos involucrados en las operaciones aritméticas son diferentes? Por ejemplo, supongamos que en una operación de suma, un operando es una palabra y el otro es una palabra doble. Se dijo anteriormente que los operandos del mismo formato deben participar en la operación de suma. Si los números no están firmados, la salida es fácil de encontrar. En este caso, sobre la base del operando original, se puede formar uno nuevo (formato de palabra doble), cuyos bits altos simplemente se pueden llenar con ceros. La situación es más complicada para los números con signo: ¿cómo tener en cuenta el signo del operando de forma dinámica, durante la ejecución del programa? Para resolver tales problemas, el conjunto de instrucciones del microprocesador tiene las llamadas instrucciones de conversión de tipo. Estas instrucciones expanden bytes en palabras, palabras en palabras dobles y palabras dobles en palabras cuádruples (valores de 64 bits). Las instrucciones de conversión de tipo son especialmente útiles cuando se convierten enteros con signo, ya que rellenan automáticamente los bits de orden superior del operando recién construido con los valores del bit de signo del objeto anterior. Esta operación da como resultado valores enteros del mismo signo y la misma magnitud que el original, pero en un formato más largo. Tal transformación se llama operación de propagación de signos.

Hay dos tipos de comandos de conversión de tipo.

1. Instrucciones sin operandos. Estos comandos funcionan con registros fijos:

1) cbw (Convertir byte en palabra): un comando para convertir un byte (en el registro al) en una palabra (en el registro ah) al distribuir el valor del bit alto al a todos los bits del registro ah;

2) cwd (Convertir palabra en doble): un comando para convertir una palabra (en el registro ax) en una palabra doble (en los registros dx: ax) al distribuir el valor del bit alto ax a todos los bits del registro dx;

3) cwde (Convertir palabra en doble): un comando para convertir una palabra (en el registro ax) en una palabra doble (en el registro eax) al distribuir el valor del bit alto ax a todos los bits de la mitad superior del registro eax ;

4) cdq (Convertir palabra doble en cuarto de palabra): un comando para convertir una palabra doble (en el registro eax) en una palabra cuádruple (en los registros edx: eax) al distribuir el valor del bit más significativo de eax a todos bits del registro edx.

2. Comandos movsx y movzx relacionados con los comandos de procesamiento de cadenas. Estos comandos tienen una propiedad útil en el contexto de nuestro problema:

1) movsx operand_1, operand_2 - enviar con propagación de señal. Extiende un valor de 8 o 16 bits de operando_2, que puede ser un registro o un operando de memoria, a un valor de 16 o 32 bits en uno de los registros, usando el valor del bit de signo para llenar las posiciones más altas de operando_1. Esta instrucción es útil para preparar operandos con signo para operaciones aritméticas;

2) movzx operand_1, operand_2 - enviar con extensión cero. Extiende el valor de 8 o 16 bits de operando_2 a 16 o 32 bits, borrando (rellenando) las posiciones altas de operando_2 con ceros. Esta instrucción es útil para preparar operandos sin signo para la aritmética.

Otros comandos útiles

1. xadd destino, fuente - intercambio y adición.

El comando le permite realizar dos acciones en secuencia:

1) intercambiar valores de origen y destino;

2) coloque el operando de destino en lugar de la suma: destino = destino + fuente.

2. operando neg - negación con complemento a dos.

La instrucción invierte el valor del operando. Físicamente, el comando realiza una acción:

operando = 0 - operando, es decir, resta el operando de cero.

El comando neg operand se puede utilizar:

1) para cambiar el signo;

2) para realizar la resta de una constante.

Operaciones aritméticas con números binario-decimales

En esta sección, veremos los detalles de cada una de las cuatro operaciones aritméticas básicas para números BCD empaquetados y no empaquetados.

Puede surgir la pregunta con razón: ¿por qué necesitamos números BCD? La respuesta podría ser: los números BCD son necesarios en aplicaciones comerciales, es decir, donde los números deben ser grandes y precisos. Como ya hemos visto en el ejemplo de los números binarios, las operaciones con dichos números son bastante problemáticas para el lenguaje ensamblador. Las desventajas de usar números binarios incluyen lo siguiente:

1) Los valores en formato word y doble word tienen un rango limitado. Si el programa está diseñado para funcionar en el campo de las finanzas, limitar la cantidad en rublos a 65 536 (para una palabra) o incluso 4 294 967 296 (para una palabra doble) reducirá significativamente el alcance de su aplicación;

2) la presencia de errores de redondeo. ¿Te imaginas un programa ejecutándose en algún lugar de un banco que no tenga en cuenta el valor del saldo cuando opera con enteros binarios y opera con miles de millones? No me gustaría ser el autor de tal programa. El uso de números de punto flotante no salvará - ahí existe el mismo problema de redondeo;

3) presentación de una gran cantidad de resultados en forma simbólica (código ASCII). Los programas de negocios no solo hacen cálculos; una de las finalidades de su uso es la pronta entrega de información al usuario. Para hacer esto, por supuesto, la información debe presentarse en forma simbólica. Convertir números de binario a ASCII requiere un poco de esfuerzo computacional. Un número de coma flotante es aún más difícil de traducir a una forma simbólica. Pero si observa la representación hexadecimal de un dígito decimal desempaquetado y su carácter correspondiente en la tabla ASCII, puede ver que difieren en 30h. Así, la conversión a forma simbólica y viceversa es mucho más fácil y rápida.

Probablemente ya hayas visto la importancia de dominar al menos los conceptos básicos de las acciones con números decimales. A continuación, considere las características de realizar operaciones aritméticas básicas con números decimales. Inmediatamente notamos el hecho de que no hay comandos separados para suma, resta, multiplicación y división de números BCD. Esto se hizo por razones bastante comprensibles: la dimensión de tales números puede ser arbitrariamente grande. Los números BCD se pueden sumar y restar, tanto empaquetados como desempaquetados, pero solo los números BCD desempaquetados pueden dividirse y multiplicarse. Por qué esto es así se verá en una discusión posterior.

Aritmética en números BCD sin empaquetar

Agregar números BCD sin empaquetar

Consideremos dos casos de suma.

ejemplo

El resultado de la suma no es más de 9

6 = 0000 0110 XNUMX

+

3 = 0000 0011 XNUMX

=

9 = 0000 1001 XNUMX

No hay transferencia de la tétrada junior a la senior. El resultado es correcto.

ejemplo

El resultado de la suma es mayor que 9:

06 = 0000 0110 XNUMX

+

07 = 0000 0111 XNUMX

=

13 = 0000 1101 XNUMX

Ya no hemos recibido un número BCD. El resultado es incorrecto. El resultado correcto en formato BCD desempaquetado debería ser 0000 0001 0000 0011 en binario (o 13 en decimal).

Después de analizar este problema al sumar números BCD (y problemas similares al realizar otras operaciones aritméticas) y las posibles formas de resolverlo, los desarrolladores del sistema de comando del microprocesador decidieron no introducir comandos especiales para trabajar con números BCD, sino varios comandos correctivos. .

El propósito de estas instrucciones es corregir el resultado de la operación de instrucciones aritméticas ordinarias para los casos en que los operandos en ellas sean números BCD.

En el caso de la resta del ejemplo 10, se puede ver que el resultado obtenido necesita ser corregido. Para corregir la operación de sumar dos números BCD desempaquetados de un solo dígito en el sistema de comando del microprocesador, existe un comando especial, aaa (ASCII Adjust for Addition), corrección del resultado de la suma para su representación en forma simbólica.

Esta instrucción no tiene operandos. Funciona implícitamente solo con el registro al y analiza el valor de su tétrada inferior:

1) si este valor es inferior a 9, entonces la bandera cf se restablece a XNUMX y se realiza la transición a la siguiente instrucción;

2) si este valor es mayor que 9, entonces se realizan las siguientes acciones:

a) Se suma 6 al contenido de la tétrada inferior (¡pero no al contenido de todo el registro!). Así, el valor del resultado decimal se corrige en la dirección correcta;

b) la bandera cf se pone a 1, fijando así la transferencia al bit más significativo para que pueda ser tenida en cuenta en acciones posteriores.

Entonces, en el ejemplo 10, suponiendo que el valor de la suma 0000 1101 está en al, después de la instrucción aaa, el registro tendrá 1101 + 0110 = 0011, es decir, 0000 0011 binario o 3 decimal, y el indicador cf se establecerá en 1, es decir, la transferencia ha sido almacenada en el microprocesador. A continuación, el programador deberá utilizar la instrucción de suma adc, que tendrá en cuenta el acarreo del bit anterior.

Resta de números BCD sin empaquetar

La situación aquí es bastante similar a la suma. Consideremos los mismos casos.

ejemplo

El resultado de la resta no es mayor que 9:

6 = 0000 0110 XNUMX

-

3 = 0000 0011 XNUMX

=

3 = 0000 0011 XNUMX

Como puede ver, no hay préstamo del cuaderno senior. El resultado es correcto y no requiere corrección.

ejemplo

El resultado de la resta es mayor que 9:

6 = 0000 0110 XNUMX

-

7 = 0000 0111 XNUMX

=

-1 = 1111 1111

La resta se realiza de acuerdo con las reglas de la aritmética binaria. Por lo tanto, el resultado no es un número BCD.

El resultado correcto en formato BCD desempaquetado debería ser 9 (0000 1001 en binario). En este caso, se asume un préstamo del dígito más significativo, como con un comando de resta normal, es decir, en el caso de números BCD, en realidad se debe realizar la resta de 16 - 7. Por lo tanto, está claro que, como en el caso de suma, el resultado de la resta debe ser corregido. Para esto, hay un comando especial - aas (ASCII Adjust for Substraction) - corrección del resultado de la resta para representación en forma simbólica.

La instrucción aas tampoco tiene operandos y opera en el registro al, analizando su tétrada de orden mínimo de la siguiente manera:

1) si su valor es inferior a 9, la bandera cf se restablece a 0 y el control se transfiere al siguiente comando;

2) si el valor de tétrada en al es mayor que 9, entonces el comando aas realiza las siguientes acciones:

a) resta 6 del contenido de la tétrada inferior del registro al (nota: no del contenido del registro completo);

b) pone a cero la tétrada superior del registro al;

c) establece el indicador cf en 1, fijando así el préstamo imaginario de orden superior.

Está claro que el comando aas se usa junto con los comandos básicos de substracción y sbb. En este caso, tiene sentido usar el comando sub solo una vez, al restar los dígitos más bajos de los operandos, luego se debe usar el comando sbb, que tendrá en cuenta un posible préstamo del orden más alto.

Multiplicación de números BCD sin empaquetar

Usando el ejemplo de sumar y restar números sin empaquetar, quedó claro que no existen algoritmos estándar para realizar estas operaciones en números BCD, y el programador debe, según los requisitos de su programa, implementar estas operaciones.

La implementación de las dos operaciones restantes, multiplicación y división, es aún más complicada. En el conjunto de instrucciones del microprocesador, solo hay medios para la producción de multiplicaciones y divisiones de números BCD descomprimidos de un solo dígito.

Para multiplicar números de dimensión arbitraria, debe implementar el proceso de multiplicación usted mismo, según algún algoritmo de multiplicación, por ejemplo, "en una columna".

Para multiplicar dos números BCD de un dígito, debes:

1) coloque uno de los factores en el registro AL (como lo requiere la instrucción mul);

2) colocar el segundo operando en un registro o memoria, asignando un byte;

3) multiplicar los factores con el comando mul (el resultado, como era de esperar, estará en ah);

4) el resultado, por supuesto, estará en código binario, por lo que debe corregirse.

Para corregir el resultado después de la multiplicación, se usa un comando especial - aam (ASCII Adjust for Multiplication) - corrección del resultado de la multiplicación para su representación en forma simbólica.

No tiene operandos y opera en el registro AX de la siguiente manera:

1) divide al por 10;

2) el resultado de la división se escribe así: cociente en al, resto en ah. Como resultado, después de ejecutar la instrucción aam, los registros AL y ah contienen los dígitos BCD correctos del producto de dos dígitos.

Antes de terminar nuestra discusión sobre el comando aam, debemos señalar un uso más del mismo. Este comando se puede utilizar para convertir un número binario en el registro AL en un número BCD desempaquetado, que se colocará en el registro ah: el dígito más significativo del resultado está en ah, el dígito menos significativo está en al. Está claro que el número binario debe estar en el rango 0...99.

División de números BCD sin empaquetar

El proceso de realizar la operación de división de dos números BCD sin empaquetar es algo diferente de las otras operaciones consideradas anteriormente con ellos. Aquí también se requieren acciones de corrección, pero deben llevarse a cabo antes de la operación principal que divide directamente un número BCD por otro número BCD. Primero, en el registro ah, debe obtener dos dígitos BCD del dividendo sin empaquetar. Esto hace que el programador se sienta cómodo para él en cierto modo. A continuación, debe emitir el comando aad - aad (ASCII Adjust for Division) - corrección de división para representación simbólica.

La instrucción no tiene operandos y convierte el número BCD desempaquetado de dos dígitos en el registro ax en un número binario. Este número binario desempeñará posteriormente el papel de dividendo en la operación de división. Además de la conversión, el comando aad coloca el número binario resultante en el registro AL. Naturalmente, el dividendo será un número binario del rango 0...99.

El algoritmo mediante el cual el comando aad realiza esta conversión es el siguiente:

1) multiplicar el dígito más alto del número BCD original en ah (el contenido de AH) por 10;

2) realizar la suma AH + AL, cuyo resultado (número binario) se ingresa en AL;

3) restablecer el contenido de AN.

A continuación, el programador debe emitir un comando de división div normal para realizar la división del contenido de ax por un solo dígito BCD ubicado en un registro de bytes o en una ubicación de memoria de bytes.

Similar a aash, el comando aad también se puede utilizar para convertir números BCD desempaquetados del rango 0...99 a su equivalente binario.

Para dividir números de mayor capacidad, así como en el caso de la multiplicación, debe implementar su propio algoritmo, por ejemplo, "en una columna", o encontrar una forma más óptima.

Aritmética en números BCD empaquetados

Como se indicó anteriormente, los números BCD empaquetados solo se pueden sumar y restar. Para realizar otras acciones en ellos, deben convertirse adicionalmente a un formato desempaquetado oa una representación binaria. Debido al hecho de que los números BCD empaquetados no son de gran interés, los consideraremos brevemente.

Adición de números BCD empaquetados

Primero, vayamos al meollo del problema e intentemos sumar dos números BCD empaquetados de dos dígitos. Ejemplo de adición de números BCD empaquetados:

67 = 01100111

+

75 = 01110101

=

142 = 1101 1100 = 220

Como puedes ver, en binario el resultado es 1101 1100 (o 220 en decimal), lo cual es incorrecto. Esto se debe a que el microprocesador desconoce la existencia de números BCD y los suma de acuerdo con las reglas para sumar números binarios. En realidad, el resultado en BCD debería ser 0001 0100 0010 (o 142 en decimal).

Puede verse que, al igual que para los números BCD no empaquetados, para los números BCD empaquetados existe la necesidad de corregir de alguna manera los resultados de las operaciones aritméticas.

El microprocesador proporciona este comando daa - daa (Ajuste decimal para la suma) - corrección del resultado de la suma para la presentación en forma decimal.

El comando daa convierte el contenido del registro al en dos dígitos decimales empaquetados de acuerdo con el algoritmo dado en la descripción del comando daa.La unidad resultante (si el resultado de la suma es mayor que 99) se almacena en la bandera cf, teniendo así en cuenta la transferencia al bit más significativo.

Resta de números BCD empaquetados

Similar a la suma, el microprocesador trata los números BCD empaquetados como binarios y resta los números BCD como binarios en consecuencia.

ejemplo

Resta de números BCD empaquetados.

Restemos 67-75. Como el microprocesador realiza la resta a modo de suma, seguiremos esto:

67 = 01100111

+

-75=10110101

=

-8 = 0001 1100 = 28

Como puedes ver, el resultado es 28 en decimal, lo cual es absurdo. En BCD, el resultado debe ser 0000 1000 (u 8 en decimal).

Al programar la resta de números BCD empaquetados, el programador, así como al restar números BCD no empaquetados, debe controlar el signo por sí mismo. Esto se hace usando la bandera CF, que corrige el préstamo de orden alto.

La resta de números BCD en sí se realiza mediante un simple comando de resta sub o sbb. La corrección del resultado se realiza mediante el comando das - das (Ajuste decimal para la resta) - corrección del resultado de la resta para la representación en forma decimal.

El comando das convierte el contenido del registro AL en dos dígitos decimales empaquetados según el algoritmo proporcionado en la descripción del comando das.

LECCIÓN N° 19. Comandos de transferencia de control

1. Comandos lógicos

Junto con los medios de los cálculos aritméticos, el sistema de comando del microprocesador también tiene medios de conversión de datos lógicos. Por medios lógicos tales transformaciones de datos, que se basan en las reglas de la lógica formal.

La lógica formal opera al nivel de los enunciados verdaderos y falsos. Para un microprocesador, esto generalmente significa 1 y 0, respectivamente. Para una computadora, el lenguaje de ceros y unos es nativo, pero la unidad mínima de datos con la que funcionan las instrucciones de la máquina es un byte. Sin embargo, a nivel de sistema, a menudo es necesario poder operar al nivel más bajo posible, el nivel de bits.

Arroz. 29. Medios de procesamiento lógico de datos

Los medios de transformación de datos lógicos incluyen comandos lógicos y operaciones lógicas. El operando de una instrucción en ensamblador generalmente puede ser una expresión, que a su vez es una combinación de operadores y operandos. Entre estos operadores puede haber operadores que implementen operaciones lógicas en objetos de expresión.

Antes de considerar estas herramientas en detalle, consideremos cuáles son los datos lógicos en sí mismos y qué operaciones se realizan en ellos.

datos booleanos

La base teórica para el procesamiento lógico de datos es la lógica formal. Hay varios sistemas de lógica. Uno de los más famosos es el cálculo proposicional. Una proposición es cualquier afirmación de la que se puede decir que es verdadera o falsa.

El cálculo proposicional es un conjunto de reglas que se utilizan para determinar la verdad o falsedad de alguna combinación de proposiciones.

El cálculo proposicional se combina muy armoniosamente con los principios de la computadora y los métodos básicos de su programación. Todos los componentes de hardware de una computadora están construidos sobre chips lógicos. El sistema de representación de información en una computadora al nivel más bajo se basa en el concepto de bit. Un bit, que tiene solo dos estados (0 (falso) y 1 (verdadero)), encaja naturalmente en el cálculo proposicional.

De acuerdo con la teoría, las siguientes operaciones lógicas se pueden realizar en declaraciones (en bits).

1. Negación (NO lógico): una operación lógica en un operando, cuyo resultado es el recíproco del valor del operando original.

Esta operación se caracteriza únicamente por la siguiente tabla de verdad (Tabla 12).

Tabla 12. Tabla de verdad para negación lógica

2. Suma lógica (OR inclusivo lógico): una operación lógica en dos operandos, cuyo resultado es "verdadero" (1) si uno o ambos operandos son verdaderos (1) y "falso" (0) si ambos operandos son falso (0).

Esta operación se describe utilizando la siguiente tabla de verdad (Tabla 13).

Tabla 13. Tabla de verdad para OR lógico inclusivo

3. Multiplicación lógica (AND lógico): una operación lógica en dos operandos, cuyo resultado es verdadero (1) solo si ambos operandos son verdaderos (1). En todos los demás casos, el valor de la operación es "falso" (0).

Esta operación se describe utilizando la siguiente tabla de verdad (Tabla 14).

Tabla 14. Tabla de verdad lógica Y

4. Adición exclusiva lógica (OR exclusivo lógico): una operación lógica en dos operandos, cuyo resultado es "verdadero" (1), si solo uno de los dos operandos es verdadero (1) y falso (0), si ambos operandos son falsos (0) o verdaderos (1). Esta operación se describe utilizando la siguiente tabla de verdad (Tabla 15).

Tabla 15. Tabla de verdad para XOR lógico

El conjunto de instrucciones del microprocesador contiene cinco instrucciones que soportan estas operaciones. Estas instrucciones realizan operaciones lógicas en los bits de los operandos. Las dimensiones de los operandos, por supuesto, deben ser las mismas. Por ejemplo, si la dimensión de los operandos es igual a la palabra (16 bits), entonces la operación lógica se realiza primero en los bits cero de los operandos y su resultado se escribe en lugar del bit 0 del resultado. A continuación, el comando repite estas acciones secuencialmente en todos los bits desde el primero hasta el decimoquinto.

Comandos lógicos

El sistema de comandos del microprocesador tiene el siguiente conjunto de comandos que permiten trabajar con datos lógicos:

1) y operando_1, operando_2 - operación de multiplicación lógica. El comando realiza una operación AND lógica bit a bit (conjunción) en los bits de los operandos operand_1 y operand_2. El resultado se escribe en lugar de operando_1;

2) og operando_1, operando_2 - operación de suma lógica. El comando realiza una operación OR lógica bit a bit (disyunción) en los bits de los operandos operand_1 y operand_2. El resultado se escribe en lugar de operando_1;

3) xor operando_1, operando_2 - operación de suma exclusiva lógica. El comando realiza una operación XOR lógica bit a bit en los bits de los operandos operand_1 y operand_2. El resultado se escribe en lugar del operando;

4) prueba operando_1, operando_2 - operación de "prueba" (usando el método de multiplicación lógica). El comando realiza una operación AND lógica bit a bit en los bits de los operandos operand_1 y operand_2. El estado de los operandos sigue siendo el mismo, solo se cambian los indicadores zf, sf y pf, lo que permite analizar el estado de los bits individuales del operando sin cambiar su estado;

5) no operando - operación de negación lógica. El comando realiza una inversión bit a bit (reemplazando el valor con el opuesto) de cada bit del operando. El resultado se escribe en lugar del operando.

Para comprender el papel de los comandos lógicos en el conjunto de instrucciones del microprocesador, es muy importante comprender las áreas de su aplicación y los métodos típicos de su uso en la programación.

Con la ayuda de comandos lógicos, es posible seleccionar bits individuales en el operando con el fin de configurarlos, restablecerlos, invertirlos o simplemente verificar un cierto valor.

Para organizar dicho trabajo con bits, el operando_2 suele desempeñar el papel de una máscara. Con la ayuda de los bits de esta máscara establecidos en el bit 1, se determinan los bits operand_1 necesarios para una operación particular. Vamos a mostrar qué comandos lógicos se pueden usar para este propósito:

1) para establecer ciertos dígitos (bits) a 1, se utiliza el comando og operando_1, operando_2.

En esta instrucción, operando_2, que actúa como máscara, debe contener 1 bits en lugar de los bits que deben establecerse en 1 en operando_XNUMX;

2) para restablecer ciertos dígitos (bits) a 0, se utiliza el comando y operando_1, operando_2.

En esta instrucción, operando_2, que actúa como máscara, debe contener cero bits en lugar de aquellos bits que deben establecerse en 0 en operando_1;

3) se aplica el comando xor operando_1, operando_2:

a) para averiguar qué bits difieren en operando_1 y operando;

b) para invertir el estado de los bits especificados en operando_1.

Los bits de máscara que nos interesan (operando_2) al ejecutar el comando xor deben ser simples, el resto debe ser cero;

El comando probar operando_1, operando_2 (comprobar operando_1) se utiliza para comprobar el estado de los bits especificados.

Los bits verificados de operando_1 en la máscara (operando_2) deben establecerse en uno. El algoritmo del comando de prueba es similar al algoritmo del comando and, pero no cambia el valor de operando_1. El resultado del comando es establecer el valor de la bandera cero zf:

1) si zf = 0, entonces, como resultado de la multiplicación lógica, se obtiene un resultado cero, es decir, un bit de unidad de la máscara, que no coincidía con el bit de unidad correspondiente del operando;

2) si zf = 1, como resultado de la multiplicación lógica se obtiene un resultado distinto de cero, es decir, al menos un bit unitario de la máscara coincide con el bit unitario correspondiente del operando_1.

Para reaccionar al resultado del comando de prueba, se recomienda utilizar el comando de salto jnz label (Jump if Not Zero) - jump if the zero flag zf is non-zero, o el comando de acción inversa - jz label (Jump if Zero ) - salta si la bandera cero zf = 0.

Los siguientes dos comandos buscan el primer bit de operando establecido en 1. La búsqueda se puede realizar tanto desde el principio como desde el final del operando:

1) bsf operand_1, operand_2 (Exploración de bits hacia adelante): exploración de bits hacia adelante. La instrucción busca (explora) los bits del operando_2 desde el menos significativo hasta el más significativo (del bit 0 al bit más significativo) en busca del primer bit establecido en 1. Si se encuentra uno, operando_1 se llena con el número de este bit como un valor entero. Si todos los bits de operando_2 son 0, entonces el indicador cero zf se establece en 1; de lo contrario, el indicador zf se restablece en 0;

2) bsr operand_1, operand_2 (Bit Scanning Reset) - escanear bits en orden inverso. La instrucción busca (escanea) los bits del operando_2 desde el más significativo al menos significativo (del bit más significativo al bit 0) en busca del primer bit establecido en 1. Si se encuentra uno, el operando_1 se llena con el número de este bit como un valor entero. Es importante que la posición del primer bit de la unidad a la izquierda todavía se cuente en relación con el bit 0. Si todos los bits del operando_2 son 0, entonces el indicador cero zf se establece en 1; de lo contrario, el indicador zf se restablece en 0.

En los últimos modelos de microprocesadores Intel, han aparecido algunas instrucciones más en el grupo de instrucciones lógicas que le permiten acceder a un bit específico del operando. El operando puede estar en memoria o en un registro general. La posición del bit viene dada por el desplazamiento del bit con respecto al bit menos significativo del operando. El valor de compensación se puede especificar como un valor directo o estar contenido en un registro de propósito general. Puede utilizar los resultados de los comandos bsr y bsf como valor de compensación. Todas las instrucciones asignan el valor del bit seleccionado a la bandera CE.

1) operando bt, bit_offset (Prueba de bit) - prueba de bit. La instrucción transfiere el valor del bit a la bandera cf;

2) operando bts, offset_bit (Bit Test and Set) - comprobación y configuración de un bit. La instrucción transfiere el valor del bit a la bandera CF y luego establece el bit a verificar en 1;

3) operando btr, bit_offset (Prueba y restablecimiento de bits): comprobación y restablecimiento de un bit. La instrucción transfiere el valor del bit al indicador CF y luego establece este bit en 0;

4) operando btc, offset_bit (Bit Test and Convert) - verificando e invirtiendo un bit. La instrucción envuelve el valor de un bit en el indicador cf y luego invierte el valor de ese bit.

Comandos de cambio

Las instrucciones de este grupo también proporcionan la manipulación de bits individuales de los operandos, pero de una manera diferente a las instrucciones lógicas discutidas anteriormente.

Todas las instrucciones de desplazamiento mueven bits en el campo del operando hacia la izquierda o hacia la derecha según el código de operación. Todas las instrucciones de cambio tienen la misma estructura: copiar operando, shift_count.

El número de bits a desplazar - counter_shifts - se encuentra en el lugar del segundo operando y se puede configurar de dos maneras:

1) estáticamente, lo que implica establecer un valor fijo usando un operando directo;

2) dinámicamente, lo que significa ingresar el valor del contador de desplazamiento en el registro cl antes de ejecutar la instrucción de desplazamiento.

Según la dimensión del registro cl, está claro que el valor del contador de desplazamiento puede oscilar entre 0 y 255. Pero, de hecho, esto no es del todo cierto. Para fines de optimización, el microprocesador acepta solo el valor de los cinco bits menos significativos del contador, es decir, el valor se encuentra en el rango de 0 a 31.

Todas las instrucciones de cambio establecen la bandera de acarreo cf.

A medida que los bits salen del operando, primero golpean la bandera de acarreo, estableciéndola igual al valor del siguiente bit fuera del operando. El destino de este bit a continuación depende del tipo de instrucción de desplazamiento y del algoritmo del programa.

Los comandos de cambio se pueden dividir en dos tipos según el principio de funcionamiento:

1) comandos de cambio lineal;

2) comandos de cambio cíclico.

Comandos de desplazamiento lineal

Los comandos de este tipo incluyen comandos que cambian según el siguiente algoritmo:

1) el siguiente bit que se presiona establece la bandera CF;

2) el bit ingresado en el operando desde el otro extremo tiene el valor 0;

3) cuando se desplaza el siguiente bit, pasa a la bandera CF, ¡mientras que se pierde el valor del bit desplazado anterior! Los comandos de desplazamiento lineal se dividen en dos subtipos:

1) comandos de desplazamiento lineal lógico;

2) instrucciones de cambio lineal aritmético.

Los comandos de desplazamiento lineal lógico incluyen lo siguiente:

1) operando shl, counter_shifts (desplazamiento lógico a la izquierda) - desplazamiento lógico a la izquierda. El contenido del operando se desplaza hacia la izquierda el número de bits especificado por shift_count. A la derecha (en la posición del bit menos significativo) se ingresan ceros;

2) operando shr, shift_count (desplazamiento lógico a la derecha) - desplazamiento lógico a la derecha. El contenido del operando se desplaza hacia la derecha el número de bits especificado por shift_count. A la izquierda (en la posición del bit de signo más significativo), se ingresan ceros.

La Figura 30 muestra cómo funcionan estos comandos.

Arroz. 30. Esquema de trabajo de comandos de desplazamiento lógico lineal

Las instrucciones de desplazamiento lineal aritmético difieren de las instrucciones de desplazamiento lógico en que operan en el bit de signo del operando de una manera especial.

1) operando sal, shift_counter (desplazamiento aritmético a la izquierda) - desplazamiento aritmético a la izquierda. El contenido del operando se desplaza hacia la izquierda el número de bits especificado por shift_count. A la derecha (en la posición del bit menos significativo), se ingresan ceros. La instrucción sal no conserva el signo, pero establece la bandera con / en caso de que un signo cambie por el siguiente bit avanzado. De lo contrario, el comando sal es exactamente igual que el comando shl;

2) operando sar, shift_count (desplazamiento aritmético a la derecha) - desplazamiento aritmético a la derecha. El contenido del operando se desplaza hacia la derecha el número de bits especificado por shift_count. Los ceros se insertan en el operando de la izquierda. El comando sar conserva el signo y lo restaura después de cada cambio de bit.

La Figura 31 muestra cómo funcionan las instrucciones de desplazamiento aritmético lineal.

Arroz. 31. Esquema de funcionamiento de los comandos de desplazamiento aritmético lineal

Rotar comandos

Las instrucciones de desplazamiento cíclico incluyen instrucciones que almacenan los valores de los bits desplazados. Hay dos tipos de instrucciones de cambio cíclico:

1) comandos de cambio cíclicos simples;

2) comandos de cambio cíclico a través de la bandera de acarreo cf.

Los comandos de cambio cíclico simples incluyen:

1) operando rol, shift_counter (Girar a la izquierda) - desplazamiento cíclico a la izquierda. El contenido del operando se desplaza hacia la izquierda el número de bits especificado por el operando shift_count. Los bits desplazados a la izquierda se escriben en el mismo operando desde la derecha;

2) operando gog, counter_shifts (Girar a la derecha) - desplazamiento cíclico a la derecha. El contenido del operando se desplaza hacia la derecha el número de bits especificado por el operando shift_count. Los bits desplazados a la derecha se escriben en el mismo operando de la izquierda.

Arroz. 32. Esquema de funcionamiento de los comandos de un cambio cíclico simple.

Como se puede ver en la Figura 32, las instrucciones de un desplazamiento cíclico simple en el curso de su trabajo realizan una acción útil, a saber: el bit desplazado cíclicamente no solo se empuja al operando desde el otro extremo, sino que al mismo tiempo su El valor se convierte en el valor de la bandera CE.

Los comandos de cambio cíclico a través de la bandera de acarreo CF difieren de los comandos de cambio cíclico simples en que el bit cambiado no ingresa inmediatamente al operando desde su otro extremo, sino que primero se escribe en la bandera de acarreo CE Solo la siguiente ejecución de este comando de cambio ( siempre que se ejecute en bucle) hace que el bit previamente avanzado se coloque en el otro extremo del operando (Fig. 33).

Los siguientes están relacionados con los comandos de cambio cíclico a través de la bandera de acarreo:

1) operando rcl, shift_count (rotar a través de acarreo a la izquierda) - desplazamiento cíclico a la izquierda a través de acarreo.

El contenido del operando se desplaza hacia la izquierda el número de bits especificado por el operando shift_count. Los bits desplazados a su vez se convierten en el valor de la bandera de acarreo cf.

2) operando rsg, shift_count (Girar a través de Acarreo a la derecha) - desplazamiento cíclico a la derecha a través de un acarreo.

El contenido del operando se desplaza hacia la derecha el número de bits especificado por el operando shift_count. Los bits desplazados, a su vez, se convierten en el valor de la bandera de acarreo CF.

Arroz. 33. Instrucciones de rotación a través de la bandera de acarreo CF

La Figura 33 muestra que cuando se desplaza a través de la bandera de acarreo, aparece un elemento intermedio, con la ayuda del cual, en particular, es posible reemplazar los bits desplazados cíclicamente, en particular, la falta de coincidencia de las secuencias de bits.

En lo sucesivo, el desajuste de una secuencia de bits significa una acción que permite de alguna manera localizar y extraer las secciones necesarias de esta secuencia y escribirlas en otro lugar.

Comandos de cambio adicionales

El sistema de comando de los últimos modelos de microprocesador Intel, comenzando con el i80386, contiene comandos de cambio adicionales que amplían las capacidades que comentamos anteriormente. Estos son los comandos de cambio de doble precisión:

1) shld operando_1, operando_2, shift_counter - desplazamiento a la izquierda de doble precisión. El comando shld realiza un reemplazo desplazando los bits del operando_1 hacia la izquierda, llenando sus bits de la derecha con los valores de los bits desplazados del operando_2 según el diagrama de la Fig. 34. El número de bits que se desplazarán está determinado por el valor de shift_counter, que puede estar en el rango 0... 31. Este valor puede especificarse como un operando inmediato o estar contenido en el registro cl. El valor del operando_2 no se cambia.

Arroz. 34. El esquema del comando shld

2) shrd operando_1, operando_2, shift_counter - desplazamiento a la derecha de doble precisión. La instrucción realiza el reemplazo desplazando los bits del operando_1 hacia la derecha, llenando sus bits de la izquierda con los valores de los bits desplazados del operando_2 según el diagrama de la Figura 35. El número de bits desplazados está determinado por el valor de shift_counter, que puede estar en el rango 0...31. Este valor puede especificarse mediante el operando inmediato o estar contenido en el registro cl. El valor del operando_2 no se cambia.

Arroz. 35. El esquema del comando shrd

Como notamos, los comandos shld y shrd se desplazan hasta 32 bits, pero debido a las peculiaridades de especificar operandos y el algoritmo de operación, estos comandos se pueden usar para trabajar con campos de hasta 64 bits de longitud.

2. Comandos de transferencia de control

Nos familiarizamos con algunos comandos a partir de los cuales se forman las secciones lineales del programa. Cada uno de ellos generalmente realiza alguna conversión o transferencia de datos, después de lo cual el microprocesador transfiere el control a la siguiente instrucción. Pero muy pocos programas funcionan de manera tan consistente. Por lo general, hay puntos en un programa en los que se debe tomar una decisión sobre qué instrucción se ejecutará a continuación. Esta solución podría ser:

1) incondicional: en este punto, es necesario transferir el control no al comando que viene a continuación, sino a otro, que está a cierta distancia del comando actual;

2) condicional: la decisión sobre qué comando se ejecutará a continuación se basa en el análisis de algunas condiciones o datos.

Un programa es una secuencia de comandos y datos que ocupan una cierta cantidad de espacio RAM. Este espacio de memoria puede ser contiguo o constar de múltiples fragmentos.

Qué instrucción de programa debe ejecutarse a continuación, el microprocesador aprende del contenido del par de registros cs: (e) ip:

1) cs - registro de segmento de código, que contiene la dirección física (base) del segmento de código actual;

2) eip/ip: registro de puntero de instrucción, que contiene un valor que representa el desplazamiento en la memoria de la siguiente instrucción que se ejecutará en relación con el comienzo del segmento de código actual.

El registro en particular que se utilizará depende del modo de direccionamiento establecido use16 o use32. Si se especifica use 16, entonces se usa ip, si use32, entonces se usa eip.

Por lo tanto, las instrucciones de transferencia de control cambian el contenido de los registros cs y eip / ip, como resultado de lo cual el microprocesador selecciona para ejecución no la siguiente instrucción del programa en orden, sino la instrucción en alguna otra sección del programa. La tubería dentro del microprocesador se reinicia.

De acuerdo con el principio de funcionamiento, los comandos del microprocesador que proporcionan la organización de las transiciones en el programa se pueden dividir en 3 grupos:

1. Transferencia incondicional de comandos de control:

1) un comando de bifurcación incondicional;

2) un comando para llamar a un procedimiento y regresar de un procedimiento;

3) un comando para llamar a las interrupciones de software y regresar de las interrupciones de software.

2. Comandos de transferencia de control condicional:

1) comandos de salto por el resultado del comando de comparación p;

2) comandos de transición según el estado de una determinada bandera;

3) instrucciones para saltar a través del contenido del registro esx/cx.

3. Comandos de control de ciclo:

1) un comando para organizar un ciclo con un contador ехх/сх;

2) un comando para organizar un ciclo con un contador ех/сх con la posibilidad de una salida anticipada del ciclo mediante una condición adicional.

Saltos incondicionales

La discusión anterior ha revelado algunos detalles del mecanismo de transición. Las instrucciones de salto modifican el registro de puntero de instrucción eip/ip y posiblemente el registro de segmento de código cs. Lo que necesita ser modificado exactamente depende de:

1) en el tipo de operando en la instrucción de bifurcación incondicional (cerca o lejos);

2) de especificar un modificador antes de la dirección de salto (en la instrucción de salto); en este caso, la propia dirección de salto se puede ubicar directamente en la instrucción (salto directo) o en un registro o celda de memoria (salto indirecto).

El modificador puede tomar los siguientes valores:

1) cerca de ptr: transición directa a una etiqueta dentro del segmento de código actual. Solo se modifica el registro eip/ip (según el tipo de segmento de código use16 o use32 especificado) según la dirección (etiqueta) especificada en el comando o una expresión que usa el símbolo de extracción de valor - $;

2) far ptr: transición directa a una etiqueta en otro segmento de código. La dirección de salto se especifica como un operando inmediato o dirección (etiqueta) y consta de un selector de 16 bits y un desplazamiento de 16/32 bits, que se cargan en los registros cs e ip/eip, respectivamente;

3) palabra ptr: transición indirecta a una etiqueta dentro del segmento de código actual. Solo se modifica eip/ip (por el valor de desplazamiento de la memoria en la dirección especificada en el comando, o de un registro). Tamaño de desplazamiento de 16 o 32 bits;

4) dword ptr: transición indirecta a una etiqueta en otro segmento de código. Ambos registros, cs y eip/ip, se modifican (por un valor de memoria, y solo de memoria, de un registro). La primera palabra/dword de esta dirección representa el desplazamiento y se carga en ip/eip; la segunda/tercera palabra se carga en cs. instrucción de salto incondicional jmp

La sintaxis del comando para un salto incondicional es jmp [modificador] jump_address: un salto incondicional sin guardar información sobre el punto de retorno.

Jump_address es la dirección en forma de etiqueta o la dirección del área de memoria en la que se encuentra el puntero de salto.

En total, en el sistema de instrucciones del microprocesador existen varios códigos de instrucciones máquina para el salto incondicional.

Sus diferencias están determinadas por la distancia de transición y la forma en que se especifica la dirección de destino. La distancia de salto está determinada por la ubicación del operando jump_address. Esta dirección puede estar en el segmento de código actual o en algún otro segmento. En el primer caso, la transición se llama intrasegmento, o cerca, en el segundo, intersegmento o distante. Un salto dentro de un segmento asume que solo se cambia el contenido del registro eip/ip.

Hay tres opciones para el uso dentro de un segmento del comando jmp:

1) recto corto;

2) recto;

3) indirecta.

Процедуры

El lenguaje ensamblador tiene varias herramientas que resuelven el problema de la duplicación de secciones de código. Éstos incluyen:

1) mecanismo de procedimientos;

2) ensamblador de macros;

3) mecanismo de interrupción.

Un procedimiento, a menudo también llamado subrutina, es la unidad funcional básica para descomponer (dividir en varias partes) una tarea. Un procedimiento es un grupo de comandos para resolver una subtarea específica y tiene los medios para recibir el control desde el punto donde se llama a la tarea en un nivel superior y devolver el control a este punto.

En el caso más simple, el programa puede consistir en un solo procedimiento. En otras palabras, un procedimiento se puede definir como un conjunto bien formado de comandos que, al ser descritos una vez, se pueden llamar en cualquier parte del programa si es necesario.

Para describir una secuencia de comandos como un procedimiento en lenguaje ensamblador, se utilizan dos directivas: PROC y ENDP.

La sintaxis de descripción del procedimiento es la siguiente (Fig. 36).

Arroz. 36. Sintaxis de la descripción del procedimiento en el programa

La Figura 36 muestra que en el encabezado del procedimiento (directiva PROC), solo el nombre del procedimiento es obligatorio. Entre la gran cantidad de operandos de la directiva PROC, cabe destacar [distancia]. Este atributo puede tomar valores cercanos o lejanos y caracteriza la posibilidad de llamar al procedimiento desde otro segmento de código. De forma predeterminada, el atributo [distancia] se establece en cerca.

El procedimiento se puede colocar en cualquier parte del programa, pero de tal manera que no obtenga el control al azar. Si el procedimiento simplemente se inserta en el flujo de instrucciones general, entonces el microprocesador percibirá las instrucciones del procedimiento como parte de este flujo y, en consecuencia, ejecutará las instrucciones del procedimiento.

Saltos condicionales

El microprocesador tiene 18 instrucciones de salto condicional. Estos comandos le permiten verificar:

1) la relación entre operandos con signo ("mayor - menor");

2) la relación entre operandos sin signo ("más alto - más bajo");

3) estados de banderas aritméticas ZF, SF, CF, OF, PF (pero no AF).

Los comandos de salto condicional tienen la misma sintaxis:

jcc jump_label

Como puede ver, el código mnemotécnico de todos los comandos comienza con "j", de la palabra salto (jump), determina la condición específica analizada por el comando.

En cuanto al operando jump_label, esta etiqueta solo se puede ubicar dentro del segmento de código actual; no se permite la transferencia de control entre segmentos en saltos condicionales. En este sentido, no hay dudas sobre el modificador, que estaba presente en la sintaxis de los comandos de salto incondicional. En los primeros modelos del microprocesador (i8086, i80186 e i80286), las instrucciones de bifurcación condicional solo podían realizar saltos cortos, de -128 a +127 bytes desde la instrucción que seguía a la instrucción de bifurcación condicional. A partir del modelo de microprocesador 80386, esta restricción se elimina, pero, como puede ver, solo dentro del segmento de código actual.

Para tomar una decisión sobre dónde se transferirá el control al comando de salto condicional, primero se debe formar una condición, en base a la cual se tomará la decisión de transferir el control.

Las fuentes de tal condición pueden ser:

1) cualquier comando que cambie el estado de las banderas aritméticas;

2) la instrucción de comparación p, que compara los valores de dos operandos;

3) el estado del registro esx/cx.

comando de comparación cmp

El comando de comparación de páginas tiene una forma interesante de trabajar. Es exactamente lo mismo que el comando de resta: suboperando, operando_2.

La instrucción p, como la instrucción sub, resta operandos y establece indicadores. Lo único que no hace es escribir el resultado de la resta en lugar del primer operando.

La sintaxis del comando str - str operand_1, operand_2 (comparar) - compara dos operandos y establece indicadores en función de los resultados de la comparación.

Las banderas establecidas por el comando p pueden analizarse mediante instrucciones especiales de bifurcación condicional. Antes de verlos, prestemos un poco de atención a los mnemotécnicos de estas instrucciones de salto condicional (Tabla 16). Comprender la notación al formar el nombre de los comandos de salto condicional (el elemento en el nombre del comando jcc, lo designamos) facilitará su memorización y su uso práctico adicional.

Tabla 16. Significado de las abreviaturas en el nombre del comando jcc Tabla 17. Lista de comandos de salto condicional para el comando p operando_1, operando_2

No se sorprenda por el hecho de que varios códigos mnemotécnicos diferentes de comandos de rama condicional corresponden a los mismos valores de bandera (están separados entre sí por una barra inclinada en la Tabla 17). La diferencia de nombre se debe al deseo de los desarrolladores de microprocesadores de facilitar el uso de instrucciones de salto condicional en combinación con ciertos grupos de instrucciones. Por lo tanto, diferentes nombres reflejan más bien una orientación funcional diferente. Sin embargo, el hecho de que estos comandos respondan a las mismas banderas los hace absolutamente equivalentes e iguales en el programa. Por lo tanto, en la Tabla 17 se agrupan no por nombre, sino por los valores de las banderas (condiciones) a las que responden.

Indicadores e instrucciones de bifurcación condicional

La designación mnemotécnica de algunas instrucciones de salto condicional refleja el nombre de la bandera con la que trabajan, y tiene la siguiente estructura: el primer carácter es "j" (Jump, jump), el segundo es la designación de bandera o el carácter de negación " n", seguido del nombre de la bandera. Esta estructura de equipo refleja su propósito. Si no hay el carácter "n", se verifica el estado de la bandera, si es igual a 1, se realiza una transición a la etiqueta de salto. Si el carácter "n" está presente, entonces se verifica que el estado de la bandera sea igual a 0 y, si tiene éxito, se realiza un salto a la etiqueta de salto.

Los mnemotécnicos de los comandos, los nombres de las banderas y las condiciones de salto se muestran en la Tabla 18. Estos comandos se pueden usar después de cualquier comando que modifique las banderas especificadas.

Tabla 18. Indicadores e instrucciones de salto condicional

Si observa detenidamente las tablas 17 y 18, puede ver que muchas de las instrucciones de salto condicional que contienen son equivalentes, ya que ambas se basan en el análisis de las mismas banderas.

Instrucciones de salto condicional y el registro esx/cx

La arquitectura del microprocesador implica el uso específico de muchos registros. Por ejemplo, el registro EAX / AX / AL se usa como acumulador, y los registros BP, SP se usan para trabajar con la pila. El registro ECX / CX también tiene un cierto propósito funcional: actúa como un contador en los comandos de control de bucle y cuando se trabaja con cadenas de caracteres. Es posible que funcionalmente la instrucción de bifurcación condicional asociada con el registro esx/cx se atribuya más correctamente a este grupo de instrucciones.

La sintaxis de esta instrucción de bifurcación condicional es:

1) jcxz jump_label (Saltar si ex es cero) - saltar si cx es cero;

2) jecxz jump_label (Jump Equal ех Zero) - salta si ех es cero.

Estos comandos son muy útiles cuando se realizan bucles y cuando se trabaja con cadenas de caracteres.

Cabe señalar que existe una limitación inherente al comando jcxz/jecxz. A diferencia de otras instrucciones de transferencia condicional, la instrucción jcxz/jecxz solo puede abordar saltos cortos de -128 bytes o +127 bytes desde la instrucción que le sigue.

Organización de ciclos

El ciclo, como saben, es una estructura algorítmica importante, sin cuyo uso, probablemente, ningún programa puede funcionar. Puede organizar la ejecución cíclica de una determinada sección del programa, por ejemplo, utilizando la transferencia condicional de comandos de control o el comando de salto incondicional jmp. Con una organización de ciclo de este tipo, todas las operaciones para su organización se realizan manualmente. Pero, dada la importancia de un elemento algorítmico como un ciclo, los desarrolladores del microprocesador introdujeron en el sistema de instrucciones un grupo de tres comandos, lo que facilita la programación de ciclos. Estas instrucciones también utilizan el registro esx/cx como contador de bucles.

Demos una breve descripción de estos comandos:

1) bucle Transition_label (bucle): repite el ciclo. El comando le permite organizar bucles similares a los bucles for en lenguajes de alto nivel con disminución automática del contador de bucles. El trabajo del equipo es hacer lo siguiente:

a) decremento del registro ECX/CX;

b) comparar el registro ECX/CX con cero: si (ECX/CX) = 0, entonces el control se transfiere al siguiente comando después del bucle;

2) loope/loopz jump_label

Los comandos loope y loopz son sinónimos absolutos. El trabajo de los comandos es realizar las siguientes acciones:

a) decremento del registro ECX/CX;

b) comparar el registro ECX/CX con cero;

c) análisis del estado de la bandera cero ZF si (ECX/CX) = 0 o XF = 0, el control se transfiere al siguiente comando después del bucle.

3) loopne/loopnz jump_label

Los comandos loopne y loopnz también son sinónimos absolutos. El trabajo de los comandos es realizar las siguientes acciones:

a) decremento del registro ECX/CX;

b) comparar el registro ECX/CX con cero;

c) análisis del estado de la bandera cero ZF: si (ECX/CX) = 0 o ZF = 1, el control se transfiere al siguiente comando después del bucle.

Los comandos loope/loopz y loopne/loopnz son recíprocos en su funcionamiento. Extienden la acción del comando de bucle al analizar adicionalmente el indicador zf, lo que permite organizar una salida anticipada del bucle, utilizando este indicador como indicador.

La desventaja de los comandos de bucle loop, loope/loopz y loopne/loopnz es que solo implementan saltos cortos (de -128 a +127 bytes). Para trabajar con bucles largos, necesitará usar saltos condicionales y la instrucción jmp, así que intente dominar ambas formas de organizar bucles.

Autor: Tsvetkova A.V.

Recomendamos artículos interesantes. sección Notas de clase, hojas de trucos:

Ley de Impuesto. Notas de lectura

Psicología de la Personalidad. Cuna

Endocrinología. Notas de lectura

Ver otros artículos sección Notas de clase, hojas de trucos.

Lee y escribe útil comentarios sobre este artículo.

<< Volver

Últimas noticias de ciencia y tecnología, nueva electrónica:

Máquina para aclarar flores en jardines. 02.05.2024

En la agricultura moderna, se están desarrollando avances tecnológicos destinados a aumentar la eficiencia de los procesos de cuidado de las plantas. En Italia se presentó la innovadora raleoadora de flores Florix, diseñada para optimizar la etapa de recolección. Esta herramienta está equipada con brazos móviles, lo que permite adaptarla fácilmente a las necesidades del jardín. El operador puede ajustar la velocidad de los alambres finos controlándolos desde la cabina del tractor mediante un joystick. Este enfoque aumenta significativamente la eficiencia del proceso de aclareo de flores, brindando la posibilidad de un ajuste individual a las condiciones específicas del jardín, así como a la variedad y tipo de fruta que se cultiva en él. Después de dos años de probar la máquina Florix en varios tipos de fruta, los resultados fueron muy alentadores. Agricultores como Filiberto Montanari, que ha utilizado una máquina Florix durante varios años, han informado de una reducción significativa en el tiempo y la mano de obra necesarios para aclarar las flores. ... >>

Microscopio infrarrojo avanzado 02.05.2024

Los microscopios desempeñan un papel importante en la investigación científica, ya que permiten a los científicos profundizar en estructuras y procesos invisibles a simple vista. Sin embargo, varios métodos de microscopía tienen sus limitaciones, y entre ellas se encuentra la limitación de resolución cuando se utiliza el rango infrarrojo. Pero los últimos logros de los investigadores japoneses de la Universidad de Tokio abren nuevas perspectivas para el estudio del micromundo. Científicos de la Universidad de Tokio han presentado un nuevo microscopio que revolucionará las capacidades de la microscopía infrarroja. Este instrumento avanzado le permite ver las estructuras internas de las bacterias vivas con una claridad asombrosa en la escala nanométrica. Normalmente, los microscopios de infrarrojo medio están limitados por la baja resolución, pero el último desarrollo de investigadores japoneses supera estas limitaciones. Según los científicos, el microscopio desarrollado permite crear imágenes con una resolución de hasta 120 nanómetros, 30 veces mayor que la resolución de los microscopios tradicionales. ... >>

Trampa de aire para insectos. 01.05.2024

La agricultura es uno de los sectores clave de la economía y el control de plagas es una parte integral de este proceso. Un equipo de científicos del Consejo Indio de Investigación Agrícola-Instituto Central de Investigación de la Papa (ICAR-CPRI), Shimla, ha encontrado una solución innovadora a este problema: una trampa de aire para insectos impulsada por el viento. Este dispositivo aborda las deficiencias de los métodos tradicionales de control de plagas al proporcionar datos de población de insectos en tiempo real. La trampa funciona enteramente con energía eólica, lo que la convierte en una solución respetuosa con el medio ambiente que no requiere energía. Su diseño único permite el seguimiento de insectos tanto dañinos como beneficiosos, proporcionando una visión completa de la población en cualquier zona agrícola. "Evaluando las plagas objetivo en el momento adecuado, podemos tomar las medidas necesarias para controlar tanto las plagas como las enfermedades", afirma Kapil. ... >>

Noticias aleatorias del Archivo

Sangre instantánea 03.04.2003

Investigadores franceses han creado sangre artificial, cuya hemoglobina se fija en la superficie de bolas hechas de un polímero inofensivo que se descompone gradualmente en el cuerpo.

Estas bolas son 20 veces más pequeñas que los eritrocitos naturales, por lo que penetran bien en los capilares más finos. La sangre artificial es apta para transfusiones para todos, independientemente del tipo de sangre. Además, puede secarse para almacenamiento y transporte, y diluirse con solución salina antes de su uso.

Otras noticias interesantes:

▪ Los relojes biológicos de los animales diurnos y nocturnos difieren en su estructura neuronal.

▪ Antibióticos del cannabis

▪ Puertos espaciales flotantes de SpaceX

▪ Nueva mina de cobre en Alemania

▪ deportes y ayuno

Feed de noticias de ciencia y tecnología, nueva electrónica

 

Materiales interesantes de la Biblioteca Técnica Libre:

▪ sección del sitio Aplicación de microcircuitos. Selección de artículos

▪ artículo Llenadora de hielo. Historia de la invención y la producción.

▪ artículo ¿Quién ganó el partido cuando Federer jugó la mitad de hierba y Nadal la mitad de arcilla? Respuesta detallada

▪ artículo Consultor en impuestos y tasas. Descripción del trabajo

▪ artículo Sistema de calefacción solar. Enciclopedia de radioelectrónica e ingeniería eléctrica.

▪ artículo Refranes y refranes lituanos. Selección larga

Deja tu comentario en este artículo:

Nombre:


Email opcional):


comentar:





Todos los idiomas de esta página

Hogar | Biblioteca | Artículos | Mapa del sitio | Revisiones del sitio

www.diagrama.com.ua

www.diagrama.com.ua
2000 - 2024