Il semble qu'il y ait eu un changement progressif dans la réflexion sur l'utilisation des pointeurs dans les langages de programmation, de sorte qu'il est devenu généralement admis que les pointeurs étaient considérés comme risqués (sinon carrément "mal" ou agrandissement similaire).
Quelles ont été les évolutions historiques de ce changement de mentalité? Y a-t-il eu des événements, des recherches ou d'autres développements spécifiques et déterminants?
Par exemple, un regard superficiel sur la transition du C au C ++ vers Java semble montrer une tendance à compléter puis à remplacer entièrement les pointeurs par des références. Cependant, la véritable chaîne d'événements était probablement beaucoup plus subtile et complexe que cela, et pas aussi séquentielle. Les caractéristiques qui l'ont fait entrer dans ces langues traditionnelles peuvent avoir leur origine ailleurs, peut-être bien avant.
Remarque: je ne pose pas de questions sur les avantages réels des pointeurs par rapport aux références par rapport à autre chose. Je me concentre sur les raisons de ce changement apparent.
Réponses:
La justification était le développement d'alternatives aux pointeurs.
Sous le capot, tout pointeur / référence / etc est implémenté comme un entier contenant une adresse mémoire (aka pointer). Lorsque C est sorti, cette fonctionnalité a été exposée comme pointeurs. Cela signifiait que tout ce que le matériel sous-jacent pouvait faire pour adresser la mémoire pouvait être fait avec des pointeurs.
C'était toujours «dangereux», mais le danger est relatif. Lorsque vous créez un programme de 1 000 lignes ou que vous avez mis en place des procédures de qualité logicielle IBM, ce danger peut être facilement résolu. Cependant, tous les logiciels n'étaient pas développés de cette façon. En tant que tel, un désir de structures plus simples est apparu.
Si vous y réfléchissez, un
int&
et unint* const
ont vraiment le même niveau de sécurité, mais l'un a une syntaxe beaucoup plus agréable que l'autre.int&
pourrait également être plus efficace car il pourrait faire référence à un int stocké dans un registre (anachronisme: cela était vrai dans le passé, mais les compilateurs modernes sont si bons à optimiser que vous pouvez avoir un pointeur sur un entier dans un registre, tant que vous n'utilisez jamais l'une des fonctionnalités qui nécessiteraient une adresse réelle, comme++
)À mesure que nous passons à Java , nous évoluons vers des langages qui offrent certaines garanties de sécurité. C et C ++ n'en ont fourni aucun. Java garantit que seules les opérations légales sont exécutées. Pour ce faire, java a supprimé complètement les pointeurs. Ils ont découvert que la grande majorité des opérations de pointage / référence effectuées dans du code réel étaient des choses pour lesquelles les références étaient plus que suffisantes. Ce n'est que dans quelques cas (comme une itération rapide à travers un tableau) que les pointeurs étaient vraiment nécessaires. Dans ces cas, java prend un coup d'exécution pour éviter de les utiliser.
Le mouvement n'a pas été monotone. C # a réintroduit des pointeurs, bien que sous une forme très limitée. Ils sont marqués comme « dangereux », ce qui signifie qu'ils ne peuvent pas être utilisés par du code non approuvé. Ils ont également des règles explicites sur ce qu'ils peuvent et ne peuvent pas pointer vers (par exemple, il est tout simplement invalide d'incrémenter un pointeur au-delà de la fin d'un tableau). Cependant, ils ont constaté qu'il y avait une poignée de cas où la haute performance des pointeurs était nécessaire, alors ils les ont remis en place.
Les langages fonctionnels, qui n'ont aucun concept de ce type, sont également intéressants, mais c'est une discussion très différente.
la source
Une sorte d'indirection est nécessaire pour les programmes complexes (par exemple, les structures de données récursives ou de taille variable). Cependant, il n'est pas nécessaire d'implémenter cette indirection via des pointeurs.
La majorité des langages de programmation de haut niveau (c'est-à-dire pas Assembly) sont assez sûrs pour la mémoire et interdisent l'accès illimité au pointeur. La famille C est étrange ici.
C a évolué à partir de B qui était une abstraction très mince par rapport à l'assemblage brut. B avait un seul type: le mot. Le mot peut être utilisé comme entier ou comme pointeur. Ces deux sont équivalents lorsque toute la mémoire est considérée comme un seul tableau contigu. C a conservé cette approche plutôt flexible et a continué à prendre en charge l'arithmétique des pointeurs intrinsèquement dangereux. Le système de type entier de C est plus une réflexion après coup. Cette flexibilité d'accès à la mémoire a rendu C très approprié pour son objectif principal: le prototypage du système d'exploitation Unix. Bien sûr, Unix et C se sont avérés très populaires, de sorte que C est également utilisé dans des applications où cette approche de bas niveau de la mémoire n'est pas vraiment nécessaire.
Si nous regardons les langages de programmation qui ont précédé le C (par exemple les dialectes Fortran, Algol, y compris Pascal, Cobol, Lisp,…), certains d'entre eux prennent en charge les pointeurs de type C. Notamment, le concept de pointeur nul a été inventé pour Algol W en 1965. Mais aucun de ces langages n'a essayé d'être un langage de systèmes efficaces et à faible abstraction de type C: Fortran était destiné au calcul scientifique, Algol a développé des concepts assez avancés, Lisp était plus d'un projet de recherche que d'un langage de qualité industrielle, et Cobol s'est concentré sur les applications commerciales.
La collecte des ordures existait depuis la fin des années 50, c'est-à-dire bien avant C (début des années 70). GC nécessite une sécurité de la mémoire pour fonctionner correctement. Les langues avant et après C utilisaient GC comme fonctionnalité normale. Bien sûr, cela rend un langage beaucoup plus compliqué et peut-être plus lent, ce qui était particulièrement visible à l'époque des ordinateurs centraux. Les langages GC avaient tendance à être orientés vers la recherche (par exemple Lisp, Simula, ML) et / ou nécessitaient des postes de travail puissants (par exemple Smalltalk).
Avec des ordinateurs plus petits et plus puissants, l'informatique en général et les langages GC en particulier sont devenus plus populaires. Pour les applications en temps non réel (et parfois même alors), le GC est désormais l'approche préférée. Mais les algorithmes GC ont également fait l'objet d'intenses recherches. Comme alternative, une meilleure sécurité de la mémoire sans GC a également été développée, en particulier au cours des trois dernières décennies: les innovations notables sont RAII et les pointeurs intelligents en C ++ et le système de vie / vérificateur d'emprunt de Rust.
Java n'a pas innové en étant un langage de programmation à mémoire sûre: il a essentiellement pris la sémantique du langage Smalltalk GCed et à mémoire sûre et les a combinées avec la syntaxe et le typage statique de C ++. Il a ensuite été commercialisé comme un C / C ++ meilleur et plus simple. Mais ce n'est que superficiellement un descendant C ++. Le manque de pointeurs de Java est dû beaucoup plus au modèle objet Smalltalk qu'au rejet du modèle de données C ++.
Les langages "modernes" comme Java, Ruby et C # ne doivent pas être interprétés comme surmontant les problèmes des pointeurs bruts comme en C, mais doivent être considérés comme tirant leur origine de nombreuses traditions - y compris C, mais aussi de langages plus sûrs comme Smalltalk, Simula, ou Lisp.
la source
D'après mon expérience, les pointeurs ont TOUJOURS été un concept difficile pour de nombreuses personnes. En 1970, l'université où je fréquentais avait un Burroughs B5500, et nous avons utilisé Extended Algol pour nos projets de programmation. L'architecture matérielle était basée sur des descripteurs et certains codes dans la partie supérieure des mots de données. Ceux-ci ont été explicitement conçus pour permettre aux tableaux d'utiliser des pointeurs sans être autorisés à quitter la fin.
Nous avons eu des discussions animées en classe sur la référence nom / valeur et le fonctionnement des baies B5500. Certains d'entre nous ont obtenu l'explication immédiatement. D'autres non.
Plus tard, ce fut un peu un choc que le matériel ne me protège pas des pointeurs incontrôlables - en particulier dans le langage d'assemblage. Lors de mon premier emploi après l'obtention du diplôme, j'ai aidé à résoudre des problèmes dans un système d'exploitation. Souvent, la seule documentation que nous avions était le vidage sur incident imprimé. J'ai développé un talent pour trouver la source de pointeurs fugitifs dans les vidages de mémoire, donc tout le monde m'a donné les vidages "impossibles" à comprendre. Plus de problèmes que nous avons rencontrés sont dus à des erreurs de pointeur qu'à tout autre type d'erreur.
Beaucoup de gens avec qui j'ai travaillé ont commencé à écrire FORTRAN, puis sont passés à C, ont écrit C qui ressemblait beaucoup à FORTRAN et ont évité les pointeurs. Parce qu'ils n'ont jamais internalisé les pointeurs et les références, Java pose des problèmes. Souvent, il est difficile pour les programmeurs FORTRAN de comprendre comment fonctionne vraiment l'attribution d'objets.
Les langues modernes ont rendu beaucoup plus facile de faire des choses qui ont besoin de pointeurs "sous le capot" tout en nous protégeant des fautes de frappe et autres erreurs.
la source