2016-04-10 6 views
3

Ich habe in letzter Zeit herumalbern versucht, C zu lernen. Aus Java kommend, hat es mich überrascht, dass Sie bestimmte Operationen ausführen können, die als "undefiniert" deklariert sind.Warum ist undefiniertes Verhalten in C zulässig?

Das scheint mir extrem unsicher zu sein. Ich verstehe, dass der Programmierer dafür verantwortlich ist, keine undefinierten Operationen auszuführen, aber warum darf er überhaupt anfangen? Warum fängt der Compiler beispielsweise keine Array-Indizes außerhalb der Grenzen oder gar hängende Zeiger ab? Sie greifen nur auf Speicherblöcke zu, auf die Sie niemals zugreifen sollten, ohne (scheinbar) gute Gründe.

Als Vergleich macht Java besonders sicher Sie nicht etwas dumm, tun Ausnahmen um wie warme Semmeln zu werfen.

Sicher muss es einen Grund geben, warum dies erlaubt ist? Was ist es?

ANTWORT: Nach meinem Verständnis ist der Hauptgrund Leistung. Außerdem hat Java undefinierte Verhaltensweisen, obwohl sie nicht als solche gekennzeichnet sind.

EDIT: eingeschränkt Frage zu C

+0

Sie meinen, neben dem offensichtlichen Grund? –

+8

Es gibt eine Leistungseinbuße durch Überprüfung der Grenzen jedes einzelnen Array-Zugriffs. –

+0

weil es ziemlich schwierig ist, es zumindest in der Vergangenheit und jetzt zu verbieten. – user3528438

Antwort

2

Ursprünglich stellten die meisten Formen von Undefined Behavior Dinge dar, die bei einigen Implementierungen auftreten können, bei anderen Implementierungen jedoch nicht. Weil es für die Autoren des Standards keine Möglichkeit gab, alle Dinge vorherzusagen, die eine Plattform im Falle einer Falle machen könnte (einschließlich buchstäblich der Möglichkeit, dass ein System einen Alarm ausgibt und blockiert, bis ein Bediener den Fehler manuell löschte) , die Folgen von Fallen fielen nicht in den Zuständigkeitsbereich des C-Standards, und somit wird fast jede Aktion, für die eine Plattform möglicherweise eine Falle verursachen könnte, aus der Sicht des Standards als "Undefiniertes Verhalten" betrachtet.

Das sollte nicht bedeuten, dass die Autoren des Standards nicht glauben, dass Implementierungen versuchen sollten, sich vernünftig für solche Dinge zu verhalten, wenn sie praktikabel sind.Die Autoren der C89 Norm erwähnt, zum Beispiel, dass die Mehrzahl der aktuellen Systeme dieser Ära Verhalten definieren würde:

/* Assume USmall is half the size of "int" */ 
unsigned mult(USmall x, USmall y) { return x*y; } 

die in allen Fällen würde, einschließlich solche, bei denen das mathematische Produkt von x und y war zwischen INT_MAX + 1 und UINT_MAX, entspricht (unsigned)x*y;. Ich sehe keinen Grund zu glauben, dass sie nicht erwartet hätten, dass sich dieser Trend fortsetzt.

Leider ist eine neue Philosophie in Mode gekommen, basierend auf dem revisionistischen Standpunkt, dass Compiler-Autoren nur hilfreiche Verhaltensweisen in Fällen unterstützt haben, die nicht vom Standard vorgeschrieben sind, weil sie zu einfach waren, um etwas anderes zu tun. In gcc, zum Beispiel, unter Verwendung der Optimierungsstufe 2, aber keiner anderen Nicht-Standardoptionen, erzeugt die obige "mult" -Routine manchmal falschen Code in Fällen, in denen das Produkt zwischen 0x80000000u und 0xFFFFFFFFu liegen würde, selbst wenn es auf Plattformen läuft, wo solche Berechnungen stattfinden würden historisch haben gearbeitet. Dies geschieht angeblich im Namen von "Optimierung"; Es wäre interessant zu wissen, wie viele der "Optimierungen", die solche Techniken durchführen, tatsächlich nützlich sind und nicht mit sichereren Mitteln hätten erreicht werden können.

Historisch war Undefined Behavior eine Lizenz für einen C-Compiler, um das Verhalten der zugrunde liegenden Plattform offenzulegen. In Fällen, in denen das Verhalten der zugrundeliegenden Plattform den Bedürfnissen des Programmierers entsprach, konnten die Anforderungen des Programmierers effizienter in Maschinencode ausgedrückt werden, als wenn alles auf eine vom Standard definierte Weise ausgeführt werden müsste. In letzter Zeit wurde es jedoch als Lizenz für Compiler interpretiert, um Verhaltensweisen zu implementieren, die nicht nur nichts mit der zugrundeliegenden Plattform zu tun haben, noch irgendwelche plausiblen Programmierererwartungen, aber nicht einmal an Gesetze von Zeit und Kausalität gebunden sind.

-2

Java hat eine Laufzeitumgebung Sie kümmern zu nehmen. Aus diesem Grund wird eine Ausnahmebedingung ausgelöst, wenn man sich außerhalb der Grenzen befindet. Dies ist etwas, was zur Kompilierzeit nicht herausgefunden werden kann.

Es gibt Laufzeitgrenzen, die in C++ überprüft werden, wenn die Methode at() für einen Vektor verwendet wird. Das unterscheidet den() von dem Operator [].

3

Undefiniertes Verhalten ist nicht erlaubt, es wird vom Compiler nicht abgefangen.

Der Kompromiss hier ist zwischen der Geschwindigkeit und der Sicherheit. Viele Arten von undefiniertem Verhalten könnten auf Kosten einiger zusätzlicher CPU-Zyklen verhindert werden.

Zum Beispiel könnten Sie verhindern, dass UB auftritt, wenn Sie aus dem Speicher lesen, der zugewiesen, aber nicht initialisiert wurde, indem der kompilierte Code Nullen in ihn schreibt. Dies kostet Sie jedoch ein ganzes zusätzliches Schreiben in einen Speicher, was völlig unnötig ist.

In ähnlicher Weise könnte man das Lesen/Schreiben über das Ende eines Arrays hinaus verhindern, indem man seine Grenzen innerhalb des Operators [] überprüft. Dies würde jedoch einige zusätzliche CPU-Zyklen bei jedem Array-Zugriff kosten.

C++ Designer entschieden, dass es besser ist, Geschwindigkeit zu haben und potentielle UB zu erlauben, als alle zu zwingen, für das zu bezahlen, was sie nicht brauchen. Dieser Ansatz ist jedoch inkompatibel mit der Java-Anforderung "einmal schreiben, irgendwo ausführen", sodass Entwickler von Java in fast allen Situationen auf vollständig definiertes Verhalten bestanden.

+1

Ist das Verhalten in allen Situationen nicht vollständig definiert? –

+0

@PaulBoddington Anders als JNI ist der Java-Standard ein "Kontroll-Freak": entweder erzeugt der Compiler einen Fehler, oder Sie erhalten das im Standard beschriebene Verhalten. Zumindest hoffen die Standardautoren darauf. – dasblinkenlight

+0

Das stimmt, und ich mag Java deswegen sehr, aber es gibt ein unbestimmtes Verhalten - z. Es gibt keine Möglichkeit, vorherzusagen, wann sich die Größe einer "WeakHashMap" ändern könnte. –