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.
Respuestas:
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 aint* const
realmente 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.
fuente
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.
fuente
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.
fuente