webentwicklung-frage-antwort-db.com.de

Was ist der Grund für Einschränkungen der Zeigerarithmetik oder des Zeigervergleichs?

In C/C++ wird Addition oder Subtraktion auf Zeiger nur dann definiert, wenn der resultierende Zeiger innerhalb des ursprünglich gezeigten vollständigen Objekts liegt. Außerdem kann der Vergleich zweier Zeiger nur durchgeführt werden, wenn die beiden gezeigten Objekte Unterobjekte eines eindeutigen vollständigen Objekts sind.

Was sind die Gründe für solche Einschränkungen?

Ich nahm an, dass das segmentierte Speichermodell (siehe hier §1.2.1) einer der Gründe sein könnte, aber da Compiler tatsächlich eine Gesamtreihenfolge für alle Zeiger definieren können, wie durch diese Antwort gezeigt, bezweifle ich dies .

24
Oliv

Es gibt Architekturen, bei denen Programm- und Datenräume getrennt sind, und es ist einfach unmöglich zwei beliebige Zeiger abzuziehen. Ein Zeiger auf eine Funktion oder auf statische Daten wird sich in einem vollständig anderen Adressraum befinden als eine normale Variable.

Selbst wenn Sie willkürlich eine Rangfolge zwischen verschiedenen Adressräumen angegeben haben, besteht die Möglichkeit, dass der diff_t-Typ größer sein muss. Und das Vergleichen oder Subtrahieren von zwei Zeigern wäre sehr kompliziert. Das ist eine schlechte Idee in einer Sprache, die auf Geschwindigkeit ausgelegt ist.

8
Mark Ransom

Sie beweisen nur, dass die Einschränkung aufgehoben werden könnte - aber vermissen Sie, dass dies mit Kosten (in Bezug auf Speicher und Code) verbunden wäre - was den Zielen von C zuwiderläuft.

Insbesondere muss der Unterschied einen Typ haben, der ptrdiff_t ist, und man würde annehmen, dass er ähnlich zu size_t ist.

In einem segmentierten Speichermodell haben Sie (normalerweise) indirekt eine Begrenzung für die Größe von Objekten - vorausgesetzt, die Antworten lauten: Wie groß ist die tatsächliche Größe von `size_t`,` uintptr_t`, `intptr_t` und` ptrdiff_t` auf 16? -Bitsysteme mit segmentiertem Adressierungsmodus? sind richtig.

Zumindest für Unterschiede, die diese Einschränkung aufheben, würden nicht nur zusätzliche Anweisungen hinzugefügt, um eine Gesamtbestellung zu gewährleisten - für einen unwichtigen Eckfall (wie in der anderen Antwort), sondern auch der doppelte Speicherplatz für Unterschiede usw.

C wurde entworfen, um minimalistischer zu sein und Compiler nicht dazu zu zwingen, Speicher und Code für solche Fälle aufzuwenden. (In diesen Tagen waren Speicherbeschränkungen wichtiger.)

Natürlich gibt es auch andere Vorteile - etwa die Möglichkeit, Fehler beim Mischen von Zeigern aus verschiedenen Arrays zu erkennen. Ähnlich wie das Mischen von Iteratoren für zwei verschiedene Container in C++ undefiniert ist (mit einigen geringfügigen Ausnahmen) - und einige Debug-Implementierungen erkennen solche Fehler.

10
Hans Olsson

Der Grund ist, die Möglichkeit zu behalten, vernünftigen Code zu generieren. Dies gilt sowohl für Systeme mit einem flachen Speichermodell als auch für Systeme mit komplexeren Speichermodellen. Wenn Sie die (nicht sehr nützlichen) Eckfälle wie das Hinzufügen oder Entfernen von Arrays und das Anfordern einer Gesamtreihenfolge für Zeiger zwischen Objekten verbieten, können Sie im generierten Code viel Overhead überspringen.

Die durch den Standard auferlegten Einschränkungen erlauben es dem Compiler, Annahmen zur Zeigerarithmetik zu treffen und diese zur Verbesserung der Codequalität zu verwenden. Es behandelt sowohl statische Berechnungen im Compiler als zur Laufzeit als auch die Auswahl der zu verwendenden Instrumente und Adressierungsmodi. Als Beispiel sei ein Programm mit zwei Zeigern p1 und p2 betrachtet. Wenn der Compiler daraus ableiten kann, dass sie auf verschiedene Datenobjekte verweisen, kann er mit Sicherheit davon ausgehen, dass keine Operation, die auf p1 folgt, jemals das Objekt beeinflusst, auf das p2 zeigt. Auf diese Weise kann der Compiler die Lade- und Speichervorgänge basierend auf p1 neu anordnen, ohne Ladevorgänge und Speichervorgänge basierend auf p2 und umgekehrt zu berücksichtigen.

9
Johan

Der Grund ist, dass einige Architekturen segmentierten Speicher haben und Zeiger auf verschiedene Objekte auf unterschiedliche Speichersegmente zeigen können. Der Unterschied zwischen den beiden Zeigern wäre dann nicht unbedingt sinnvoll.

Dies geht bis auf den Standard C zurück. Die Begründung erwähnt dies nicht explizit, aber es deutet darauf hin, dass dies der Grund ist, wenn wir uns ansehen, wo es die Gründe dafür erklärt, warum die Verwendung eines negativen Array-Index undefiniertes Verhalten ist (C99 Begründung 5.10 6.5.6, Hervorhebung meiner):

Im Fall von p-1 müsste dagegen vor dem - Array von Objekten, die von p durchlaufen werden, ein gesamtes Objekt zugewiesen werden, sodass Dekrementierungsschleifen, die am unteren Ende eines Arrays ablaufen, fehlschlagen können. Durch diese Einschränkung können segmentierte Architekturen beispielsweise Objekte am Anfang eines Bereichs Eines adressierbaren Speichers platzieren.

3
Lundin

In den meisten Fällen, in denen der Stanadrd eine Aktion als Aufrufen von undefined Behavior klassifiziert, wurde dies aus folgenden Gründen vorgenommen:

  1. Es kann Plattformen geben, auf denen die Definition des Verhaltens teuer wäre. Segmentierte Architekturen verhalten sich möglicherweise merkwürdig, wenn Code versucht, Zeigerarithmetik auszuführen, die über Objektgrenzen hinausgeht. Einige Compiler können p > q durch Testen des Vorzeichens von q-p auswerten.

  2. Es gibt einige Arten der Programmierung, bei denen die Definition des Verhaltens nutzlos wäre. Viele Arten von Code können ohne Weiteres auskommen, ohne sich auf Formen der Zeigeraddition, -subtraktion oder eines relationalen Vergleichs zu verlassen, die über die im Standard angegebenen Werte hinausgehen.

  3. Personen, die Compiler für verschiedene Zwecke schreiben, sollten in der Lage sein, Fälle zu erkennen, in denen sich qualitativ hochwertige Compiler, die für solche Zwecke vorgesehen sind, vorhersehbar verhalten sollten, und gegebenenfalls zu behandeln, ob der Standard sie dazu zwingt oder nicht.

Sowohl # 1 als auch # 2 sind sehr niedrige Takte und # 3 wurde als "Gimme" betrachtet. Obwohl es für Compiler-Autoren in Mode gekommen ist, ihre Klugheit zu demonstrieren, indem sie Wege finden, Code zu brechen, dessen Verhalten durch Qualitätsimplementierungen definiert wurde, die für Low-Level-Programmierung gedacht sind, glaube ich nicht, dass die Autoren des Standards von Compiler-Autoren erwartet haben, dass sie einen riesigen Umfang annehmen Unterschied zwischen Aktionen, die voraussagbar verhalten werden mussten, im Vergleich zu solchen, bei denen von nahezu allen Qualitätsimplementierungen erwartet wurde, dass sie sich identisch verhalten sollten, wo es jedoch sinnvoll sein könnte, einige arkane Implementierungen etwas anderes tun zu lassen.

2
supercat

Da der C-Standard die Mehrheit der Prozessorarchitekturen abdecken soll, sollte er auch diese abdecken: Stellen Sie sich eine Architektur vor (ich kenne eine, aber ich würde sie nicht nennen), bei der Zeiger nicht einfach Zahlen sind, sondern sind wie Strukturen oder "Deskriptoren". Eine solche Struktur enthält Informationen über das Objekt, auf das es zeigt (virtuelle Adresse und Größe) und den Versatz darin. Durch das Hinzufügen oder Subtrahieren eines Zeigers wird eine neue Struktur erstellt, bei der nur das Versatzfeld angepasst wird. Das Erstellen einer Struktur mit einem Versatz, der größer als die Größe des Objekts ist, ist hardwaremäßig verboten. Es gibt andere Einschränkungen (z. B. wie der ursprüngliche Deskriptor erstellt wird oder wie er geändert werden kann), sie sind jedoch für das Thema nicht relevant.

2

Ich möchte das beantworten, indem ich die Frage umwandle. Anstatt zu fragen, warum die Zeigeraddition und die meisten arithmetischen Operationen nicht zulässig sind, erlauben es die Zeiger nur, eine Ganzzahl hinzuzufügen oder zu subtrahieren. Dies hat mit der logischen Konsequenz der arithmetischen Operation zu tun. Durch Addieren/Subtrahieren einer Ganzzahl n zu einem Zeiger p erhält ich die Adresse des n-ten Elements von dem aktuell angezeigten Element entweder in Vorwärts- oder Rückwärtsrichtung. In ähnlicher Weise ergibt das Subtrahieren von p1 und p2, die auf dasselbe Array zeigen, die Anzahl der Elemente zwischen den beiden Zeigern. Die Tatsache (oder das Design), die die Zeigerarithmetikoperationen definieren, ist konsistent mit dem Typ der Variablen, auf die sie zeigt ist ein echter Geniestreich. Jede andere Operation als die erlaubte Operation widerspricht der Programmierung oder der philosophischen Logik und ist daher nicht zulässig.

1
Seshadri R

(Bitte nicht befürworten) Dies ist eine Zusammenfassung der Antwort und der Grund, warum ich die Mark Ransom-Antwort gewählt habe, und um diesen Beitrag zu erstellen: Die Antwort ist ihr Beitrag + der folgende Kommentar.

Die Frage war Was sind die Gründe für solche Einschränkungen (dass Zeigerarithmetik und -vergleich auf ein eindeutiges vollständiges Objekt fallen müssen)?

Kommentar- und Antwortzusammenfassung des Lösegelds: Da es kein Konzept des Adressraums gibt, besteht die einzige Möglichkeit, die Zeigerarithmetik auf einen Adressraum zu beschränken, darin, sie auf ein Objekt zu beschränken.

Krazy Glew comment's liefert auch eine soziologisch orientierte Antwort.

0
Oliv