Wann und warum wurden Zeiger als riskant eingestuft?

18

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.

DaveInCaz
quelle
1
Es war auf den Niedergang der Liberal Arts-Ausbildung zurückzuführen. Die Leute konnten Indirect Reference, eine der grundlegendsten Ideen in der Computertechnologie, die in allen CPUs enthalten ist, nicht mehr nachvollziehen.
10
Zeiger sind riskant. Warum hat sich Ihrer Meinung nach das Denken verändert? Es wurden Verbesserungen bei den Sprachfunktionen und der Hardware vorgenommen, die das Schreiben von Software ohne Zeiger ermöglichen, jedoch nicht ohne Leistungseinbußen.
Hören Sie auf, Monica
4
@ DaveInCaz Soweit ich weiß, war die spezifische Entwicklung die Erfindung von Zeigern.
Hören Sie auf, Monica
5
@nocomprende: Was du gerade geschrieben hast, sind weder Fakten noch Beweise, nur Meinungen. 1970 gab es weitaus weniger Programmierer, Sie haben keine Beweise dafür, dass die Bevölkerung heute bei der "indirekten Referenz" besser oder schlechter ist.
Whatsisname
3
Zeiger galten vom ersten Tag an als riskant. Es war einfach ein Kompromiss, sie von der Assembler-Version in eine höhere Sprache zu verschieben.
Frank Hileman

Antworten:

21

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 ein int* constwirklich 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.

Cort Ammon - Setzen Sie Monica wieder ein
quelle
3
Ich bin nicht sicher, ob es richtig ist zu sagen, dass Java keine Zeiger hat. Ich möchte nicht in eine lange Debatte darüber geraten, was ein Zeiger ist und was nicht, aber das JLS sagt, dass "der Wert einer Referenz ein Zeiger ist". Es ist nur kein direkter Zugriff oder keine Änderung von Zeigern erlaubt. Dies dient nicht nur der Sicherheit, es ist auch hilfreich, die Leute davon abzuhalten, den Überblick darüber zu behalten, wo sich ein Objekt gerade befindet.
JimmyJames
6
@ JimmyJames True. Für die Zwecke dieser Antwort bestand die Trennlinie zwischen Zeiger und Nichtzeiger darin, ob Zeigerarithmetikoperationen unterstützt wurden, die normalerweise nicht von Verweisen unterstützt werden.
Cort Ammon - Reinstate Monica
8
@JimmyJames Ich stimme der Behauptung von Cort zu, dass ein Zeiger etwas ist, mit dem Sie arithmetische Operationen ausführen können, eine Referenz jedoch nicht. Der eigentliche Mechanismus, der eine Referenz in Sprachen wie Java implementiert,
Robert Harvey
3
Im Allgemeinen haben C und C ++ freiwillig die Mitgliedschaft in diesem gefährlichen Club akzeptiert, indem sie viele "undefinierte Verhaltensweisen" in die Spezifikation aufgenommen haben.
rwong
2
Übrigens gibt es CPUs, die zwischen Zeigern und Zahlen unterscheiden. Dies erledigt beispielsweise die ursprüngliche 48-Bit-CISC-CPU in IBM AS / 400. Und in der Tat gibt es eine Abstraktionsschicht unter dem Betriebssystem, was bedeutet , dass nicht nur die CPU unterscheiden zwischen Zahlen und Zeigern und verbieten Arithmetik auf Zeiger, aber das Betriebssystem selbst nicht einmal wissen , über Zeiger überhaupt und weder die Sprachen tun . Interessanterweise macht dies das ursprüngliche AS / 400-System zu einem System, bei dem das Neuschreiben von Code aus einer höheren Skriptsprache in C die Geschwindigkeit um Größenordnungen verringert .
Jörg W Mittag
10

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.

amon
quelle
4

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.

Valerie Griffin
quelle