LENGUAJES DE MAQUINA
Lenguaje de máquina
El lenguaje de máquina de una computadora consta de cadenas de números binarios (ceros y unos) y es el único que "entienden" directamente los procesadores. Todas las instrucciones preparadas en cualquier lenguaje de máquina tienen por lo menos dos partes. La primera es el comando u operación, que dice a la computadora cuál es la función que va a realizar. Todas las computadoras tiene un código de operación para cada una de sus funciones. La segunda parte de la instrucción es el operando, que indica a la computadora donde hallar o almacenar los datos y otras instrucciones que se van a manipular; el número de operandos de una instrucción varía en las distintas computadoras.
En el principio de la computación este era el lenguaje que tenía que "hablar" el ser humano con la computadora y consistía en insertar en un tablero miles de conexiones y alambres y encender y apagar interruptores.
Aunque en la actualidad ya no se emplea, es importante reconocer que ya no es necesario que nos comuniquemos en este lenguaje de "unos" y "ceros", pero es el que internamente una computadora reconoce o "habla".
COMO NACE UNA APLICACIÓN
Todas las
"aplicaciones" (software para el "usuario final") que conocemos han debido ser
creadas especificando primero sus funciones y traduciendo éstas en
"instrucciones" comprensibles por la máquina.
Para ésto, el o los programadores utilizan un determinado "lenguaje de
programación", que es como un idioma: cuenta con un diccionario (los "comandos")
y una gramática (reglas de sintaxis). El programador traduce las
especificaciones de funciones y operaciones (que están en su idioma natural y/o
en gráficos que especifican su secuencia) en este idioma, elaborando un producto
llamado "programa fuente".
Este programa fuente ha de ser luego traducido (por el mismo computador) en el
"lenguaje de máquina", que es el que "entiende" el microprocesador. La
traducción, por lo tanto, debe hacerse teniendo en cuenta el "lenguaje" propio
del chip procesador (de ahí que un mismo programa tendrá una versión "para
PowerPC (PPC)", "para Pentium", etc.
Esta traducción puede hacerse de dos maneras: en forma previa al uso, con lo
cual se genera el "programa compilado" o "programa objeto", o bien se instala
junto con el programa de traducción en el computador del usuario y se hace que
la máquina traduzca las órdenes a medida que lee el programa fuente.
A diferencia de un programa interpretado, un programa compilado (como la mayoría
de las aplicaciones que hoy puede comprar el usuario común) sólo puede ser
utilizado, pero no puede ser leído, revisado o modificado: la compilación lo
hace ilegible. Tratar de "decompilarlo" para conocer su estructura y modo de
funcionar (lo cual se llama "reverse engineering") es muy complejo y normalmente
prohibido en las condiciones de venta. Así, la versión "fuente" constituye un
"secreto comercial", celosamente guardado por los fabricantes. Veremos, al final
del capítulo, que este secreto puede ser algunas veces perjudicial para los
usuarios. Pero antes hablaremos más detalladamente de los lenguajes de
programación.
LENGUAJES DE BAJO Y ALTO NIVEL
En efecto, lo único
que entienden y pueden manipular la Unidad de Control y la Unidad Aritmética son
dígitos binarios o sea series de ceros y unos (paso o no paso de corriente
eléctrica). Así, mientras el hombre usa un "lenguaje natural" (idioma) muy rico
en significados, la máquina usa un sistema en que existiría un sólo
"significado": la diferencia entre 0 y 1 (o sea un bit de información).
¿Cómo lograr más? Simplemente concibiendo un nuevo lenguaje constituido de
"bloques" de dígitos binarios (llamados "bytes"). Este es el primer paso o
"primer nivel" en la construcción de lenguajes de computación.
1er Nivel
Se dice que el código binario es de "bajo nivel" o "primer nivel" (porque al
usar pocos signos logra muy difícilmente expresar cosas complicadas), mientras
un lenguaje humano es de "muy alto nivel" (con una cantidad mayor de signos y
con reglas combinatorias logra expresar con facilidad cosas muy complicadas).
Todo el esfuerzo, entonces, para facilitar la comunicación del hombre con el
computador, ha de centrarse en el desarrollo de lenguajes de mayor nivel.
El fabricante de un procesador fija los bloques de bits que llevarán a la CPU
(unidad central de procesos) a reconocer y realizar diferentes operaciones. Este
el el "código de máquina", primer lenguaje que la máquina puede interpretar y
transformar en acciones. Pero es evidentemente muy difícil de usar para un ser
humano. Supongamos que quiera hacer imprimir y para ello deba decir "10011101
11100010": ¿cómo recordar órdenes de este tipo y no equivocarse al escribirlas?
Prácticamente nadie trabaja hoy a este nivel, excepto los diseñadores de "chips"
procesadores. Del mismo modo que es posible pasar de un sistema binario a un
sistema decimal (más comprensible y más desarrollado en términos de signos
legibles) es posible asociar a los bloques de bits no solo valores decimales
sino también otros signos. Esto lleva a un segundo nivel de expresión.
Hemos de recordar que el teclado equivale a un conjunto de interruptores: cada
tecla que pulsamos equivale a cerrar brevemente uno de éstos, es decir
produciendo un BIT de información (no teclear = 0, teclear =1). Pero dado que
hay muchas teclas, hay que identificar cada una, por lo cual pulsar una tecla
significa activar un circuito que generará un "bloque" binario (byte) específico
que identifica esa tecla.
A cada tecla está asociado un código decimal y un código hexadecimal. El
hexadecimal (16 caracteres: de 0 a 9 y de la A a la F) es el que sirve de
intermediario a la máquina, para traducir nuestro código natural (alfanumérico)
al código binario. Algunos ejemplos de equivalentes decimales y hexadecimales
del teclado son los siguientes:
Tecla |
Valor Decimal |
Valor Hexadecimal |
1 |
49 |
31 |
2 |
50 |
32 |
9 |
57 |
39 |
a |
97 |
61 |
b |
98 |
62 |
i |
105 |
69 |
j |
106 |
6A |
k |
107 |
6B |
o |
111 |
6F |
p |
112 |
70 |
z |
122 |
7A |
! |
33 |
21 |
? |
63 |
3F |
= |
61 |
3D |
Esto permite no sólo reconocer los signos del teclado y reproducirlos (como en un procesador de palabras), sino también atribuir a cada tecla otra función y hacer -por ejemplo- que transmita una orden a la CPU. Así, podríamos ordenar la impresión con un simple "?", o -como es común hoy en los Macintosh- apretando simultáneamente una tecla de "comando" y la letra "P".
2º Nivel
La creación de un lenguaje más comprensible por el hombre consiste por lo tanto
en establecer la equivalencia de bloques binarios con signos de nuestro lenguaje
habitual. Para permitir la programación (secuencia de comandos), se usan
pequeños conjuntos de signos ("palabras") de fácil memorización, con las cuales
se redactan programas, por ejemplo "ADC" significará "sumar con reserva" (en
inglés: "ADd with Carry"). Este tipo de lenguaje se llama "ensamblador" o "Assembler"
(Vea más adelante la "Descripción" de Lenguajes). La máquina misma hará la tarea
de traducirlo en código binario, para seguir las instrucciones, gracias a otro
programa cuya función es traducir la expresión humana en "lenguaje de máquina"
(binario). Es programa se llama "compilador".
Aunque el Assembler es un inmenso progreso en relación al código binario, su
desventaja reside en que permanece estrechamente ligado a los bloques binarios
que reconoce la CPU (es decir al "hardware"). Para facilitar más la tarea, se
han inventado lenguajes de "alto nivel", es decir más cercanos al modo de
expresar del hombre que de operación de la máquina. Los primeros y más comunes
son los llamados de "tercera generación", más fáciles de manejar y más
independientes de las características técnicas de los procesadores. Ahora, hasta
un aficionado puede llegar a redactar un programa, sin tener que preocuparse por
el código binario o de ensamble: si un programa traductor podía resolver la
transformación de bloques de signos en bloques binarios, era cosa de extender
las habilidades del traductor para "enseñar" a la máquina cómo "entender" un
lenguaje más complejo y agregar mecanismos automáticos de manejo de la memoria
para poder utilizar lenguajes aún más comprensibles.
3er Nivel
El avance en el desarrollo de "compiladores" e "intérpretes" (los dos tipos de
programas traductores) ha sido por lo tanto fundamental en el desarrollo de los
lenguajes de "3º generación" cuyas ventajas además de la facilidad de
aprendizaje y lectura/escritura son las facilidades de corrección,
transformación y conversión de un lenguaje a otro.
Los más antiguos son el FORTRAN (para aplicaciones matemáticas y científicas) y
el COBOL (para aplicaciones de administración y contabilidad).
Con los micro-computadores nació el BASIC ("para principiantes"). Mucha
importancia tiene el PASCAL, especialmente en la docencia. (Más detalles luego,
en la "Descripción" de los lenguajes).
Generadores de
aplicaciones o 4º Nivel
Posteriormente, usando estos lenguajes, se han redactado programas destinados a
facilitar un número variado de operaciones en campos de aplicación específicos
como simulación de fenómenos físicos, manipulación de datos estadísticos, etc.
Los más avanzados y flexibles de estos programas son las planillas electrónicas
u hojas de cálculo y los programas de administración de archivos o bases de
datos (Vea el capítulo "Aplicaciones").
Dados que tales aplicaciones no "hacen nada" sin que el usuario defina ciertas
estructuras y ciertas operaciones, pueden ser consideradas como "generadores" de
aplicaciones, aunque este nombre se reserva habitualmente para niveles más
avanzados en que los usuarios pueden generar sistemas muy diferentes unos de
otros, con "herramientas" que se parecen a lenguajes de programación. Estas
herramientas conforman los lenguajes de cuarto nivel que son por esencia
"programas para crear programas" con una finalidad específica, como el "CASE"
destinado a facilitar el trabajo de los analistas de sistemas.
ESTRUCTURA DE UN LENGUAJE
Todo lenguaje, para permitir la programación, ha de contener diversos tipos de instrucciones:
Instrucciones simples:
Instrucciones compuestas:
En
este ejemplo, se introduce un concepto muy importante en el desarrollo y uso de
lenguajes de programación: la recursividad, factible por el hecho de que lo que
se manipula es siempre un valor colocado en alguna celda de memoria. Así, si
bien la matemática no puede aceptar una ecuación como N=N+1, aquí estamos ante
una instrucción (no una ecuación) que significa "tomar el valor que está en una
celda llamada N, sumarle 1 y volver a colocar el nuevo valor en la celda llamada
N. Ésta es una "instrucción de asignación".
Algunos lenguajes (el Algol y sus descendientes como Pascal y "C", y también
Lisp y Prolog en que este procedimiento constituye la esencia del lenguaje) van
aún más lejos y permiten una recursividad consistente en la posibilidad de que
una instrucción compuesta ordene la ejecución de sí misma. El siguiente podría
ser una breve ejemplo:
Para imprimir una lista con una instrucción recursiva, basta tener en cuenta que
una lista se compone de una cabeza (primer elemento) y una cola (el resto).
Obviamente la cola es también una lista, por lo cual se le puede aplicar el
procedimiento consistente en separar su cabeza de su cola, y así sucesivamente,
hasta encontrar una cola vacía. Por lo tanto, se puede ordenar algo así:
imprimir lista = imprimir cabeza
lista = cola [borra de la lista la cabeza ya impresa]
si lista no es vacía, imprimir lista [orden de recursión]
sino: fin.
PROGRAMACIÓN ESTRUCTURADA
Como explicado a
propósito de las "Instrucciones", todo programa se compone de una secuencia de
instrucciones que pueden ser simples o compuestas. La presencia de instrucciones
que deban repetirse muchas veces no sólo se expresa en las iteraciones, donde
dicha repetición es inmediata. Existen muchos casos en que la repetición no es
un flujo continuo sino dependiente de otras operaciones o condiciones que son
muy variables. Es el caso por ejemplo de las instrucciones para leer o grabar
datos en un disco.
La programación estructurada, que es una forma de redacción de programas
(obligatoria u optativa, según el lenguaje escogido), se hace cargo de este
requisito facilitando la constitución de "paquetes" de instrucciones (llamados "sub-rutinas"
o "procedimientos"), los que pueden escribirse una sola vez y ser "llamados"
(utilizados) las veces que se requiera.
Además, pone énfasis en la conveniencia de facilitar la lectura de los programas
haciendo más visible la dependencia jerárquica de las instrucciones compuestas
mediante "indentación", es decir modificando el ancho del margen izquierdo para
cada grupo de instrucciones.