Articulo de referencia

Comparación de Pascal y C

Los lenguajes de programación C y Pascal tienen orígenes, influencias y propósitos similares. Ambos se utilizaron para diseñar (y compilar) sus propios compiladores en sus inici...

Los lenguajes de programación C y Pascal tienen orígenes, influencias y propósitos similares. Ambos se utilizaron para diseñar (y compilar) sus propios compiladores en sus inicios. La definición original de Pascal apareció en 1969 y el primer compilador en 1970. La primera versión de C apareció en 1972.

Ambos son descendientes de la serie de lenguajes ALGOL . ALGOL introdujo el soporte de lenguaje de programación para la programación estructurada , donde los programas se construyen con estructuras de entrada y salida únicas como if , while , for y case . Pascal proviene directamente de ALGOL W , aunque compartió algunas ideas nuevas con ALGOL 68. El lenguaje C está relacionado de forma más indirecta con ALGOL, originalmente a través de B , BCPL y CPL , y posteriormente a través de ALGOL 68 (por ejemplo, en el caso de structy union) y también Pascal (por ejemplo, en el caso de enumeraciones, consty typedefbooleanos). Algunos dialectos de Pascal también incorporaron rasgos de C.

Los lenguajes documentados aquí son Pascal, diseñado por Niklaus Wirth y estandarizado como ISO 7185 en 1982, y C, diseñado por Dennis Ritchie y estandarizado como C89 en 1989. La razón es que ambas versiones representan la versión madura del lenguaje y también porque son relativamente cercanas en el tiempo. Las características de ANSI C y C99 (los estándares C posteriores), así como las características de implementaciones posteriores de Pascal ( Turbo Pascal , Free Pascal , etc.), no se incluyen en la comparación, a pesar de las mejoras en robustez y funcionalidad que aportaron (por ejemplo, Comparación de Pascal y Delphi).

Sintaxis

Sintácticamente , Pascal se parece mucho más a ALGOL que C. Se conservan las palabras clave en inglés donde C usa símbolos de puntuación: Pascal tiene and, or, y moddonde C usa &&, ||, y %por ejemplo. Sin embargo, C se parece más a ALGOL que Pascal en lo que respecta a las declaraciones (simples), conservando la sintaxis tipo-nombre -variable-nombre . Por ejemplo, C puede aceptar declaraciones al inicio de cualquier bloque, no solo en el bloque externo de una función.

Uso del punto y coma

Otra diferencia, más sutil, es el papel del punto y coma . En Pascal, los puntos y coma separan las instrucciones individuales dentro de una instrucción compuesta; en cambio, en C, finalizan la instrucción. En C, también forman parte sintácticamente de la instrucción (transformando una expresión en una instrucción). Esta diferencia se manifiesta principalmente en dos situaciones:

  • En Pascal, un punto y coma nunca puede estar directamente antes de else, mientras que en C, es obligatorio, a menos que se utilice una instrucción de bloque.
  • La última instrucción antes de una " endo" untilno requiere ir seguida de un punto y coma.

Se puede colocar un punto y coma superfluo en la última línea antes de end , insertando así formalmente una instrucción vacía .

Comentarios

En la primera edición de C, solo hay /* block comments */. C añadió posteriormente // line comments.

En Pascal, hay y .{ block comments }(* block comments *)

Identificadores y palabras clave

C y Pascal difieren en su interpretación de mayúsculas y minúsculas. C distingue entre mayúsculas y minúsculas, mientras que Pascal no; por lo tanto, MyLabely mylabelson nombres distintos en C, pero idénticos en Pascal. En ambos lenguajes, los identificadores constan de letras y dígitos, con la regla de que el primer carácter no puede ser un dígito. En C, el guion bajo cuenta como una letra, por lo que incluso _abc es un nombre válido. Los nombres con un guion bajo inicial se utilizan a menudo para diferenciar identificadores especiales del sistema en C.

Tanto C como Pascal utilizan palabras clave (palabras reservadas para el lenguaje). Algunos ejemplos son `if` , ` while` , `const` , `for` y `goto` , palabras clave comunes a ambos lenguajes. En C, los nombres de los tipos básicos integrados también son palabras clave (por ejemplo, ` int` , `char` ) o combinaciones de palabras clave (por ejemplo, `unsigned char` ), mientras que en Pascal los nombres de los tipos integrados son identificadores normales predefinidos.

Definiciones, declaraciones y bloques

En Pascal, las definiciones de subrutinas comienzan con las palabras clave procedure (no devuelve ningún valor) o function (devuelve un valor) y las definiciones de tipo con type . En C, todas las subrutinas tienen functiondefiniciones (los procedimientos son void function) y las definiciones de tipo usan la palabra clave typedef. Ambos lenguajes usan una combinación de palabras clave y signos de puntuación para las definiciones de tipos complejos; por ejemplo, los arreglos se definen con la palabra clave array en Pascal y con signos de puntuación en C, mientras que las enumeraciones se definen con la palabra clave enumen C pero con signos de puntuación en Pascal.

En las subrutinas de Pascal, `begin` y `end` delimitan un bloque de instrucciones precedido por declaraciones locales, mientras que las funciones de C usan `{" y `}` para delimitar un bloque de instrucciones opcionalmente precedido por declaraciones  . C (antes de C99) define estrictamente que cualquier declaración debe aparecer antes de las instrucciones dentro de un bloque particular, pero permite que los bloques aparezcan dentro de otros bloques, lo cual es una forma de sortear esta restricción. Mediante la sintaxis del cuerpo de una subrutina, Pascal impone que las declaraciones aparezcan antes de las instrucciones. Pascal también permite que las definiciones de tipos y funciones —no solo las declaraciones de variables— se encapsulen mediante definiciones de funciones a cualquier nivel de profundidad.  

Implementación

Las gramáticas de ambos lenguajes tienen un tamaño similar. Desde una perspectiva de implementación, la principal diferencia entre ambos lenguajes radica en que para analizar C es necesario tener acceso a una tabla de símbolos para los tipos, mientras que en Pascal solo existe una construcción de este tipo: la asignación. Por ejemplo, el fragmento de C X * Y;podría ser una declaración de Yque es un objeto cuyo tipo es un puntero a X, o una expresión de instrucción que multiplica Xy Y. En cambio, los fragmentos correspondientes de Pascal y son inherentemente inequívocos; el análisis correcto no requiere una tabla de símbolos.var Y : ^X;Z := X * Y;

Tipos simples

Números enteros

Pascal requiere que todas las declaraciones de variables y funciones especifiquen su tipo explícitamente. En C tradicional, el nombre del tipo puede omitirse en la mayoría de los contextos y el tipo predeterminado int(que corresponde a integeren Pascal) se asume implícitamente (sin embargo, tales valores predeterminados se consideran una mala práctica en C y a menudo se señalan con advertencias).

C admite diferentes tamaños y modos con y sin signo para enteros mediante el uso de modificadores como long, short, signed, unsigned, etc. El significado exacto del tipo de entero resultante depende de la máquina, lo que se puede garantizar es que long intno es más corto que inty intno es más corto que short int. Sin embargo, en el estándar de C, se especifican al menos tamaños mínimos de tipos que garantizan charque sea un solo byte y intque sea al menos dos bytes.

Subrangos

En Pascal, se logra un resultado similar declarando un subrango de enteros (un compilador puede entonces optar por asignar una cantidad menor de almacenamiento para la variable declarada):

tipo a = 1 .. 100 ; b = - 20 .. 20 ; c = 0 .. 100000 ;

Esta característica de subrango no es compatible con C.

Una diferencia importante, aunque sutil, entre C y Pascal radica en cómo promueven las operaciones con enteros. En Pascal, el resultado de una operación está definido para todos los tipos enteros/subrangos, incluso si los resultados intermedios no caben en un entero. El resultado es indefinido solo si no cabe en el entero/subrango del lado izquierdo de la asignación. Esto puede implicar una restricción artificial en el rango de tipos enteros o puede requerir una ejecución lenta para manejar los resultados intermedios. Sin embargo, el compilador puede aprovechar los subrangos restringidos para generar código más eficiente.

En C, los operandos deben ser promovidos al tamaño del resultado requerido: los resultados intermedios no están definidos si no caben dentro del rango de los operandos promovidos. Si el rango del resultado requerido es mayor que el rango de los operandos, esto normalmente produce código lento e ineficiente, incluso con un buen compilador optimizador. Sin embargo, un compilador de C nunca está obligado ni se espera que maneje resultados intermedios fuera de rango: es responsabilidad del programador asegurarse de que todos los resultados intermedios se ajusten al rango de los operandos.

Las implementaciones de C anteriores al estándar, así como Small-C y otras, permitían que los tipos de enteros y punteros se mezclaran con relativa libertad.

Tipos de caracteres

En C, el tipo de carácter es charque es un tipo de entero que no es más largo que short int, . Por lo tanto, expresiones como 'x' + 1son perfectamente válidas, al igual que declaraciones como int i = 'i';y char c = 74;.

Esta naturaleza entera de char(un byte) se ilustra claramente mediante declaraciones como

unsigned char uc = 255 ; // límite común signed char sc = -128 ; // límite negativo común

Si el chartipo debe considerarse como signedo unsignedpor defecto depende de la implementación.

En Pascal, los caracteres y los enteros son tipos distintos. Las funciones integradas del compilador ord()se chr()pueden usar para convertir caracteres individuales al valor entero correspondiente del conjunto de caracteres en uso, y viceversa. Por ejemplo, en sistemas que usan el conjunto de caracteres ASCII ord('1') = 49, chr(9)es un carácter de tabulación.

Tipos booleanos

En Pascal, boolean es un tipo enumerado. Los posibles valores de boolean son false y true , con un valor ordinal de false  =  0 y true  =  1. Para la conversión a entero , se utiliza ord :

i := ord ( b ) ;

No existe una función estándar para convertir un entero a un booleano ; sin embargo, la conversión es sencilla en la práctica:

b := i <> 0 ;

C tiene un tipo booleanobool , , desde C23 , pero no lo tenía antes. C usa operadores relacionales de valor binario (<, >, ==,  !=, <=, >=) que pueden considerarse booleanos en el sentido de que siempre dan como resultado cero o uno. Como todas las pruebas (&&, ||,  ?:, if , while , etc.) se realizan mediante comprobaciones de cero, falso se representa con cero, mientras que verdadero se representa con cualquier otro valor. Esto es visible en el booltipo de dato numérico definido en <stdbool.h>(anterior a C23).

Operaciones bit a bit

C permite utilizar operadores bit a bit para realizar operaciones booleanas. Es necesario tener cuidado, ya que la semántica cambia cuando los operandos utilizan más de un bit para representar un valor.

Pascal tiene otro método más abstracto y de alto nivel para trabajar con datos bit a bit: los conjuntos . Los conjuntos permiten al programador establecer, borrar, intersecar y unir valores de datos bit a bit, en lugar de usar operadores bit a bit directos. Ejemplo:

Pascal:

Estado := Estado + [ StickyFlag ] ; ​​Estado := Estado - [ StickyFlag ] ; ​​si ( StickyFlag en Estado ) entonces ...

DO:

Estado |= StickyFlag ; Estado &= ~ StickyFlag ; si ( Estado & StickyFlag ) { ...

Si bien las operaciones con bits en números enteros y las operaciones con conjuntos pueden considerarse similares si los conjuntos se implementan utilizando bits, no existe un paralelismo directo entre sus usos a menos que sea posible una conversión no estándar entre números enteros y conjuntos.

Una nota sobre la implementación

Durante la evaluación de expresiones, y en ambos lenguajes , un valor booleano puede almacenarse internamente como un solo bit, un solo byte, una palabra de máquina completa, una posición en el código generado o como un código de condición en un registro de estado, dependiendo de la máquina, el compilador y la situación; estos factores suelen ser más importantes que el lenguaje compilado.

Tipos de punto flotante

C tiene un modelo menos estricto de tipos de punto flotante que Pascal. En C, los enteros se pueden convertir implícitamente a números de punto flotante, y viceversa (aunque la posible pérdida de precisión puede indicarse mediante advertencias). En Pascal, los enteros se pueden convertir implícitamente a real, pero la conversión de reala integer(donde puede perderse información) debe hacerse explícitamente mediante las funciones trunc()y round(), que truncan o redondean la fracción, respectivamente.

Tipos de enumeración

Tanto C como Pascal incluyen tipos de enumeración. Un ejemplo en Pascal:

tipo color = ( rojo , verde , azul ) ; var a : color ;

Ejemplo de CA:

enum Color { ROJO , VERDE , AZUL };enum Color a = ROJO ;

Sin embargo, el comportamiento de los tipos en ambos lenguajes es muy diferente. En Pascal, las enumeraciones son ordinales y se analizan usando ord(), succ()y pred()las funciones y son distintas de la arrayestructura. En C, las enumeraciones se implementan como arreglos, y REDse convierte simplemente en un sinónimo de 0, GREENpara 1, BLUEpara 2, y nada impide que se asigne un valor fuera de este rango a la variable a. Además, operaciones como a = a + 1;están estrictamente prohibidas en Pascal; en su lugar, se usaría . En C, las enumeraciones se pueden convertir libremente a y desde enteros, pero en Pascal, se debe usar la función para convertir de tipos enumerados a enteros. Los enteros no se pueden convertir a tipos enumerados, pero los tipos enumerados se pueden usar en instrucciones que de otro modo usarían enteros:a := succ(a);ord()

para a := rojo a azul hacer

Tipos estructurados

Tipos de matrices

Tanto C como Pascal permiten arreglos de otros tipos complejos, incluyendo otros arreglos. Sin embargo, ahí terminan las similitudes entre ambos lenguajes. Los arreglos en C se definen simplemente por un tipo base y el número de elementos:

entero a [ TAMAÑO ];

y siempre están indexados desde 0hasta SIZE - 1(es decir, módulo SIZE).

En Pascal, el rango de índices a menudo se especifica mediante un subrango (como se introdujo en los tipos simples más arriba). Los diez elementos de

var a : array [ 0..9 ] de enteros ;

Se indexarían de 0 a 9 (al igual que en C en este caso). Los índices de los arrays pueden ser de cualquier tipo de dato ordinal , no solo rangos:

tipo TColor = ( rojo , verde , azul ) ; (* enumeración *) RGB = array [ TColor ] de 0 .. 255 ;var picture : array [ 1..640 , 1..480 ] de RGBpaleta var : matriz [ byte , 0 .. 2 ] de byte

Las cadenas que constan de n > 1 caracteres se definen como matrices compactas con un rango de 1 a n.

Matrices y punteros

En las expresiones de C, un identificador que representa un array se trata como un puntero constante al primer elemento del array; por lo tanto, dadas las declaraciones int a[10]y int *p;la asignación, p = aes válida y hace que p y a apunten al mismo array. Sin embargo, como el identificador arepresenta una dirección constantea = p , no es válido.

Si bien los arreglos en C son fijos, los punteros a ellos son intercambiables. Esta flexibilidad permite a C manipular arreglos de cualquier longitud con el mismo código. Sin embargo, también deja al programador la responsabilidad de no escribir fuera del arreglo asignado, ya que el lenguaje no incluye comprobaciones al respecto.

En Pascal, los arreglos son un tipo distinto de los punteros. Esto permite la comprobación de límites para arreglos desde la perspectiva del compilador. Prácticamente todos los compiladores de Pascal admiten la comprobación de rangos como opción de compilación . La capacidad de tener arreglos cuya longitud cambia en tiempo de ejecución y poder comprobarla bajo el control del lenguaje se denomina a menudo "arreglos dinámicos". En Pascal, el número de elementos en cada tipo de arreglo se determina en tiempo de compilación y no puede cambiarse durante la ejecución del programa. Por lo tanto, no es posible definir un arreglo cuya longitud dependa de alguna manera de los datos del programa.

C permite inicializar arreglos de longitud arbitraria. El sizeofoperador `size` se puede usar para obtener el tamaño de un arreglo inicializado estáticamente en código C. Por ejemplo, en el siguiente código, el índice de terminación del bucle se ajusta automáticamente si se modifica la lista de cadenas.

#include <stdio.h>static char * wordlist [] = { "imprimir" , "salir" , "el" , "texto" , "mensaje" }; static int listSize = sizeof ( wordlist ) / sizeof ( wordlist [ 0 ]);for ( int i = 0 ; i < listSize ; i ++ ) { printf ( wordlist [ i ]); }for ( int i = listSize - 1 ; i >= 0 ; i -- ) { printf ( wordlist [ i ]); }

Pascal no dispone de inicialización de arrays (excepto en el caso de cadenas) ni de un medio para determinar tamaños de arrays arbitrarios en tiempo de compilación. Una forma de implementar el ejemplo anterior en Pascal, pero sin el ajuste automático del tamaño, es:

const minlist = 1 ; maxlist = 5 ; maxword = 7 ;tipo listrange = minlist .. maxlist ; wordrange = 1 .. maxword ; word = record contents : packed array [ wordrange ] of char ; length : wordrange end ; wordlist = array [ listrange ] of word ; var i : integer ; words : wordlist ;procedure CreateList ( var w : wordlist ) ; begin w [ 1 ] . contents := 'print ' ; w [ 1 ] . length := 5 ; w [ 2 ] . contents := 'out ' ; w [ 2 ] . length := 3 ; w [ 3 ] . contents := 'the ' ; w [ 3 ] . length := 3 ; w [ 4 ] . contents := 'text ' ; w [ 4 ] . length := 4 ; w [ 5 ] . contents := 'message' ; w [ 5 ] . length := 7 ; end ;begin CreateList ( palabras ) ; para i := minlist hasta maxlist hacer con palabras [ i ] hacer WriteLn ( contenido : longitud ) ; para i := maxlist hasta minlist hacer con palabras [ i ] hacer WriteLn ( contenido : longitud ) fin .

Instrumentos de cuerda

En ambos idiomas, una cadena es una secuencia primitiva de caracteres.

En Pascal, un literal de cadena de longitud n es compatible con el tipo packed array [1..n] of char. En C, una cadena generalmente tiene el tipo char[n].

Pascal no admite matrices de longitud variable, por lo que cualquier conjunto de rutinas para realizar operaciones con cadenas depende de un tamaño de cadena específico. La extensión "conformant array parameter" de Pascal, ahora estandarizada, resuelve este problema en gran medida, y muchas, o incluso la mayoría, de las implementaciones de Pascal admiten cadenas de forma nativa en el lenguaje.

Las cadenas literales de C terminan en nulo ; es decir, un carácter nulo al final actúa como centinela de fin de cadena :

// la cadena s termina en nulo const char * s = "la lluvia en España" ;

La terminación nula debe mantenerse manualmente para las variables de cadena almacenadas en matrices (esto a menudo se gestiona parcialmente mediante rutinas de biblioteca).

C carece de funciones integradas para la asignación de cadenas o matrices, por lo que la cadena no se transfiere a p, sino que se hace que p apunte a la cadena constante en la memoria.

En Pascal, a diferencia de C, el primer carácter de la cadena se encuentra en el índice 1 y no en el 0 (lo que provoca que se anteponga la longitud ). Esto se debe a que Pascal almacena la longitud de la cadena en el elemento 0 del array de caracteres. Si no se comprende bien esta diferencia, pueden surgir errores al portar o intentar integrar código objeto generado por ambos lenguajes.

El desarrollador de FreeBSD, Poul-Henning Kamp , escribiendo en ACM Queue , se referiría más tarde a la victoria de las cadenas terminadas en nulo sobre las cadenas con prefijo de longitud como "el error de un byte más costoso" de la historia. [ 1 ]

Tipos de registro

Tanto C como Pascal pueden declarar tipos de " registro ". En C, se denominan "estructuras".

struct A { int b ; char c ; };
tipo a = registro b : entero ; c : carácter ; fin ;

En Pascal, podemos usar la sentencia " with name_of_record do" para usar directamente los campos de ese registro, como variables locales, en lugar de escribir name_of_record . name_of_field . Aquí hay un ejemplo:

tipo r = registro s : cadena ; c : carácter ; fin ; var r1 : r ; comenzar con r1 hacer comenzar s := 'foo' ; c := 'b' fin fin ;

No existe una característica equivalente a `with` en C.

En C, se puede especificar la longitud exacta en bits de un campo:

struct A { unsigned int b : 3 ; unsigned int c : 1 ; };

La cantidad de almacenamiento utilizado depende de las características (por ejemplo, la alineación de palabras) del sistema de destino.

Esta característica está disponible en Pascal mediante el uso de la construcción de subrango (3 bits dan un rango de 0 a 7) en asociación con la palabra clave packed :

tipo a = registro empaquetado b : 0 .. 7 ; c : 0 .. 1 ; fin ;

Tanto C como Pascal admiten registros que pueden incluir diferentes campos superpuestos:

unión A { int a ; float b ; };
tipo a = registro caso booleano de falso : ( a : entero ) ; verdadero : ( b : real ) fin ;

Ambos procesadores de lenguaje pueden asignar a estos registros solo el espacio necesario para contener el tipo más grande en la unión/registro. En Pascal, estas construcciones se denominan registros variantes , que no deben confundirse con el tipo de dato Variant definido en Free Pascal.

La mayor diferencia entre C y Pascal es que Pascal admite el uso explícito de un "tagfield" para que el procesador del lenguaje determine si se está accediendo al componente válido del registro de variante:

tipo a = registro caso q : booleano de falso : ( a : entero ) ; verdadero : ( b : real ) fin ;

En este caso, el campo de etiqueta q debe estar configurado en el estado correcto para acceder a las partes adecuadas del registro.

Consejos

En C, los punteros pueden apuntar a la mayoría de las entidades del programa, incluidos objetos o funciones:

int a ; int * b ; int ( * compare )( int c , int d ); int myCompare ( int c , int d ); b = &a a ; comparar = & miComparar ;

En C, dado que los arreglos y los punteros tienen una equivalencia cercana, lo siguiente es lo mismo:

a = b [ 5 ]; a = * ( b + 5 ); a = * ( 5 + b ); a = 5 [ b ]; // técnicamente legal, pero quizás inusual

Por lo tanto, en C, los punteros se utilizan a menudo simplemente como otro método para acceder a los arrays.

malloc()Para crear datos dinámicos, se free()utilizan las funciones de la biblioteca para obtener y liberar bloques de datos dinámicos. Por lo tanto, la asignación dinámica de memoria no está integrada en el procesador del lenguaje. Esto es especialmente valioso cuando se utiliza C en núcleos de sistemas operativos o sistemas embebidos, ya que estos son muy específicos de la plataforma (no solo de la arquitectura) y requerirían modificar el compilador de C para cada plataforma (o sistema operativo) en la que se utilice.

Pascal tiene el mismo tipo de punteros que C, a través del ^operador de referencia en lugar del operador *de C. Cada puntero está vinculado a un único elemento de datos dinámico y solo se puede mover mediante asignación:

tipo a = ^ entero ;var b , c : a ;nuevo ( b ) ; c := b ;

En Pascal, los punteros son de tipo seguro; es decir, un puntero a un tipo de dato solo puede asignarse a un puntero del mismo tipo. Además, los punteros nunca pueden asignarse a variables que no sean punteros. La aritmética de punteros (una fuente común de errores de programación en C, especialmente cuando se combina con problemas de orden de bytes y tamaños de tipo independientes de la plataforma) no está permitida en Pascal. Todas estas restricciones reducen la posibilidad de errores relacionados con punteros en Pascal en comparación con C, pero no evitan por completo las referencias a punteros no válidas. Por ejemplo, se producirá un error en tiempo de ejecución si se hace referencia a un puntero antes de que se haya inicializado o después de que se haya liberado.

Expresiones

Niveles de precedencia

Los idiomas difieren significativamente en lo que respecta a la evaluación de las expresiones, pero en general son comparables.

Pascal

  1. Negación lógica:not
  2. Multiplicativo:* / div mod and
  3. Aditivo:+ - or
  4. Relacional:= <> < > <= >= in

do

  1. sufijo unario:[] () . -> ++ --
  2. Prefijo unario:& * + - ! ~ ++ -- (type) sizeof
  3. Multiplicativo:* / %
  4. Aditivo:+ -
  5. Cambio:<< >>
  6. Relacional:< > <= >=
  7. Igualdad:== !=
  8. AND bit a bit:&
  9. XOR bit a bit:^
  10. OR bit a bit:|
  11. Lógico y:&&
  12. O lógico:||
  13. Condicional:? :
  14. Asignación:= += -= *= /= %= <<= >>= &= ^= |=
  15. operador coma :,

Mecanografía

En Pascal, la mayoría de los operadores cumplen diversas funciones; por ejemplo, el signo menos puede usarse para la negación, la resta o la diferencia de conjuntos (dependiendo del tipo y el contexto sintáctico), el >=operador puede usarse para comparar números, cadenas o conjuntos, etc. C utiliza símbolos de operadores específicos en mayor medida.

Pruebas de asignación e igualdad

Los dos lenguajes utilizan operadores diferentes para la asignación. Pascal, al igual que ALGOL , utiliza el operador de igualdad matemática =para la prueba de igualdad y el símbolo :=para la asignación, mientras que C, al igual que B , utiliza el operador de igualdad matemática para la asignación. En C (y B) se eligió el ==símbolo de FORTRAN para la prueba de igualdad.

Es un error común en C, ya sea por inexperiencia o por un simple error de escritura, colocar accidentalmente expresiones de asignación en sentencias condicionales como . El código entre llaves siempre se ejecutará porque la expresión de asignación tiene el valor 10, que es distinto de cero y, por lo tanto, se considera "verdadero" en C; esto se debe en parte a que C (y ALGOL) permiten la asignación múltiple en la forma que no es compatible con Pascal. Tenga en cuenta también que ahora tiene el valor , lo que puede afectar al código siguiente. Los compiladores de C recientes intentan detectar estos casos y advertir al usuario, solicitando una sintaxis menos ambigua como .if(a=10){...}a = 10a = b = c = 10;a10if((a=10)!=0){...}

Este tipo de error no puede ocurrir en Pascal, ya que las asignaciones no son expresiones y no tienen un valor: usar el operador incorrecto provocará un error de compilación inequívoco, y también es menos probable que alguien confunda el :=símbolo con una prueba de igualdad.

Es notable que la expresión condicional de ALGOL en la forma tenga un equivalente en C (el operador ternario de CPL ) pero no en Pascal, que utilizará .z:=ifa>bthenaelseb;ifa>bthenz:=aelsez:=b;

Problemas de implementación

Cuando Niklaus Wirth diseñó Pascal, el deseo era limitar el número de niveles de precedencia (menos rutinas de análisis sintáctico, después de todo). Por lo tanto, los operadores OR y OR exclusivo se tratan como un Addop y se procesan al nivel de una expresión matemática. De manera similar, el AND se trata como un Mulop y se procesa con Term. Los niveles de precedencia son

Nótese que solo hay UN conjunto de reglas de sintaxis, que se aplica a ambos tipos de operadores. Según esta gramática, entonces, expresiones como

 x + (y Y NO z) / 3 

son perfectamente legales. Y, de hecho, lo son, al menos desde la perspectiva del analizador sintáctico. Pascal no permite la mezcla de variables aritméticas y booleanas, y este tipo de situaciones se detectan a nivel semántico, al generar el código correspondiente, en lugar de a nivel sintáctico.

Los autores de C adoptaron un enfoque diametralmente opuesto: tratan los operadores como distintos y, de hecho, en C existen no menos de 15 niveles. Esto se debe a que C también cuenta con los operadores '=', '+=' y sus variantes, '<<', '>>', '++', '--', etc. Si bien en C los operadores aritméticos y booleanos se tratan por separado, las variables no: se puede realizar una prueba booleana sobre cualquier valor entero.

Conectores lógicos

En Pascal, una expresión booleana que depende de un orden de evaluación específico (posiblemente a través de efectos secundarios en las llamadas a funciones) se considera, en general, un error. El compilador de Pascal tiene la libertad de usar el orden que prefiera y siempre debe evaluar la expresión completa, incluso si el resultado puede determinarse mediante una evaluación parcial.

En C, la dependencia del orden de evaluación booleana es perfectamente válida y a menudo se emplea sistemáticamente utilizando los operadores &&y ||junto con operadores como ++, +=, el operador coma, etc. Los operadores &&y ||funcionan así como combinaciones de operadores lógicos y sentencias condicionales .

La evaluación de expresiones de cortocircuito se ha considerado comúnmente una ventaja para C debido al "problema de evaluación":

var i : entero ; a : matriz empaquetada [ 1 .. 10 ] de caracteres ; ... i := 1 ; mientras ( i <= 10 ) y ( a [ i ] <> 'x' ) hacer i := i + 1 ; ...

Esta búsqueda, aparentemente sencilla, resulta problemática en Pascal porque el acceso al array a[i] no sería válido para i igual a 11. Existen varias maneras de evitar este problema. El siguiente ejemplo introduce una variable booleana que indica si se ha encontrado o no el carácter buscado:

const strlen = 10 ; var i : integer ; a : packed array [ 1..strlen ] of char ; found : boolean ; ... i : = 1 ; found := false ; while not found and ( i <= strlen ) do if ( a [ i ] = 'x' ) then found := true else i : = i + 1 ; ...

En su lugar, se utiliza una instrucción de repetición:

const strlen = 10 ; var i : entero ; a : matriz empaquetada [ 1..strlen ] de caracteres ; encontrado : booleano ; ... i := 1 ; repetir encontrado := a [ i ] = 'x' ; si no se encuentra, entonces incrementar ( i ) ; hasta que se encuentre o ( i > strlen ) ; ...

Estructuras de control

Las declaraciones relativas a las estructuras de control de edificios son similares, pero no equivalentes.

El valor final de id, la variable de control, queda sin definir al finalizar la instrucción for en Pascal.

Pascal no tiene un equivalente a `default` . Los programadores de Pascal deben proteger las sentencias `case` con una expresión como: ` if expr not in [A..B] then default-case` . Como máximo, solo se ejecutará una de las sentencias `case`.

C tiene las llamadas instrucciones de salida anticipada break y continue . No son obligatorias y, si se omiten, pueden provocar que se ejecute más de una instrucción case.

Tanto C como Pascal cuentan con la instrucción goto . Sin embargo, dado que Pascal admite procedimientos/funciones anidados, es posible realizar saltos desde un procedimiento o función interna a la que lo contiene; esto se utilizaba comúnmente para implementar la recuperación de errores. C ofrece esta capacidad mediante las instrucciones setjmp y longjmp del estándar ANSI C. Si bien esto es equivalente, podría considerarse menos seguro, ya que almacena información específica del programa, como direcciones de salto y marcos de pila, en una estructura accesible para el programador.

Funciones y procedimientos

Las rutinas de Pascal que devuelven un valor se denominan funciones; las rutinas que no devuelven un valor se denominan procedimientos. Todas las rutinas en C se denominan funciones; las funciones de C que no devuelven un valor se declaran con un tipo de retorno void .

Los procedimientos de Pascal se consideran equivalentes a las funciones "void" de C, y las funciones de Pascal son equivalentes a las funciones de C que devuelven un valor.

Las dos declaraciones siguientes en C:

int f ( int x , int y ); void k ( int q );

son equivalentes a las siguientes declaraciones en Pascal:

función f ( x , y : entero ) : entero ; procedimiento k ( q : entero ) ;

Las funciones de Pascal solo se pueden usar dentro de una expresión, mientras que una función de C que devuelve un valor se puede llamar de la misma manera que una función "void" y su valor de retorno se puede ignorar.

Pascal tiene dos tipos diferentes de parámetros: paso por valor y paso por referencia (VAR). En ambos casos, se utiliza el nombre de la variable al realizar la llamada (no es necesario el operador de dirección).

función f ( z : entero ; var k : entero ) : entero ; // la función acepta dos enteros, uno por valor, otro por referencia Begin z := 1 ; // la variable externa u no se modificará, pero el valor local se modifica en el ámbito de la función k := 1 ; // la variable externa t se modificará porque se pasó por referencia // hasta aquí, z existe y es igual a 1 End ;x := f ( u , t ) ; // Las variables u y t se pasan a la llamada: el valor de u y la referencia a t

En C, todos los parámetros se pasan por valor, pero el paso por referencia se puede simular usando punteros. El siguiente segmento es similar al segmento de Pascal anterior:

// La función acepta un entero (por valor) y un puntero a entero (también por valor) como parámetro. int f ( int z , int * k ) { z = 1 ; // Igual que en Pascal, el valor local se modifica, pero la variable externa u no se modifica. * k = 1 ; // La variable a la que hace referencia k (por ejemplo, t) se modificará. // Hasta aquí, z existe y es igual a 1. }x = f ( u , & t ); // el valor de u y la dirección (valor de) de la variable t se pasan a la llamada

Una de las diferencias más importantes entre C y Pascal radica en la forma en que manejan los parámetros en la pila durante una llamada a subrutina  : esto se conoce como convención de llamada  . En Pascal, los parámetros se colocan en la pila de izquierda a derecha, mientras que la convención de llamada STDCALL de C los coloca de derecha a izquierda.

La llamada a un procedimiento al estilo Pascal se realiza con  :

  • El llamador inserta los parámetros en la pila de izquierda a derecha (lo opuesto a __cdecl ).
  • llamando a la función
  • La pila es limpiada por la función llamada.
; ejemplo de llamada al estilo Pascal. ; NOTA: __stdcall insertaría los argumentos en orden inverso. insertar arg1 insertar arg2 insertar arg3 llamar a la función ; no hay limpieza de pila al regresar: la función llamada lo hizo

La ventaja de usar PASCAL call en lugar de STDCALL es que el código es ligeramente más pequeño, aunque el impacto en el tamaño solo es visible en programas grandes, y que la recursión funciona más rápido.

C permite que las funciones acepten un número variable de parámetros, conocidas como funciones variádicas , utilizando un mecanismo torpe de va_list ap;, va_start(ap, count);, con disponibilidad de tipos limitada (ejemplo : nada para ).va_arg(ap, type); bool

int f ( int a , ...);f ( 1 , 2 , 3 , 4 , 5 );

La función f()utiliza un conjunto especial de funciones ( varargs) que le permiten acceder a cada uno de los parámetros por turno.

Pascal y C también tienen algunas funciones de E/S variádicas, por ejemplo WriteLn()y printf().

Pascal permite anidar procedimientos y funciones . Esto resulta útil para permitir variables locales a un grupo de procedimientos, pero no globales. C carece de esta característica, y la localización de variables o funciones solo se puede realizar para un módulo de compilación donde dichas variables o funciones se hayan declarado como estáticas .

C permite invocar funciones indirectamente a través de un puntero a función . En el siguiente ejemplo, la instrucción (*cmpar)(s1, s2)es equivalente a strcmp(s1, s2):

#include <string.h>int ( * cmpar )( const char * a , const char * b ); const char * s1 = "hola" ; const char * s2 = "mundo" ;cmpar = & strcmp ; b = ( * cmpar )( s1 , s2 );

En Pascal, las funciones y los procedimientos se pueden pasar como parámetros a otras funciones o procedimientos:

procedimiento ShowHex ( i : entero ) ; ... fin ;procedimiento ShowInt ( i : entero ) ; ... fin ;procedimiento Demostración ( procedimiento Mostrar ( i : entero )) ; varj : entero ;comenzar Mostrar ( j ) terminar ;... Demo ( ShowHex ) ; Demo ( ShowInt ) ; ...

Preprocesador

Las primeras versiones de C no tenían declaraciones de constantes ni de tipos, y el lenguaje C se definió originalmente como un lenguaje que requería un " preprocesador "; un programa independiente, con su correspondiente paso, que gestionaba las definiciones de constantes, inclusiones y macros para minimizar el uso de memoria. Posteriormente, con ANSI C, se incorporaron las definiciones de constantes y tipos, y el preprocesador también se integró al lenguaje, dando lugar a la sintaxis que conocemos hoy en día.

En Pascal, las definiciones de constantes y tipos están integradas y no requieren un preprocesador. Si bien algunos programadores utilizaban un preprocesador con Pascal (a veces el mismo que con C), no era tan común como con C. Aunque a menudo se señala como una "carencia" de Pascal, técnicamente C tampoco cuenta con modularidad de programas ni macros integradas. Sin embargo, dispone de un sencillo sistema de compilación independiente de bajo nivel (que tradicionalmente utiliza el mismo enlazador genérico que el lenguaje ensamblador), mientras que Pascal no.

Escapes de tipo

En C, el programador puede inspeccionar la representación a nivel de bytes de cualquier objeto apuntándole un charpuntero:

int a ; char * p = ( char * )( &a a ); char c = * p ; // primer byte de un

Es posible hacer algo similar en Pascal utilizando un registro de variante no discriminada:

var a : integer ; b : real ; a2c : record case boolean of false : ( a : integer ) ; true : ( b : real ) ; end ; end ; begin a2c . b := b ; a := a2c . a ; end ;

Aunque la conversión de tipos es posible en la mayoría de los compiladores e intérpretes de Pascal, incluso en el código anterior, ninguna estandarización de Pascal exige que a2c.a y a2c.b compartan el mismo espacio de direcciones. Niklaus Wirth, el diseñador de Pascal, ha escrito sobre la naturaleza problemática de intentar realizar escapes de tipo utilizando este enfoque:

La mayoría de los desarrolladores de Pascal decidieron que esta verificación sería demasiado costosa, aumentando el código y disminuyendo la eficiencia del programa. En consecuencia, el registro de variantes se convirtió en una característica predilecta para vulnerar el sistema de tipos por parte de todos los programadores amantes de los trucos, que generalmente se convierten en trampas y desastres.

Actualmente, varios lenguajes excluyen específicamente este tipo de escapes de tipos, por ejemplo Java, C# y el propio Oberon de Wirth .

Archivos

En C, los archivos no existen como un tipo integrado (se definen en un archivo de cabecera del sistema) y toda la entrada/salida se realiza mediante llamadas a bibliotecas. Pascal incorpora la gestión de archivos en el lenguaje.

Las instrucciones típicas que se utilizan para realizar operaciones de entrada/salida en cada lenguaje son:

Cª:

printf ( "La suma es: %d \n " , x );

En Pascal:

escribir ( 'La suma es: ' , x ) ;

La principal diferencia radica en que C utiliza una "cadena de formato" que se interpreta para encontrar los argumentos de la función printf y convertirlos, mientras que Pascal realiza esta operación bajo el control del procesador de lenguaje. El método de Pascal es probablemente más rápido, ya que no requiere interpretación, pero el método de C es altamente extensible.

Implementaciones y extensiones posteriores de Pascal

Algunas implementaciones populares de Pascal han incorporado prácticamente todas las construcciones de C en Pascal. Los ejemplos incluyen conversiones de tipo, [ 2 ] la capacidad de obtener la dirección de cualquier variable, local o global, y diferentes tipos de enteros con propiedades de promoción especiales.

Sin embargo, la incorporación de la actitud permisiva de C hacia los tipos y las conversiones de tipos puede resultar en un Pascal que pierda parte o la totalidad de su seguridad de tipos. Por ejemplo, Java y C# se crearon en parte para abordar algunos de los problemas de seguridad de tipos percibidos en C, y cuentan con punteros "administrados" que no pueden usarse para crear referencias inválidas. En su forma original (tal como la describió Niklaus Wirth ), Pascal califica como un lenguaje de punteros administrados, unos 30 años antes que Java o C#. Sin embargo, un Pascal fusionado con C perdería esa protección por definición. En general, la menor dependencia de los punteros para las tareas básicas lo hace más seguro que C en la práctica.

El estándar Extended Pascal amplía Pascal para admitir muchas funcionalidades compatibles con C, que el estándar original de Pascal no ofrecía, de una manera más segura en cuanto a tipos. Por ejemplo, los tipos de esquema admiten (entre otros usos) matrices de longitud variable, manteniendo la seguridad de tipos que exige que la dimensión de la matriz se incluya en la matriz, lo que permite comprobaciones automáticas en tiempo de ejecución para detectar índices fuera de rango, incluso para matrices de tamaño dinámico.

Véase también

Notas

  1. Kamp, Poul-Henning (25 de julio de 2011), "El error de un byte más costoso", ACM Queue , 9 (7): 40– 43, doi : 10.1145/2001562.2010365 , ISSN 1542-7730 , S2CID 30282393  
  2. "Typecast - Lazarus wiki" . wiki.freepascal.org . Consultado el 18 de mayo de 2024 .

Lecturas adicionales

  • Kathleen Jensen y Niklaus Wirth: PASCAL - Manual de usuario e informe. Springer-Verlag, 1974, 1985, 1991, ISBN 3-540-97649-3https://web.archive.org/web/20050314152247/http://www.cs.inf.ethz.ch/~wirth/books/Pascal/ ]
  • Brian Kernighan , Dennis Ritchie : El lenguaje de programación C. También conocido como K&R: el libro original sobre C.
    • 1.ª edición, Prentice Hall, 1978; ISBN 0-13-110163-3. Pre-ANSI C.
    • 2.ª edición, Prentice Hall, 1988; ISBN 0-13-110362-8. ANSI C.
  • Niklaus Wirth: Comentario sobre una nota sobre arreglos dinámicos en PASCAL 37-38, ACM SIGPLAN Notices, Volumen 11, Número 1, enero de 1976.
  • Niklaus Wirth: Recuerdos sobre el desarrollo de Pascal. 333-342, ACM SIGPLAN Notices, Volumen 28, Número 3, marzo de 1993.
  • ISO/IEC 9899. La norma oficial C:1999, junto con informes de defectos y su justificación.
  • Análisis detallado de la conversión de C a Pascal
  • Alan R. Feuer, Narain H. Gehani: Comparación de los lenguajes de programación C y Pascal 73-92, ACM Computing Surveys, Volumen 14, Número 1, marzo de 1982.
  • Comparación y evaluación de lenguajes de programación: Ada, C y Pascal , editado por Alan R. Feuer y Narain Gehani, Prentice Hall, 1984. ISBN 0-13-154840-9
  • Scott Meyers: Effective C++ , 2.ª ed., Addison-Wesley, 1998, ISBN 0-201-92488-9
  • Vincent Hayward: Anatomía comparada de los lenguajes de programación Pascal y C 50-60, ACM SIGPLAN Notices, Volumen 21, Número 5, mayo de 1986.
  • Pascal para usuarios de C en la Wiki del compilador FreePascal
  • Brian Kernighan: Por qué Pascal no es mi lenguaje de programación favorito
  • Andrew Koenig: Trampas y escollos de la categoría C