c++ - stroustrup - Was sind nahe, weit und große Zeiger?




this pointer c++ deutsch (4)

Kann mir jemand diese Zeiger mit einem geeigneten Beispiel erklären ... und wenn diese Zeiger verwendet werden?


Alles in dieser Antwort ist nur für das alte segmentierte Speichermodell 8086 und 80286 relevant.

near: ein 16-Bit-Zeiger, der jedes Byte in einem 64-k-Segment adressieren kann

weit: ein 32-Bit-Zeiger, der ein Segment und einen Offset enthält. Beachten Sie, dass zwei verschiedene Fernzeiger auf dieselbe Adresse zeigen können, da sich Segmente überlappen können.

riesig: ein 32-Bit-Zeiger, in dem das Segment "normalisiert" ist, so dass keine zwei Zeiger auf die gleiche Adresse zeigen, es sei denn, sie haben den gleichen Wert.

Tee: ein Getränk mit Marmelade und Brot.

Das bringt uns zurück zu oh oh oh oh

und wenn diese Zeiger verwendet werden?

in den 1980er und 90 'bis 32 Bit Windows wurde allgegenwärtig,


Das wichtigste Beispiel ist die Intel X86-Architektur.

Der Intel 8086 war intern ein 16-Bit-Prozessor: Alle Register waren 16 Bit breit. Der Adreßbus war jedoch 20 Bits breit (1 MiB). Dies bedeutet, dass Sie nicht eine ganze Adresse in einem Register halten können, was Sie auf die ersten 64 kB beschränkt.

Intels Lösung bestand darin, 16-Bit- "Segmentregister" zu erstellen, deren Inhalt um vier Bits nach links verschoben und zu der Adresse addiert würde. Beispielsweise:

DS ("Data Segment") register:  1234 h
DX ("D eXtended") register:   + 5678h
                              ------
Actual address read:           179B8h

Dies schuf das Konzept von 64 KiB-Segment. Somit wäre ein "naher" Zeiger nur der Inhalt des DX-Registers (5678h) und wäre ungültig, wenn das DS-Register nicht bereits richtig eingestellt wäre, während ein "far" -Zeiger 32 Bits (12345678h, DS gefolgt von DX) und würde immer funktionieren (aber war langsamer, weil Sie zwei Register laden mussten und dann das DS-Register wiederherstellen, wenn Sie fertig sind).

(Als Supercat-Noten unten würde ein Offset zu DX, der übergelaufen ist, "überrollt" werden, bevor er zu DS hinzugefügt wird, um die endgültige Adresse zu erhalten. Dies erlaubte 16-Bit-Offsets, auf irgendeine Adresse in dem 64 kB-Segment zuzugreifen, nicht nur auf den Teil, der war ± 32 kiB von wo DX deutete, wie in anderen Architekturen mit 16-Bit relativen Offset-Adressierung in einigen Anweisungen.

Beachten Sie jedoch, dass Sie zwei "ferne" Zeiger haben können, die unterschiedliche Werte haben, aber auf dieselbe Adresse zeigen. Zum Beispiel zeigt der Fernzeiger 100079B8h auf denselben Platz wie 12345678h. Daher war der Zeigervergleich auf fernen Zeigern eine ungültige Operation: Die Zeiger könnten sich unterscheiden, aber immer noch auf dieselbe Stelle zeigen.

Hier entschied ich, dass Macs (mit Motorola 68000 Prozessoren zu der Zeit) nicht so schlecht waren, also verpasste ich riesige Hinweise. IIRC, sie waren nur Fernzeiger, die garantierten, dass alle überlappenden Bits in den Segmentregistern Nullen waren, wie in dem zweiten Beispiel.

Motorola hatte dieses Problem mit ihrer 6800-Serie von Prozessoren nicht, da sie auf 64 kB beschränkt waren. Als sie die 68000-Architektur erstellten, gingen sie direkt zu 32-Bit-Registern und brauchten daher nie Nah-, Fern- oder große Zeiger . (Stattdessen war ihr Problem, dass nur die unteren 24 Bits der Adresse wirklich wichtig waren, daher würden einige Programmierer (notorisch Apple) die hohen 8 Bits als "Zeigerflags" verwenden, was Probleme verursachte, wenn Adressbusse auf 32 Bits (4 GiB) erweitert wurden. .)

Linus Torvalds hielt gerade bis zur 80386, die einen "geschützten Modus", wo die Adressen waren 32 Bit, und die Segmentregister waren die hohe Hälfte der Adresse, und keine Zugabe wurde erforderlich, und schrieb Linux von Anfang an, um geschützt zu verwenden Nur der Modus, kein merkwürdiger Segmentkram, und deshalb gibt es in Linux keine Unterstützung für Nah- und Fernzeiger (und warum kein Unternehmen, das eine neue Architektur entwirft, jemals wieder zu ihnen zurückkehren wird, wenn sie Linux unterstützen wollen). Und sie aßen Robins Spielleute, und es freute sich sehr. (Yay...)


In den alten Tagen laut dem Turbo C-Handbuch war ein naher Zeiger nur 16 Bits, wenn der gesamte Code und die Daten in das eine Segment passten. Ein Fernzeiger bestand aus einem Segment und einem Offset, aber es wurde keine Normalisierung durchgeführt. Und ein großer Zeiger wurde automatisch normalisiert. Zwei entfernte Zeiger könnten möglicherweise auf den gleichen Ort im Speicher zeigen, aber unterschiedlich sein, während die normalisierten großen Zeiger, die auf den gleichen Speicherort zeigen, immer gleich wären.


In einigen Architekturen ist ein Zeiger, der auf jedes Objekt im System zeigen kann, größer und langsamer zum Arbeiten als einer, der auf eine nützliche Teilmenge von Dingen zeigen kann. Viele Leute haben Antworten in Bezug auf die 16-Bit-x86-Architektur gegeben. Verschiedene Arten von Zeigern waren auf 16-Bit-Systemen üblich, obwohl Nah / Furcht-Unterschiede in 64-Bit-Systemen wieder auftreten könnten, je nachdem, wie sie implementiert sind (ich wäre nicht überrascht, wenn viele Entwicklungssysteme auf 64-Bit-Zeigern verweisen würden) alles, trotz der Tatsache, dass dies in vielen Fällen sehr verschwenderisch sein wird).

In vielen Programmen ist es ziemlich einfach, die Speicherbelegung in zwei Kategorien zu unterteilen: kleine Dinge, die zusammen eine ziemlich kleine Menge von Sachen (64K oder 4GB) ergeben, aber oft abgerufen werden, und größere Dinge, die eine viel größere Menge ergeben können , auf die aber nicht oft zugegriffen werden muss. Wenn eine Anwendung mit einem Teil eines Objekts im Bereich "große Dinge" arbeiten muss, kopiert sie diesen Teil in den Bereich "kleine Dinge", arbeitet damit und schreibt ihn gegebenenfalls zurück.

Einige Programmierer meckern, wenn sie zwischen "nahem" und "fernem" Speicher unterscheiden müssen, aber in vielen Fällen können solche Unterschiede es Compilern ermöglichen, viel besseren Code zu erzeugen.

(Anmerkung: Selbst auf vielen 32-Bit-Systemen kann auf bestimmte Speicherbereiche direkt ohne zusätzliche Anweisungen zugegriffen werden, während dies in anderen Bereichen nicht möglich ist. Wenn beispielsweise auf einem 68000 oder einem ARM ein Register auf den globalen Variablenspeicher verweist, Es ist möglich, jede Variable innerhalb der ersten 32K (68000) oder 2K (ARM) dieses Registers direkt zu laden.Wenn Sie eine Variable woanders abholen, benötigen Sie eine zusätzliche Anweisung, um die Adresse zu berechnen.Wegen Sie häufiger verwendete Variablen in die bevorzugten Regionen und den Compiler wissen zu lassen würde eine effizientere Code-Generierung ermöglichen.







x86-16