Es scheint, dass sich das Denken über die Verwendung von Zeigern in Programmiersprachen allmählich verschoben hat, so dass allgemein anerkannt wurde, dass Zeiger als riskant eingestuft wurden (wenn nicht geradezu "böse" oder ähnliche Erschwernisse).
Was waren die historischen Entwicklungen für diese Denkveränderung? Gab es spezielle, wegweisende Ereignisse, Forschungen oder andere Entwicklungen?
Beispielsweise scheint ein oberflächlicher Rückblick auf den Übergang von C zu C ++ zu Java einen Trend aufzuzeigen, Zeiger zu ergänzen und dann vollständig durch Verweise zu ersetzen. Die reale Kette von Ereignissen war jedoch wahrscheinlich viel subtiler und komplexer als diese und bei weitem nicht so sequentiell. Die Merkmale, die es in diese Mainstream-Sprachen geschafft haben, könnten anderswo entstanden sein, vielleicht schon lange zuvor.
Hinweis: Ich frage nicht nach den tatsächlichen Vorzügen von Zeigern gegenüber Referenzen gegenüber etwas anderem. Mein Fokus liegt auf den Gründen für diese offensichtliche Verschiebung.
Antworten:
Die Begründung war die Entwicklung von Alternativen zu Zeigern.
Unter der Haube wird jeder Zeiger / Verweis / etc als Ganzzahl implementiert, die eine Speicheradresse (auch als Zeiger bezeichnet) enthält. Als C herauskam, wurde diese Funktionalität als Zeiger verfügbar gemacht. Dies bedeutete, dass alles, was die zugrunde liegende Hardware zur Adressierung des Speichers tun konnte, mit Zeigern durchgeführt werden konnte.
Das war immer "gefährlich", aber die Gefahr ist relativ. Wenn Sie ein Programm mit 1000 Zeilen erstellen oder über Softwarequalitätsverfahren von IBM verfügen, kann diese Gefahr leicht behoben werden. Es wurde jedoch nicht die gesamte Software auf diese Weise entwickelt. Als solches entstand der Wunsch nach einfacheren Strukturen.
Wenn Sie darüber nachdenken, haben ein
int&
und einint* const
wirklich das gleiche Maß an Sicherheit, aber eine hat eine viel schönere Syntax als die andere.int&
Es könnte auch effizienter sein, weil es sich auf ein in einem Register gespeichertes int beziehen könnte (Anachronismus: Dies war in der Vergangenheit der Fall, aber moderne Compiler können so gut optimieren, dass Sie einen Zeiger auf eine Ganzzahl in einem Register haben können, solange Sie niemals eine der Funktionen nutzen, die eine tatsächliche Adresse erfordern würde, wie++
)Während wir auf Java umsteigen, wechseln wir in Sprachen, die einige Sicherheitsgarantien bieten. C und C ++ lieferten keine. Java garantiert, dass nur legale Operationen ausgeführt werden. Um dies zu tun, hat Java die Zeiger komplett entfernt. Was sie fanden, ist, dass die überwiegende Mehrheit der in realem Code ausgeführten Zeiger- / Referenzoperationen Dinge waren, für die Referenzen mehr als ausreichend waren. Nur in wenigen Fällen (z. B. schnelle Iteration durch ein Array) wurden Zeiger wirklich benötigt. In diesen Fällen benötigt Java einen Runtime-Treffer, um die Verwendung zu vermeiden.
Die Bewegung war nicht monoton. C # hat Zeiger wieder eingeführt, allerdings in sehr eingeschränkter Form. Sie sind als " unsicher " gekennzeichnet , was bedeutet, dass sie nicht von nicht vertrauenswürdigem Code verwendet werden können. Sie haben auch explizite Regeln, auf was sie verweisen dürfen und nicht (zum Beispiel ist es einfach ungültig , einen Zeiger über das Ende eines Arrays hinaus zu erhöhen). Sie stellten jedoch fest, dass es eine Handvoll Fälle gab, in denen die hohe Leistung von Zeigern erforderlich war, und legten sie wieder ein.
Interessant wären auch die funktionalen Sprachen, die überhaupt kein solches Konzept haben, aber das ist eine ganz andere Diskussion.
quelle
Für komplexe Programme ist eine Art Indirektion erforderlich (z. B. rekursive oder variabel große Datenstrukturen). Es ist jedoch nicht erforderlich, diese Indirektion über Zeiger zu implementieren.
Die meisten höheren Programmiersprachen (dh nicht Assembly) sind relativ speichersicher und erlauben keinen uneingeschränkten Zeigerzugriff. Die C-Familie ist hier die seltsame.
C ist aus B hervorgegangen, was eine sehr dünne Abstraktion über die Rohmontage war. B hatte einen einzigen Typ: das Wort. Das Wort kann als Ganzzahl oder als Zeiger verwendet werden. Diese beiden sind äquivalent, wenn der gesamte Speicher als einzelnes zusammenhängendes Array betrachtet wird. C behielt diesen ziemlich flexiblen Ansatz bei und unterstützte weiterhin inhärent unsichere Zeigerarithmetik. Das ganze Typensystem von C ist eher ein nachträglicher Gedanke. Diese Flexibilität beim Speicherzugriff machte C für seinen Hauptzweck sehr geeignet: das Prototyping des Unix-Betriebssystems. Natürlich erwiesen sich Unix und C als recht beliebt, so dass C auch in Anwendungen verwendet wird, in denen dieser einfache Ansatz zum Speichern nicht wirklich benötigt wird.
Wenn wir uns die Programmiersprachen vor C ansehen (z. B. Fortran, Algol-Dialekte, einschließlich Pascal, Cobol, Lisp usw.), unterstützen einige von ihnen C-ähnliche Zeiger. Insbesondere wurde das Null-Zeiger-Konzept 1965 für Algol W erfunden. Keine dieser Sprachen versuchte jedoch, eine C-ähnliche, effiziente Sprache für Systeme mit niedriger Abstraktion zu sein: Fortran war für wissenschaftliches Rechnen gedacht, Algol entwickelte einige recht fortgeschrittene Konzepte, Lisp war es Cobol war eher ein Forschungsprojekt als eine Sprache für die Industrie und konzentrierte sich auf Geschäftsanwendungen.
Garbage Collection gab es seit den späten 50er Jahren, also weit vor C (Anfang der 70er Jahre). GC erfordert Speichersicherheit, um ordnungsgemäß zu funktionieren. Sprachen vor und nach C verwendeten GC als normale Funktion. Das macht natürlich eine Sprache viel komplizierter und möglicherweise langsamer, was sich besonders in der Zeit der Mainframes bemerkbar machte. GC-Sprachen waren in der Regel forschungsorientiert (z. B. Lisp, Simula, ML) und / oder erfordern leistungsfähige Workstations (z. B. Smalltalk).
Mit kleineren, leistungsstärkeren Computern im Allgemeinen und mit GC-Sprachen im Besonderen wurde das immer beliebter. Für Nicht-Echtzeit-Anwendungen (und manchmal sogar dann) ist GC jetzt der bevorzugte Ansatz. GC-Algorithmen waren aber auch Gegenstand intensiver Forschung. Als Alternative wurde insbesondere in den letzten drei Jahrzehnten auch eine bessere Speichersicherheit ohne GC weiterentwickelt: Bemerkenswerte Neuerungen sind RAII und Smart Pointer in C ++ sowie Rusts Lifetime System / Borrow Checker.
Java war keine speichersichere Programmiersprache, sondern übernahm im Grunde die Semantik der speichersicheren GCed-Sprache Smalltalk und kombinierte sie mit der Syntax und statischen Typisierung von C ++. Es wurde dann als besseres, einfacheres C / C ++ vermarktet. Aber es ist nur oberflächlich ein C ++ - Nachkomme. Javas Mangel an Zeigern ist viel mehr dem Smalltalk-Objektmodell zu verdanken als der Ablehnung des C ++ - Datenmodells.
Daher sollten „moderne“ Sprachen wie Java, Ruby und C # nicht so interpretiert werden, als würden sie die Probleme von rohen Zeigern wie in C überwinden, sondern als Zeichen vieler Traditionen gesehen werden - einschließlich C, aber auch sichererer Sprachen wie Smalltalk, Simula, oder Lisp.
quelle
Nach meiner Erfahrung waren Hinweise für viele Menschen IMMER eine Herausforderung. 1970 hatte die Universität, die ich besuchte, einen Burroughs B5500, und wir verwendeten Extended Algol für unsere Programmierprojekte. Die Hardwarearchitektur basierte auf Deskriptoren und einigen Codes im oberen Teil der Datenwörter. Diese wurden explizit entwickelt, damit Arrays Zeiger verwenden können, ohne das Ende verlassen zu dürfen.
Wir hatten lebhafte Diskussionen im Klassenzimmer über Name vs. Wert und die Funktionsweise der B5500-Arrays. Einige von uns bekamen die Erklärung sofort. Andere nicht.
Später war es ein Schock, dass die Hardware mich nicht vor außer Kontrolle geratenen Zeigern schützte - insbesondere in Assemblersprache. Bei meinem ersten Job nach dem Abschluss half ich bei der Behebung von Problemen in einem Betriebssystem. Oft war die einzige Dokumentation, die wir hatten, der gedruckte Crash Dump. Ich habe ein Händchen dafür entwickelt, die Quelle von außer Kontrolle geratenen Zeigern in Speicherauszügen zu finden, also hat mir jeder die "unmöglichen" Speicherauszüge gegeben, um das herauszufinden. Mehr der Probleme, die wir hatten, wurden durch Zeigerfehler verursacht als durch irgendeine andere Art von Fehler.
Viele der Leute, mit denen ich zusammengearbeitet habe, begannen FORTRAN zu schreiben, wechselten dann zu C, schrieben C, das FORTRAN sehr ähnlich war, und mieden Zeiger. Da sie niemals Zeiger und Referenzen verinnerlicht haben, wirft Java Probleme auf. Für FORTRAN-Programmierer ist es oft schwierig zu verstehen, wie die Objektzuweisung wirklich funktioniert.
Moderne Sprachen haben es viel einfacher gemacht, Dinge zu tun, die Hinweise "unter der Haube" benötigen, und schützen uns gleichzeitig vor Tippfehlern und anderen Fehlern.
quelle