¿Cuándo y por qué los punteros comenzaron a ser vistos como riesgosos?

18

Parece que ha habido un cambio gradual en el pensamiento sobre el uso de punteros en los lenguajes de programación, de modo que se acepta generalmente que los punteros se consideran riesgosos (si no directamente "malvados" o engrandecimientos similares).

¿Cuáles fueron los desarrollos históricos de este cambio de pensamiento? ¿Hubo eventos específicos, seminarios, investigaciones u otros desarrollos?

Por ejemplo, una mirada superficial a la transición de C a C ++ a Java parece mostrar una tendencia a complementar y luego reemplazar completamente los punteros con referencias. Sin embargo, la verdadera cadena de eventos fue probablemente mucho más sutil y compleja que esta, y no tan secuencial. Las características que llegaron a esos idiomas principales pueden haberse originado en otros lugares, quizás mucho antes.

Nota: No estoy preguntando sobre los méritos reales de los punteros vs. referencias vs. algo más. Mi enfoque está en los fundamentos de este aparente cambio.

DaveInCaz
fuente
1
Se debió al declive de la educación en artes liberales. La gente ya no podía comprender la Referencia indirecta, una de las ideas más fundamentales en tecnología informática, incluida en todas las CPU.
10
Los punteros son arriesgados. ¿Por qué crees que ha habido un cambio en el pensamiento? Ha habido mejoras en las características del lenguaje y el hardware que permiten escribir software sin punteros, aunque no sin alguna penalización de rendimiento.
Deja de dañar a Mónica el
44
@DaveInCaz Hasta donde yo sé, ese desarrollo específico fue la invención de punteros.
Deja de dañar a Mónica el
55
@nocomprende: lo que acabas de escribir no son hechos ni pruebas, solo opiniones. Hubo muchos menos programadores en 1970, no hay evidencia de que la población actual sea mejor o peor en la "referencia indirecta".
whatsisname
3
Los punteros siempre se han considerado riesgosos, desde el primer día. Fue simplemente un compromiso moverlos del lenguaje ensamblador a los de nivel superior.
Frank Hileman

Respuestas:

21

La razón fue el desarrollo de alternativas a los punteros.

Debajo del capó, cualquier puntero / referencia / etc. se está implementando como un entero que contiene una dirección de memoria (también conocido como puntero). Cuando salió C , esta funcionalidad se expuso como punteros. Esto significaba que cualquier cosa que el hardware subyacente pudiera hacer para direccionar la memoria podría hacerse con punteros.

Esto siempre fue "peligroso", pero el peligro es relativo. Cuando crea un programa de 1000 líneas, o cuando tiene implementados procedimientos de calidad de software de grado IBM, este peligro podría ser fácilmente abordado. Sin embargo, no todo el software se estaba desarrollando de esa manera. Como tal, surgió un deseo de estructuras más simples.

Si lo piensa, an int&y a int* constrealmente tienen el mismo nivel de seguridad, pero uno tiene una sintaxis mucho mejor que la otra. int&también podría ser más eficiente porque podría referirse a un int almacenado en un registro (anacronismo: esto era cierto en el pasado, pero los compiladores modernos son tan buenos en la optimización que puede tener un puntero a un entero en un registro, siempre que nunca usa ninguna de las funciones que requerirían una dirección real, como ++)

A medida que nos movemos a Java , nos movemos a idiomas que brindan algunas garantías de seguridad. C y C ++ no proporcionaron ninguno. Java garantiza que solo se ejecuten las operaciones legales. Para hacer esto, Java eliminó los punteros por completo. Lo que encontraron es que la gran mayoría de las operaciones de puntero / referencia realizadas en código real eran cosas para las que las referencias eran más que suficientes. Solo en un puñado de casos (como la iteración rápida a través de una matriz) se necesitaban realmente punteros. En esos casos, Java recibe un golpe de tiempo de ejecución para evitar su uso.

El movimiento no ha sido monótono. C # reintrodujo punteros, aunque en una forma muy limitada. Están marcados como " inseguros " , lo que significa que no pueden ser utilizados por código no confiable. También tienen reglas explícitas sobre lo que pueden y no pueden señalar (por ejemplo, simplemente no es válido para incrementar un puntero más allá del final de una matriz). Sin embargo, descubrieron que había un puñado de casos en los que se necesitaba el alto rendimiento de los punteros, por lo que los volvieron a colocar.

También serían de interés los lenguajes funcionales, que no tienen tal concepto en absoluto, pero esa es una discusión muy diferente.

Cort Ammon - Restablece a Monica
fuente
3
No estoy seguro de que sea correcto decir que Java no tiene punteros. No quiero entrar en un largo debate sobre qué es y qué no es un puntero, pero el JLS dice que "el valor de una referencia es un puntero". Simplemente no hay acceso directo o modificación de punteros permitidos. Esto no es solo por seguridad, ya que mantener a las personas fuera del negocio de realizar un seguimiento de dónde está un objeto en cualquier momento es útil para GC.
JimmyJames
66
@JimmyJames Cierto. Para los fines de esta respuesta, la línea divisoria entre el puntero y el no puntero era si admitía operaciones aritméticas de puntero, que normalmente no son compatibles con las referencias.
Cort Ammon - Restablece a Mónica
8
@JimmyJames Estoy de acuerdo con la afirmación de Cort de que un puntero es algo en lo que puedes hacer operaciones aritméticas, mientras que una referencia no lo es. El mecanismo real que implementa una referencia en lenguajes como Java es un detalle de implementación.
Robert Harvey
3
En general, C y C ++ habían aceptado voluntariamente la membresía en este club peligroso al permitir muchos "comportamientos indefinidos" en la especificación.
rwong
2
Por cierto, no son CPUs, que distinguen entre los punteros y números. Por ejemplo, la CPU CISC original de 48 bits en IBM AS / 400 hace eso. Y de hecho, hay una capa de abstracción debajo del sistema operativo, lo que significa que no sólo se distingue la CPU entre los números y los punteros y prohibir la aritmética de punteros, pero el sistema operativo en sí ni siquiera sabe acerca de los punteros en absoluto y tampoco lo hacen los idiomas . Curiosamente, esto hace que el sistema AS / 400 original sea único, donde reescribir el código desde un lenguaje de scripting de alto nivel en C hace que los órdenes de magnitud sean más lentos .
Jörg W Mittag
10

Es necesario algún tipo de indirección para programas complejos (por ejemplo, estructuras de datos recursivas o de tamaño variable). Sin embargo, no es necesario implementar esta indirección a través de punteros.

La mayoría de los lenguajes de programación de alto nivel (es decir, no ensamblados) son bastante seguros para la memoria y no permiten el acceso sin restricciones del puntero. La familia C es la extraña aquí.

C evolucionó de B, que era una abstracción muy delgada sobre el ensamblaje sin procesar. B tenía un solo tipo: la palabra. La palabra podría usarse como un entero o como un puntero. Esos dos son equivalentes cuando toda la memoria se ve como una única matriz contigua. C mantuvo este enfoque bastante flexible y continuó apoyando la aritmética de puntero inherentemente insegura. Todo el sistema de tipos de C es más una idea de último momento. Esta flexibilidad para acceder a la memoria hizo que C fuera muy adecuado para su propósito principal: crear prototipos del sistema operativo Unix. Por supuesto, Unix y C resultaron ser bastante populares, por lo que C también se usa en aplicaciones donde este enfoque de bajo nivel para la memoria no es realmente necesario.

Si observamos los lenguajes de programación anteriores a C (p. Ej., Dialectos de Fortran, Algol, incluidos Pascal, Cobol, Lisp, ...), algunos de ellos admiten punteros tipo C. Notablemente, el concepto de puntero nulo fue inventado para Algol W en 1965. Pero ninguno de esos lenguajes trató de ser un lenguaje de sistemas de baja abstracción eficiente tipo C: Fortran estaba destinado a la computación científica, Algol desarrolló algunos conceptos bastante avanzados, Lisp fue más de un proyecto de investigación que un lenguaje de grado industrial, y Cobol se centró en aplicaciones comerciales.

La recolección de basura existió desde finales de los años 50, es decir, mucho antes de C (principios de los 70). GC requiere seguridad de memoria para funcionar correctamente. Los lenguajes anteriores y posteriores a C utilizaron GC como característica normal. Por supuesto, eso hace que un lenguaje sea mucho más complicado y posiblemente más lento, lo que fue especialmente notable en la época de los mainframes. Los lenguajes GC tienden a estar orientados a la investigación (por ejemplo, Lisp, Simula, ML) y / o requieren estaciones de trabajo potentes (por ejemplo, Smalltalk).

Con computadoras más pequeñas y potentes, la informática en general y los lenguajes GC específicamente se hicieron más populares. Para aplicaciones que no son en tiempo real (y a veces incluso entonces) GC es ahora el enfoque preferido. Pero los algoritmos de GC también han sido objeto de una intensa investigación. Como alternativa, también se ha desarrollado aún más la seguridad de la memoria sin GC, especialmente en las últimas tres décadas: innovaciones notables son RAII y punteros inteligentes en C ++ y el sistema de verificación de vida / préstamo de Rust.

Java no innovó al ser un lenguaje de programación seguro para la memoria: básicamente tomó la semántica del lenguaje Smalltalk GCed, seguro para la memoria y los combinó con la sintaxis y la tipificación estática de C ++. Luego se comercializó como un C / C ++ mejor y más simple. Pero solo es superficialmente un descendiente de C ++. La falta de punteros de Java se debe mucho más al modelo de objetos Smalltalk que al rechazo del modelo de datos C ++.

Por lo tanto, los lenguajes "modernos" como Java, Ruby y C # no deben interpretarse como la superación de los problemas de los punteros en bruto como en C, sino que deben verse como dibujados de muchas tradiciones, incluido C, pero también de lenguajes más seguros como Smalltalk, Simula, o Lisp.

amon
fuente
4

En mi experiencia, los punteros SIEMPRE han sido un concepto desafiante para muchas personas. En 1970, la universidad a la que asistía tenía un Burroughs B5500, y usamos Extended Algol para nuestros proyectos de programación. La arquitectura del hardware se basó en descriptores y algunos códigos en la parte superior de las palabras de datos. Estos se diseñaron explícitamente para permitir que las matrices usen punteros sin que se les permita salir del final.

Tuvimos discusiones animadas en el aula sobre la referencia de nombre vs valor y cómo funcionaban las matrices B5500. Algunos de nosotros obtuvimos la explicación de inmediato. Otros no lo hicieron.

Más tarde, fue un poco sorprendente que el hardware no me protegiera de los punteros desbocados, especialmente en lenguaje ensamblador. En mi primer trabajo después de graduarme, ayudé a solucionar problemas en un sistema operativo. A menudo, la única documentación que teníamos era el volcado de memoria impreso. Desarrollé un don para encontrar la fuente de punteros fuera de control en los volcados de memoria, por lo que todos me dieron los volcados "imposibles" para que me diera cuenta. La mayoría de los problemas que tuvimos fueron causados ​​por errores de puntero que por cualquier otro tipo de error.

Muchas de las personas con las que he trabajado comenzaron a escribir FORTRAN, luego se mudaron a C, escribieron C que se parecía mucho a FORTRAN y evitaron los punteros. Como nunca internalizaron punteros y referencias, Java plantea problemas. A menudo, es difícil comprender para los programadores de FORTRAN cómo funciona realmente la asignación de objetos.

Los lenguajes modernos han hecho que sea mucho más fácil hacer cosas que necesitan punteros "bajo el capó" mientras nos mantienen a salvo de errores tipográficos y otros errores.

Valerie Griffin
fuente