Geschwindigkeit vs. Sicherheit
-
Gregor schrieb:
Für mich läuft "mit C++ programmieren" meistens darauf hinaus, dass ich viel Zeit mit dem Debugging verbringe. Wenn ich mit Java programmiere, dann läuft das darauf hinaus, dass ich die meiste Zeit programmiere. Ich muss da nicht so viel debuggen.
Genau aus solchen Gründen glaube ich, dass sich zu viele Leute ein negatives Urteil über C++ erlauben, ohne die Sprache wirklich zu kennen. Ich glaube, ich habe noch nie in C++ einen Debugger zur Fehlersuche verwendet. Oder zumindest ist das solange her, dass ich mich nicht mehr daran erinnern kann. Natürlich verwende ich auch schon mal einen Debugger, aber idR nur dann, wenn ich eine Schrittanalyse brauche, um zu testen, ob ein implementierter Algorithmus auch richtig funktioniert.
Fehlerhandling gibt's bei mir natürlich trotzdem, und man muss dafür auch einiges tun. Das fällt in den meisten Fällen aber trivialer aus als man glaubt. Mein Leak Detektor zB nötigt mir gerade mal ein simples Include ab. Und ich empfinde es auch nicht als Nachteil, dass bestimmte Sicherheitsmechanismen nicht "eingebaut" sind.
Du hast ja einen Auszug gebracht, in dem es um die Sicherheit zwischen Java und C++ geht.Gregor schrieb:
As a demonstration of the safety properties provided by Java, we introduced a null pointer dereferencing bug into both the C and Java versions of the RAM disk device. The effect in the Java version was that a Java exception was thrown, resulting in an error code being returned from the kernel to the calling application. The C version caused a panic crash of the Solaris kernel. (This was not unexpected, but it does make for a dramatic conclusion to a presentation!)
Erstmal, ich dachte eigentlich, dass es in Java keine Zeiger gibt, aber egal.
Man kann das natürlich positiv für die Sicherheit von Java auslegen, aber genauso negativ für die Performance von Java. Und einzig und allein seinen Blick auf Sicherheit zu richten, wenn man eine generelle Betrachtung machen will, ist zu wenig. Keinem nützt eine sichere Sprache etwas, wenn sie zu langsam ist. Gerade Javaisten dürften das doch mit am besten wissen. Und genau das ist auch der Punkt bei dem Auszug. Um die Sicherheit zu erreichen, muss in Java permanent auf Null abgefragt werden. Auch wenn es praktisch nie notwendig ist, da man normalerweise keinen Nullzeiger dereferenziert. Und wenn tatsächlich der Fall eintreten sollte, dass ein entsprechender Fehler im Programm ist, dann wird er halt gefixt und gut ist. Der unnötige Vergleich auf Null bleibt in Java trotzdem. Und das ist, glaube ich, auch die Thematik, die Java Fanatiker nicht verstehen. Dir wird von C++ nichts aufgezwungen. Du kannst selbst entscheiden, was du möchtest. Optimale Performance oder notwendige Sicherheit, beides gleichzeitig wirst du niemals bekommen. Auch wenn ich davon ausgehe, dass Rechner irgendwann so schnell werden, dass Performance sekundär ist. Aber bis dahin kann man sehr gut mit C++ arbeiten und Java wird dann vermutlich durch etwas vollkommen anderes ersetzt worden sein, was besser auf ein Virtualisierungskonzept zugeschnitten ist.
-
groovemaster schrieb:
[
Erstmal, ich dachte eigentlich, dass es in Java keine Zeiger gibt, aber egal.innerhalb der vm gibt's welche und die sind auch damit gemeint...
-
net schrieb:
groovemaster schrieb:
[
Erstmal, ich dachte eigentlich, dass es in Java keine Zeiger gibt, aber egal.innerhalb der vm gibt's welche und die sind auch damit gemeint...
Ja, genau, die JVM-Bugs schmeissen dann immer diese lästigen NullPointerExceptions ...
-
net schrieb:
innerhalb der vm gibt's welche und die sind auch damit gemeint...
Hmmm...war aber Gregors Standpunkt nicht gerade der, dass die JVM Implementierung keine Relevanz für die Sicherheit von Java hat?
-
groovemaster schrieb:
Und genau das ist auch der Punkt bei dem Auszug. Um die Sicherheit zu erreichen, muss in Java permanent auf Null abgefragt werden.
Das geht über den Speicherschutz der Hardware. Bei jedem Zugriff auf eine Speicheradresse (auch in C++) wird die Adresse durch die Hardware überprüft und so wie ein Speicherzugriffsfehler in C++ als access violation dargestellt wird, ist das in Java eine NullPointerException. Ein Unterschied ergibt sich erst bei nicht-virtuellen Funktionsaufrufen.
Wenn man C++ Programme debugged, dann ist es meistens so, dass man sich innerhalb einer Methode mit this-Zeiger == 0 befinden kann und beim Zugriff auf ein Member erst stürzt das Programm ab. Java garantiert demgegenüber, dass this niemals null ist und muss daher beim nicht-virtuellen Aufruf als einzigen Fall eine zusätzliche Prüfung einbauen (beim virtuellen wird auf die vtable zugegriffen was eine access violation auslösen würde). Es ist also keineswegs so, dass sehr dramatisch öfter eine Prüfung auf null stattfindet. Tatsächlich findet sie sowieso "in beiden Sprachen" immer statt, in Java nur in einigen Fällen öfters, wenn es darum geht, schneller einen Fehler liefern zu können.
-
Ich möchte aber dazu sagen, dass ich diesen Teil in Java und auch in C# und ähnlichen Sprachen außerordentlich wenig gelungen finde. Null-Exceptions sind Dinge, die sich komplett zur Compile-Zeit eliminieren lassen würden und dass viele Sprachen ein nicht-null Konzept nicht vorsehen, geht mir tierisch auf den Wecker.
-
Optimizer schrieb:
Das geht über den Speicherschutz der Hardware.
Aber auch nur, wenn auf der entsprechenden Hardware einer vorhanden ist, gelle?
Optimizer schrieb:
Tatsächlich findet sie sowieso "in beiden Sprachen" immer statt
Ich kann nur von C++ sprechen, und da ist "immer" übertrieben. Man macht es eigentlich nur, wenn Zeiger über Schnittstellen geschickt werden (und entsprechend dokumentiert sind), oder ein Zeiger erstmal default auf Null gesetzt wird und später eine gültige Adresse zugewiesen bekommt. Was man allerdings möglichst vermeiden sollte, und schön RAII verwenden sollte. Ansonsten hat man immer einen gültigen Zeiger (nicht initialisierte jetzt mal aussen vor gelassen).
-
Optimizer schrieb:
Null-Exceptions sind Dinge, die sich komplett zur Compile-Zeit eliminieren lassen würden
class Foo { private Bar bar; function Foo() { bar=null; //do a lazy initialize } private void ensure_is_initialized() { if(!is_initialized()) initialize(); } private boolean is_initialized() { return bar!=null; } private initialize() { bar = new Bar();//expesnive initialization code } public void do_smth_good() { ensure_is_initialized(); bar.do_smth_good(); } public void do_smth_bad() { bar.do_smth_bad(); } }
wie checkst du das zur compile time?
-
groovemaster schrieb:
...
Erstmal, ich dachte eigentlich, dass es in Java keine Zeiger gibt, aber egal....Es gibt eigentlich in Java NUR Pointer. Es gibt zwar keine void*, aber ansonsten erfüllt das, was in Java "Referenz" genannt wird, alle Kriterien von dem, was in C++ "Pointer" genannt wird. Was es dagegen nicht gibt, sind "Stackobjekte" und "Referenzen (wie es sie in C++ gibt)".
Deswegen finde ich ja dieses "Java ist sicher, weil es keine Pointer gibt" so lächerlich und falsch. Probleme mit Pointern gibt es eigentlich nur da, wo sie woanders hinzeigen als man meint/will. Das kann man in Java ebenso leicht machen wie in C++....
Aber "echte Zugriffssicherheit", wie sie Dir ein Stackobjekt oder (in Grenzen) eine C++-Referenz bietet, gibt's in Java gar nicht...Insgesamt teile ich Deine Erfahrung: Leute, die Java für die "sichere" Sprache halten, haben oft aufgrund falscher Beratung/Einarbeitung/Beispiele/... schlechte Erfahrung mit C++ gemacht (meistens in Sachen Stringbearbeitung) und meinen, wenn sie schlechte Programme geschrieben haben, wäre das das Problem der Sprache.
Wenn man mal ins C++-Unterforum schaut, stellt man mit Erschrecken fest, dass fast alle C++-Anfänger mit char[]-und anderem Array-Zeugs anfangen und immer erst mühsam auf std::string und std::vector hingewiesen werden müssen !
Ich frage mich, wer die ganzen Kollegen so konsequent auf die falsche Fährte schickt (vermutlich alte "C-Hasen", die sich jede noch so kleine Hilfsfunktion immer wieder neu selbst schreiben).....Natürlich bietet einem Java mit seiner VM und seinen eingeschränkten Möglichkeiten eine niedrige Einstiegsschwelle ... man kommt schnell zu einem Hello-World, bekommt einen einfachen und konsistenten Einblick in OO-Programmierung, ....
Aber die "Fehler findet man eben zur Laufzeit"-Philosophie frisst IMO die Nerven dafür später (und bei größeren Programmen) und über bestimmte Programmierformen/-bereiche lernt man in Java nie wieder etwas (generische Programmierung, Compiletime-Polymorphie, Performanceoptimierung, Speicherverwaltung, ....).Damit ist für mich Java tatsächlich eine gute (wenn nicht gar ideale) Sprache da, wo man viel mit Ein-/Umsteigern , stark nichttechnischen Anforderungen, GUIs, .... zu tun (z.B. in unserer Firma) und eine Anforderung an niedrige Codingzeiten (die dann allerdings im Test und Produktivsetzung/-betrieb wieder verbraten werden) hat.
Wer "Lust auf mehr" (Technik(en)) hat, ist mit IMO C++ besser bedient.
Gruß,
Simon2.
-
Ich glaub ja eh, die am Besten durchdachte Sprache ist Ada. Die hat ihre Stärken wirklich im Sicherheitsbereich. Wird nicht umsonst in der Luft - und Raumfahrtbranche eingesetzt.
-
GPC schrieb:
Ich glaub ja eh, die am Besten durchdachte Sprache ist Ada. Die hat ihre Stärken wirklich im Sicherheitsbereich. Wird nicht umsonst in der Luft - und Raumfahrtbranche eingesetzt.
Klingt interessant. Muß ich mir mal anschauen...
Danke,
Simon2.
-
Was z.B. ein sehr witziges Feature ist: Ich kann einer Variablen sagen, welchen Wertebereich sie haben darf (nein, nicht über den Typ
). So kann ich bei einer Integer-Variable sagen, sie darf Werte zw. -5 und 5 annehmen. Alles andere löst einen Sicherheitsfehler aus.
Automatische Index-Checks sowie Checks auf Speicher-Zugriffsfehler sind natürlich auch dabei. Kann auch alles deaktiviert werden.Außerdem eignet sich die Sprache sehr gut für die automatische Verifikation von Programmen.
Ja, ich glaube Ada ist wirklich sehr gut durchdacht, nur die Syntax is etwas schräg, aber immer noch besser als bei LISP
-
GPC schrieb:
Was z.B. ein sehr witziges Feature ist: Ich kann einer Variablen sagen, welchen Wertebereich sie haben darf (nein, nicht über den Typ
). So kann ich bei einer Integer-Variable sagen, sie darf Werte zw. -5 und 5 annehmen. Alles andere löst einen Sicherheitsfehler aus.
Und was ist daran "besser" als an einer Realisierung durch nen eigenen Typ?
-
byto schrieb:
GPC schrieb:
Was z.B. ein sehr witziges Feature ist: Ich kann einer Variablen sagen, welchen Wertebereich sie haben darf (nein, nicht über den Typ
). So kann ich bei einer Integer-Variable sagen, sie darf Werte zw. -5 und 5 annehmen. Alles andere löst einen Sicherheitsfehler aus.
Und was ist daran "besser" als an einer Realisierung durch nen eigenen Typ?
Na ja, es ist halt sauber und einfach in die Sprache integriert und daher ohne viel Aufwand zu realisieren. Wenn ich z.B. in C++ den Wertebereich (sagen wir grad mal -5 und 5) einschränken will, ist das schon aufwändiger.
-
Also ein...
type WINKEL is delta 0.01 range 0.0..360.0;
hat schon was
-
Shade Of Mine schrieb:
wie checkst du das zur compile time?
Die Behauptung war nicht, zur compile-Zeit auf null prüfen zu können. Aber man kann immer verhindern, dass irgendwo unerwartet null steht. Dazu musst es zwei Zeigertypen geben, einen Foo und einen Foo? und nur der Foo? darf null sein. Foo kann immer implizit in ein Foo? umgewandelt werden, aber umgekehrt muss "gecasted" werden.
Der Compiler könnte bei so einem Cast einen auch zwingen, Code für den Fall zu schreiben, dass der Pointer null ist (ähnlich wie checked Exceptions). Das wäre supergeil.
-
groovemaster schrieb:
Optimizer schrieb:
Das geht über den Speicherschutz der Hardware.
Aber auch nur, wenn auf der entsprechenden Hardware einer vorhanden ist, gelle?
Wenn keiner vorhanden ist, dann muss der Test explizit vom JIT-compiler eingebaut werden. Aber gerade ohne Speicherschutz sind noch strengere Tests durchaus zu befürworten. Ohne Speicherschutz und ohne Garantien wie sie beispielsweise Java bietet, könntest du kein sicheres Betriebssystem schreiben.
Optimizer schrieb:
Tatsächlich findet sie sowieso "in beiden Sprachen" immer statt
Ich kann nur von C++ sprechen, und da ist "immer" übertrieben. Man macht es eigentlich nur, wenn Zeiger über Schnittstellen geschickt werden (und entsprechend dokumentiert sind), oder ein Zeiger erstmal default auf Null gesetzt wird und später eine gültige Adresse zugewiesen bekommt. Was man allerdings möglichst vermeiden sollte, und schön RAII verwenden sollte. Ansonsten hat man immer einen gültigen Zeiger (nicht initialisierte jetzt mal aussen vor gelassen).
Du verstehst nicht ganz, worauf ich hinauswollte: Weder du noch der Compiler bauen Prüfungen beim Speicherzugriff ein. Das macht die MMU des Prozessors und das Betriebssystem hat als einziges Programm das Recht, den Schutz abzustellen. Dass du einen 0-Pointer nicht dereferenzieren kannst liegt daran, weil bei 0 das Textsegment steht und das Betriebssystem dem Prozessor sagt "nur innerhalb dieser Segmente darfste das und das". Den Test hast du immer und kannst dich nicht dagegen wehren.
Schlägt jetzt so eine Prüfung fehl, wird der entsprechende Interrupt-Handler vom BS abgearbeitet. Das kann benutzt werden, um in C++ Programmen eine access violation anzuzeigen (anstatt den Prozess gleich zu beenden) - oder in Java eine NullPointerException. Das kostet "nichts", weil du es sowieso immer bezahlst. Ausnahmen sind die seltenen Fälle wo der Speicherschutz nicht so schnell greifen würde wie eine explizite Prüfung auf null.
-
Optimizer schrieb:
Du verstehst nicht ganz, worauf ich hinauswollte: Weder du noch der Compiler bauen Prüfungen beim Speicherzugriff ein. Das macht die MMU des Prozessors und das Betriebssystem hat als einziges Programm das Recht, den Schutz abzustellen.
Ja, das interessiert dich als Hochsprachenprogrammierer aber überhaupt nicht. Java garantiert dir eine entsprechende Exception, deshalb müssen auch die Voraussetzungen dafür geschaffen werden. Darum schrieb ich, dass dort generell ein Overhead da ist, egal ob sich das nun tatsächlich +performacetechnisch niederschlägt oder nicht, weil die CPU zB einen hardwareseitigen Schutz hat. In C++ gibt es diese Restriktionen eben nicht. Da hast du minimal keinen Overhead, weder soft- noch hardwareseitig. Weil deine CPU dies zB nicht unterstützt, und auch softwareseitig keine Prüfung notwendig ist (zB weil du dich auf das Ergebnis von new verlassen kannst). Bis maximal 2mal Overhaed, soft- und hardwareseitig. Allerdings kann man dort, natürlich leider nur plattformabhängig, die Funktionalität des Betriebssystems ausnutzen, so dass keine explizite Prüfung mehr notwendig ist.
-
In C++ gibt es diese Restriktionen eben nicht. Da hast du minimal keinen Overhead, weder soft- noch hardwareseitig.
Das mag für das Programmieren von Mikro-Controllern noch stimmen, auf einem typischen Desktop-System aber brauchst du ohne Wenn und Aber ein sicheres Betriebssystem. Und genau wenn man so weit schon ist, dass man ein sicheres Betriebssystem braucht, kann der Ansatz von C++ sich zum Performance-Nachteil entwickeln.
ftp://ftp.research.microsoft.com/pub/tr/TR-2006-43.pdf schrieb:
These mechanisms can incur non-trivial performance costs. Mapping
from virtual to physical addresses can incur overheads up to 10–
30% due to exception handling, inline TLB lookup, TLB reloads,
and maintenance of kernel data structures such as page tables
[28]. In addition, virtual memory and privilege levels increase the
cost of inter-process communication.Umgekehrt können Garantien die dir Sprachen wie Java bieten, ausgenutzt werden, um eine höhere Performance zu erreichen. Der Punkt dabei ist, dass man auf Grund dieser Garantien auf ein Stück Überwachung verzichten kann. Ein sehr interessantes Forschungsprojekt dazu ist IMHO Singularity, ein Betriebssystem von Microsoft, welches auf den Hardware-seitigen Schutz der Prozesse gegeneinander verzichtet. Das ermöglicht eine deutlich schnellere Interprozess-Kommunikation und performante Mikrokernel-Architektur (d.h. auch Treiber laufen in eigenen abgeschotteten Prozessen, was die Sicherheit weiter erhöht).
Das Betriebssystem soll aber keineswegs ohne Schutz sein. Microsoft nennt das, was man heute durch Speichersegmentierung und Schutz durch die MMU erreicht "Hardware isolated processes" und stellt dem "Software isolated processes" gegenüber, die die Eigenschaften von "sicheren" Programmiersprachen ausnutzen um nur Speicherzugriffe zu überwachen, auf die es ankommt. Ausführbare Programme liegen in .Net-Code vor und laufen praktisch ohne Speicherschutz ab.
-
Optimizer schrieb:
Umgekehrt können Garantien die dir Sprachen wie Java bieten, ausgenutzt werden, um eine höhere Performance zu erreichen. Der Punkt dabei ist, dass man auf Grund dieser Garantien auf ein Stück Überwachung verzichten kann. Ein sehr interessantes Forschungsprojekt dazu ist IMHO Singularity, ein Betriebssystem von Microsoft, welches auf den Hardware-seitigen Schutz der Prozesse gegeneinander verzichtet. Das ermöglicht eine deutlich schnellere Interprozess-Kommunikation und performante Mikrokernel-Architektur (d.h. auch Treiber laufen in eigenen abgeschotteten Prozessen, was die Sicherheit weiter erhöht).
Singularity ist doch genau das Gegenteil einer µKernel-Architektur. Anstelle nur wenig Code im kritischen Bereich (Ring0) einzusetzen, packen die da nicht nur eine komplette VM rein, sondern auch alle Treiber und Programme die ausgeführt werden. Das der "mehr Code" -> "mehr Sicherheits"-Ansatz fundamentale Fehler hat, sieht man ja allein an den Sicherheitslücken, der Java VMs.
Der µKernel hat ja das Prinzip möglichst wenig Code in Ring0 platzieren, so das man den Code leicht prüfen kann.