¿El lenguaje de programación C se consideró un lenguaje de bajo nivel cuando salió?

151

Actualmente C se considera un lenguaje de bajo nivel , pero en los años 70 ¿se consideraba de bajo nivel? ¿Estaba el término incluso en uso entonces?

Muchos lenguajes populares de alto nivel no existieron hasta mediados de los 80 y más allá, así que tengo curiosidad por saber cómo y cómo la naturaleza del bajo nivel ha cambiado a lo largo de los años.

joeyfb
fuente
13
Como un punto de datos, alrededor de 1978, algunos mentores de programación me describieron C como un lenguaje ensamblador glorificado.
Ben Crowell
77
@BenCrowell No estoy seguro de lo que significa "glorificado" en el contexto de su declaración, pero he experimentado llamar a C un lenguaje ensamblador universal (= independiente de la plataforma) .
Melebius el
13
Curiosamente, hay que argumentar que C no es un lenguaje de bajo nivel , y ahora es menos uno que en los años 70, porque la máquina abstracta de C está más alejada del hardware moderno que del PDP-11.
Tom
55
Al pensar en esto, es posible que también desee pensar en el origen: Unix está escrito en C, c se ejecuta en Unix y compila de forma cruzada Unix en otras plataformas. Todo lo que no era necesario para compilar Unix era una sobrecarga innecesaria, y el objetivo principal de C era facilitar la escritura / puerto de un compilador como fuera posible. Para este propósito, era el nivel correcto EXACTO, así que no pienso en C como nivel alto o bajo, lo considero como una herramienta para portar Unix que, como casi todas las herramientas de Unix, es extremadamente adaptable a muchos problemas.
Bill K
44
Vale la pena señalar que lisp fue inventado en 1958
Prime

Respuestas:

143

Para responder a los aspectos históricos de la pregunta:

La filosofía de diseño se explica en El lenguaje de programación C escrito por Brian Kernighan y el diseñador C Dennis Ritchie, el "K&R" del que quizás haya oído hablar. El prefacio de la primera edición dice

C no es un lenguaje de "nivel muy alto", ni uno "grande" ...

y la introducción dice

C es un lenguaje relativamente de "bajo nivel" ... C no proporciona operaciones para tratar directamente con objetos compuestos como cadenas de caracteres, conjuntos, listas o matrices. No hay operaciones que manipulen una matriz o cadena completa ...

La lista continúa por un tiempo antes de que el texto continúe:

Aunque la ausencia de algunas de estas características puede parecer una grave deficiencia, ... mantener el lenguaje en un tamaño modesto tiene beneficios reales.

(Solo tengo la segunda edición de 1988, pero el comentario a continuación indica que el texto citado es el mismo en la primera edición de 1978).

Entonces, sí, los términos "alto nivel" y "bajo nivel" estaban en uso en ese entonces, pero C fue diseñado para caer en algún lugar del espectro intermedio. Era posible escribir código en C que fuera portátil a través de plataformas de hardware, y ese era el criterio principal para determinar si un lenguaje se consideraba de alto nivel en ese momento. Sin embargo, C carecía de algunas características que eran características de los lenguajes de alto nivel, y esta fue una decisión de diseño a favor de la simplicidad.

Gatkin
fuente
42
Esta es una excelente respuesta! Evidencia histórica verificable + testimonio de un actor mejor informado del significado de bajo y alto nivel en el período al que se refiere el OP. Por cierto, confirmo que las citas ya estaban en la edición de 1978.
Christophe
157

Esto depende de su definición de lenguaje de alto y bajo nivel. Cuando se desarrolló C, todo lo que era de nivel superior al ensamblado se consideraba un lenguaje de alto nivel. Esa es una barra baja para despejar. Más tarde, esta terminología cambió al punto de que algunos hoy en día considerarían incluso a Java como un lenguaje de bajo nivel.

Incluso dentro del panorama lingüístico de alto nivel de los años 70, vale la pena señalar que C tiene un nivel bastante bajo. El lenguaje C es básicamente B más un sistema de tipo simple, y B no es mucho más que una conveniente capa de sintaxis procesal / estructurada para el ensamblaje. Debido a que el sistema de tipos es un ajuste posterior en la parte superior del lenguaje B sin tipo, aún puede omitir anotaciones de tipo en algunos lugares y intse supondrá.

C omite conscientemente funciones costosas o difíciles de implementar que ya estaban bien establecidas en ese momento, como

  • gestión automática de memoria
  • funciones anidadas o cierres
  • POO básica o corutinas
  • sistemas de tipos más expresivos (por ejemplo, tipos de rango restringido, tipos definidos por el usuario, como tipos de registro, tipeo fuerte, ...)

C tiene algunas características interesantes:

  • soporte para la recursividad (como consecuencia de sus variables automáticas basadas en pila, en comparación con los idiomas donde todas las variables tienen una vida útil global)
  • punteros de función
  • Los tipos de datos definidos por el usuario (estructuras y uniones) se implementaron poco después del lanzamiento inicial de C.
  • La representación de cadena de C (puntero a caracteres) es en realidad una gran mejora sobre B que codificó varias letras en una sola palabra de máquina.
  • Los archivos de encabezado de C fueron un truco de eficiencia para mantener pequeñas las unidades de compilación, pero también proporcionan un sistema de módulo simple.
  • Punteros sin restricciones de estilo ensamblador y aritmética de punteros, en comparación con referencias más seguras. Los punteros son una característica inherentemente insegura pero también muy útil para la programación de bajo nivel.

En el momento en que se desarrolló C, otros lenguajes innovadores como COBOL, Lisp, ALGOL (en varios dialectos), PL / I, SNOBOL, Simula y Pascal ya se habían publicado y / o se usaban ampliamente para dominios con problemas específicos. Pero la mayoría de esos idiomas existentes estaban destinados a la programación de mainframe, o eran proyectos de investigación académica. Por ejemplo, cuando ALGOL-60 se diseñó por primera vez como un lenguaje de programación universal, la tecnología y la informática necesarias para implementarlo aún no existían. Algunos de estos (algunos dialectos ALGOL, PL / I, Pascal) también estaban destinados a la programación de bajo nivel, pero tendían a tener compiladores más complejos o eran demasiado seguros (por ejemplo, sin punteros sin restricciones). Pascal carece notablemente de un buen soporte para matrices de longitud variable.

En comparación con esos lenguajes, C rechaza las características "elegantes" y caras para ser más práctico para el desarrollo de bajo nivel. C nunca fue principalmente un proyecto de investigación de diseño de lenguaje. En cambio, fue una rama del desarrollo de kernel de Unix en el minicomputador PDP-11 que estaba relativamente limitado en recursos. Por su nicho (un lenguaje minimalista de bajo nivel para escribir Unix con un compilador de un solo paso que es fácil de portar) C se destacó por completo, y más de 45 años después sigue siendo la lengua franca de la programación de sistemas.

amon
fuente
17
Solo una pequeña adición para las personas que desconocen lo que era necesario para la familia ALGOL existente y los idiomas de Pascal: esos idiomas tenían funciones léxicamente anidadas donde se podía acceder a la variable (local) declarada en las funciones externas. Eso significaba que tenía que mantener una "pantalla", una matriz de punteros a ámbitos léxicos externos, en cada llamada de función y retorno (que cambiaba el nivel léxico), o tenía que encadenar ámbitos léxicos en la pila y cada acceso variable requirió múltiples saltos indirectos en la pila para encontrarlo. ¡Costoso! C desechó todo eso. Aún lo extraño.
davidbak 01 de
12
@davidbak: El x86-64 System V ABI (también conocido como la convención de llamadas en Linux / OS X) se define %r10como el "puntero de cadena estática", que es exactamente de lo que estás hablando. Para C es solo otro registro de scratch con capa de llamada, pero supongo que Pascal lo usaría. (Las funciones anidadas de GNU C lo usan para pasar un puntero al ámbito externo cuando dicha función no está en línea (por ejemplo, si hace un puntero de función para que el compilador cree un trampolín de código de máquina en la pila): Aceptabilidad de regular uso de r10 y r11 )
Peter Cordes
2
@PeterCordes ¡Fascinante! Pascal todavía se usaba ampliamente cuando salió el Sistema V (aunque no sé cuándo se definió el SysV ABI formal). Su respuesta vinculada es muy informativa.
davidbak 01 de
3
C tiene tipos definidos por el usuario (struct / union). Sospecho que el resto de esas "características" quedaron fuera, porque son de uso cero o negativo (a menos que esté ingresando a un concurso de código ofuscado :-)), ya que restan el objetivo de mantener el lenguaje simple y expresivo. .
jamesqf
66
@jamesqf: pero muy temprano C no tenía asignación de estructura; Supongo que tiene que memcpy o copiar los miembros individualmente en lugar de escribir a = b;para copiar una estructura completa de la manera que puede en ISO C89. Entonces, a principios de C, los tipos definidos por el usuario eran definitivamente de segunda clase y solo podían pasarse por referencia como argumentos de función. La aversión de C a las matrices y también ¿Por qué C ++ admite la asignación de matrices dentro de las estructuras por parte de los miembros, pero generalmente no?
Peter Cordes
37

A principios de la década de 1970, C era un soplo de aire fresco deslumbrante que usaba construcciones modernas de manera tan efectiva que todo el sistema UNIX podía reescribirse desde lenguaje ensamblador a C con espacio insignificante o penalización de rendimiento. En ese momento, muchos contemporáneos se referían a él como un lenguaje de alto nivel.

Los autores de C, principalmente Dennis Ritchie, fueron más circunspectos y en el artículo de Bell System Technical Journal dijeron que "C no es un lenguaje de muy alto nivel". Con una sonrisa irónica y con la intención de ser provocativo, Dennis Ritchie diría que era un lenguaje de bajo nivel. El principal de sus objetivos de diseño para C era mantener el lenguaje cerca de la máquina y proporcionar portabilidad, es decir, independencia de la máquina.

Para más información consulte el artículo original de BSTJ:

Gracias Dennis Que descanse en paz.

Bud Wonsiewicz
fuente
3
Básicamente, fue mecanografiado y envoltorios de sintaxis más agradables para el ensamblaje de PDP si me preguntas.
einpoklum
2
@einpoklum Tiendo a estar de acuerdo con eso. Aprendí C alrededor de 1980 en la universidad, en un PDP-11 / 34a, y uno de los profesores lo describió como un "lenguaje ensamblador portátil". Probablemente porque además del PDP-11, teníamos varias máquinas Superbrain CP / M en el laboratorio que tenían compiladores C disponibles. en.wikipedia.org/wiki/Intertec_Superbrain
dgnuff
2
También una excelente respuesta basada en evidencia histórica verificable (¡guau! ¡Había una versión previa de K&R disponible por ahí!).
Christophe
77
@einpoklum ¿No es algo exagerado? Tuve la oportunidad de escribir código de sistema y aplicación a mediados de los 80 en ensamblador (Z80 y M68K), un "ensamblador portátil" llamado M (sintaxis PDP11 con un registro abstracto de 16 bits y un conjunto de instrucciones) y C. En comparación con el ensamblador , C es definitivamente un lenguaje de alto nivel: ¡la productividad de la programación en C fue un orden de magnitud mayor que en ensamblador! Por supuesto, sin cadenas (SNOBOL), sin soporte de archivos de datos nativos (COBOL), sin código de autogeneración (LISP), sin matemáticas avanzadas (APL), sin objetos (SIMULA), por lo que podemos estar de acuerdo en que no era muy alto
Christophe
21

Como escribí en otra parte de este sitio cuando alguien se refirió al patrón de administración de memoria libre / malloc como "programación de bajo nivel"

Es curioso cómo la definición de "bajo nivel" cambia con el tiempo. Cuando estaba aprendiendo a programar, cualquier lenguaje que proporcionara un modelo de montón estandarizado que hiciera posible un patrón simple de asignación / libre se consideró de alto nivel. En la programación de bajo nivel, tendría que realizar un seguimiento de la memoria usted mismo (¡no las asignaciones, sino las ubicaciones de memoria en sí mismas!), O escribir su propio asignador de montón si se siente realmente elegante.

Para el contexto, esto fue a principios de los 90, mucho después de que salió C.

Mason Wheeler
fuente
¿No era la biblioteca estándar solo una cosa desde la estandarización a finales de los 80 (bueno, basada en las API de Unix existentes)? Además, la programación del kernel (que era el dominio del problema original de C) naturalmente requiere cosas de bajo nivel como la gestión manual de la memoria. En ese momento, C debe haber sido el lenguaje de programación de más alto nivel en el que se escribió un kernel serio (creo que hoy en día el kernel NT también usa una buena cantidad de C ++).
amon
66
@hyde, usé Algol 60, dos dialectos FORTRAN diferentes y varios dialectos BASIC diferentes en la década de 1970, y ninguno de esos idiomas tenía punteros o un asignador de montón.
Solomon Slow
77
Estrictamente hablando, aún puede programar sin malloc()llamar directamente brk(2)o mmap(2)administrar la memoria resultante usted mismo. Es un PITA masivo sin ningún beneficio concebible (a menos que esté implementando algo similar a Malloc), pero puede hacerlo.
Kevin
1
@amon: a excepción de las extraordinarias máquinas basadas en pila de Burroughs que se programaron en ALGOL de abajo hacia arriba ... y mucho antes que Unix también. Ah, y por cierto, Multics, que fue la inspiración para Unix: escrito en PL / I. Similar a ALGOL, nivel más alto que C.
davidbak
66
En realidad, la razón por la que muchos de nosotros no estamos usando Multics hoy en día probablemente tenga más que ver con el hecho de que solo se ejecuta en mainframes terriblemente caros, es decir, el típico gasto extravagante de mainframe del día más el gasto adicional de medio gabinete de hardware especializado para implementar memoria virtual con seguridad. Cuando salieron minicomputadoras de 32 bits como el VAX-11, todos menos los bancos y el gobierno se separaron de IBM y los Siete Enanitos y llevaron su procesamiento a gran escala a computadoras "mini".
davidbak 01 de
15

Muchas respuestas ya se han referido a los primeros artículos que decían cosas como "C no es un lenguaje de alto nivel".

Sin embargo, no puedo resistirme a la acumulación: muchos, si no la mayoría o todos los HLL en ese momento, Algol, Algol-60, PL / 1, Pascal, proporcionaron la verificación de los límites de la matriz y la detección de desbordamiento numérico.

La última vez que verifiqué los desbordamientos de búfer y enteros fueron la causa raíz de muchas vulnerabilidades de seguridad. ... Sí, sigue siendo el caso ...

La situación para la administración dinámica de la memoria fue más complicada, pero aún así, el estilo C malloc / free fue un gran paso atrás en términos de seguridad.

Entonces, si su definición de HLL incluye "evita automáticamente muchos errores de bajo nivel", bueno, el lamentable estado de ciberseguridad sería muy diferente, probablemente mejor, si C y UNIX no hubieran sucedido.

Krazy Glew
fuente
77
Dado que he estado involucrado en la implementación de Intel MPX y el compilador de comprobación de puntero / límite que resultó si es así, puedo referirlo a los documentos sobre su rendimiento: esencialmente 5-15%. Gran parte de eso implica análisis de compiladores que apenas eran posibles en los años setenta y ochenta, en comparación con controles ingenuos que podrían ser un 50% más lentos. Sin embargo, creo que es justo decir que C y UNIX retrasaron el trabajo en dichos análisis en 20 años: cuando C se convirtió en el lenguaje de programación más popular, había mucha menos demanda de seguridad.
Krazy Glew
99
@jamesqf Además, muchas máquinas anteriores a C tenían soporte de hardware especial para la verificación de límites y desbordamientos de enteros. Como C no usó ese HW, eventualmente fue desaprobado y eliminado.
Krazy Glew
55
@jamesqf Por ejemplo: MIPS RISC ISA se basó originalmente en los puntos de referencia de Stanford, que se escribieron originalmente en Pascal, y luego se trasladó a C. Dado que Pascal comprobó el desbordamiento de enteros firmados, MIPS también lo hizo, en instrucciones como ADD. Alrededor de 2010 estaba trabajando en MIPS, mi jefe quería eliminar las instrucciones no utilizadas en MIPSr6, y los estudios mostraron que las verificaciones de desbordamiento casi nunca se usaban. Pero resultó que Javascript hizo tales comprobaciones, pero no pudo usar las instrucciones baratas debido a la falta de soporte del sistema operativo.
Krazy Glew
3
@KrazyGlew: es muy interesante que mencione esto, ya que en C / C ++ la lucha entre usuarios y escritores de compiladores por "comportamiento indefinido" debido al desbordamiento de enteros firmados se está calentando, ya que los escritores de compiladores de hoy han tomado el viejo mantra "Cometer un error programa peor es ningún pecado" y la giró hasta 11. Un montón de mensajes en stackOverflow y refleja otra parte esto ...
davidbak
2
Si Rust tuviera intrínsecos SIMD como C, podría ser un lenguaje ensamblador portátil moderno casi ideal. C está empeorando cada vez más como lenguaje ensamblador portátil debido a la agresiva optimización basada en UB y al fracaso para exponer de manera portátil nuevas operaciones primitivas que admiten las CPU modernas (como popcnt, recuento de ceros iniciales / finales, bit-reverse, byte-reverse, saturación matemáticas). Hacer que los compiladores de C hagan asm eficientes para estos en CPU con soporte HW a menudo requiere intrínsecos no portátiles. Hacer que el compilador emule popcnt en objetivos sin él es mejor que obtener un reconocimiento de idioma popcnt.
Peter Cordes
8

Considere los lenguajes más antiguos y mucho más antiguos que precedieron a C (1972):

Fortran - 1957 (nivel no mucho más alto que C)

Lisp - 1958

Cobol - 1959

Fortran IV - 1961 (nivel no mucho más alto que C)

PL / 1 - 1964

APL - 1966

Además de un lenguaje de nivel medio como RPG (1959), principalmente un lenguaje de programación para reemplazar los sistemas de grabación de unidades basados ​​en el tablero.

Desde esta perspectiva, C parecía un lenguaje de muy bajo nivel, solo un poco por encima de los macro ensambladores utilizados en mainframes en ese momento. En el caso de los mainframes de IBM, las macros ensambladoras se usaron para el acceso a la base de datos, como BDAM (método básico de acceso al disco), ya que las interfaces de la base de datos no se habían portado a Cobol (en ese momento), lo que resultó en un legado de una combinación de ensamblaje y Los programas de Cobol todavía están en uso hoy en mainframes de IBM.

rcgldr
fuente
2
Si desea enumerar idiomas más antiguos y superiores, no olvide LISP .
Deduplicador el
@Dupuplicator: lo agregué a la lista. Me estaba centrando en lo que se usaba en los mainframes de IBM, y no recuerdo que LISP fuera tan popular, pero APL también era un lenguaje de nicho para mainframes de IBM (a través de consolas de tiempo compartido) e IBM 1130. Similar a LISP, APL es uno de los los lenguajes de alto nivel más exclusivos, por ejemplo, miren el pequeño código que se necesita para crear el juego de la vida de Conway con una versión actual de APL: youtube.com/watch?v=a9xAKttWgP4 .
rcgldr
2
No consideraría RPG o COBOL como de alto nivel, al menos no antes de COBOL-85. Cuando rascas la superficie de COBOL, ves que es esencialmente una colección de macros ensambladoras muy avanzadas. Para empezar, carece de funciones, procedimientos y recursividad, así como de cualquier tipo de alcance. Todo el almacenamiento debe declararse en la parte superior del programa que conduzca a aperturas extremadamente largas o reutilización variable dolorosa.
idrougge
Tengo algunos malos recuerdos de usar Fortran IV, no recuerdo que sea apreciablemente un "nivel superior" que C.
DaveG
@idrougge: COBOL y RPG, y también los conjuntos de instrucciones de mainframe incluyeron soporte completo para BCD empaquetado o desempaquetado, un requisito para el software financiero en países como los EE. UU. Considero que los operadores nativos relacionados, como "mover correspondiente", son de alto nivel. El RPG era inusual en el sentido de que especificaba un enlace entre los campos de entrada sin procesar y los campos de salida formateados y / o acumuladores, pero no el orden de las operaciones, similar a la programación del plugboard que reemplazó.
rcgldr
6

La respuesta a su pregunta depende de qué lenguaje C está preguntando.

El lenguaje descrito en el Manual de referencia C de 1974 de Dennis Ritchie era un lenguaje de bajo nivel que ofrecía algunas de las ventajas de programación de los lenguajes de nivel superior. Los dialectos derivados de ese lenguaje también tienden a ser lenguajes de programación de bajo nivel.

Sin embargo, cuando se publicó el estándar C 1989/1990, no describía el lenguaje de bajo nivel que se había vuelto popular para programar máquinas reales, sino que describía un lenguaje de nivel superior que podría ser, pero no era obligatorio, -implementado en términos de nivel inferior.

Como señalan los autores de la Norma C, una de las cosas que hizo útil el lenguaje fue que muchas implementaciones podrían tratarse como ensambladores de alto nivel. Debido a que C también se usó como una alternativa a otros lenguajes de alto nivel, y debido a que muchas aplicaciones no requerían la capacidad de hacer cosas que los lenguajes de alto nivel no podían hacer, los autores del Estándar permitieron que las implementaciones se comportaran de manera arbitraria si los programas intentaron usar construcciones de bajo nivel. En consecuencia, el lenguaje descrito por el Estándar C nunca ha sido un lenguaje de programación de bajo nivel.

Para comprender esta distinción, considere cómo Ritchie's Language y C89 verían el fragmento de código:

struct foo { int x,y; float z; } *p;
...
p[3].y+=1;

en una plataforma donde "char" tiene 8 bits, "int" tiene 16 bits big-endian, "float" tiene 32 bits y las estructuras no tienen requisitos especiales de relleno o alineación, por lo que el tamaño de "struct foo" es de 8 bytes.

En el lenguaje de Ritchie, el comportamiento de la última declaración tomaría la dirección almacenada en "p", le agregaría 3 * 8 + 2 [es decir, 26] bytes y buscaría un valor de 16 bits de los bytes en esa dirección y la siguiente , agregue uno a ese valor y luego vuelva a escribir ese valor de 16 bits en los mismos dos bytes. El comportamiento se definiría como actuar sobre los bytes 26 y 27 que siguen al de la dirección p sin tener en cuenta qué tipo de objeto estaba almacenado allí.

En el lenguaje definido por el Estándar C, en el caso de que * p identifique un elemento de un "struct foo []" seguido de al menos tres elementos completos más de ese tipo, la última instrucción agregaría uno al miembro y de El tercer elemento después de * p. El comportamiento no sería definido por el Estándar bajo ninguna otra circunstancia.

El lenguaje de Ritchie era un lenguaje de programación de bajo nivel porque, si bien permitía a un programador usar abstracciones como matrices y estructuras cuando era conveniente, definía el comportamiento en términos del diseño subyacente de los objetos en la memoria. Por el contrario, el lenguaje descrito por C89 y estándares posteriores define las cosas en términos de una abstracción de nivel superior, y solo define el comportamiento del código que es consistente con eso. Las implementaciones de calidad adecuadas para la programación de bajo nivel se comportarán de manera útil en más casos que los exigidos por el Estándar, pero no existe un documento "oficial" que especifique qué debe hacer una implementación para ser adecuada para tales fines.

El lenguaje C inventado por Dennis Ritchie es, por lo tanto, un lenguaje de bajo nivel, y fue reconocido como tal. Sin embargo, el lenguaje inventado por el Comité de Normas C nunca ha sido un lenguaje de bajo nivel en ausencia de garantías proporcionadas por la implementación que vayan más allá de los mandatos de la Norma.

Super gato
fuente