Ich weiß, dass Zeiger Adressen enthalten. Ich weiß, dass Zeigertypen "allgemein" bekannt sind, basierend auf dem "Typ" der Daten, auf die sie zeigen. Zeiger sind jedoch weiterhin Variablen, und die Adressen, die sie enthalten, müssen einen "Datentyp" haben. Nach meinen Informationen sind die Adressen im hexadezimalen Format. Aber ich weiß immer noch nicht, welcher "Datentyp" dieses Hexadezimal ist. (Beachten Sie, dass ich weiß, was ein Hexadezimalwert ist, aber wenn Sie beispielsweise sagen 10CBA20
, ist dies eine Zeichenfolge? Ganzzahlen? Was? Wenn ich auf die Adresse zugreifen und sie selbst bearbeiten möchte, muss ich ihren Typ kennen. Dies Deshalb frage ich.)
30
Antworten:
Der Typ einer Zeigervariable ist .. pointer.
Die Operationen, die Sie formal in C ausführen dürfen, bestehen darin, sie zu vergleichen (mit anderen Zeigern oder dem speziellen NULL / Null-Wert), Ganzzahlen zu addieren oder zu subtrahieren oder sie in andere Zeiger umzuwandeln.
Sobald Sie undefiniertes Verhalten akzeptieren , können Sie den tatsächlichen Wert anzeigen. Es wird normalerweise ein Maschinenwort sein, dasselbe wie eine Ganzzahl, und es kann normalerweise verlustfrei in einen Ganzzahltyp und von einem Ganzzahltyp umgewandelt werden. (Sehr viel Windows-Code tut dies, indem er Zeiger in DWORD- oder HANDLE-Typedefs versteckt.)
Es gibt einige Architekturen, in denen Zeiger nicht einfach sind, weil der Speicher nicht flach ist. DOS / 8086 'nah' und 'fern'; PICs verschiedene Speicher- und Codebereiche.
quelle
p1-p2
. Das Ergebnis ist ein vorzeichenbehafteter Integralwert. Insbesondere&(array[i])-&(array[j]) == i-j
intptr_t
und zwaruintptr_t
für Zeigerwerte, die garantiert "groß genug" sind.p
Spezifizierers zu printf macht das Abrufen einer für den Menschen lesbaren Darstellung eines Leerzeigers zu einem definierten, wenn implementierungsabhängigen Verhalten in c.Du machst die Dinge zu kompliziert.
Adressen sind nur ganze Zahlen, Punkt. Im Idealfall ist dies die Nummer der Speicherzelle, auf die verwiesen wird (in der Praxis wird dies aufgrund von Segmenten, virtuellem Speicher usw. komplizierter).
Die hexadezimale Syntax ist eine vollständige Fiktion, die nur zur Vereinfachung für Programmierer existiert. 0x1A und 26 sind genau die gleiche Anzahl von genau dem gleichen Typ und auch nicht das, was der Computer verwendet - intern verwendet der Computer immer 00011010 (eine Reihe von Binärsignalen).
Ob ein Compiler es Ihnen erlaubt, Zeiger als Zahlen zu behandeln, hängt von der Sprachdefinition ab - "Systemprogrammierungs" -Sprachen sind normalerweise transparenter darüber, wie Dinge unter der Haube funktionieren, während "High-Level" -Sprachen häufiger versuchen, das bloße Metall zu verbergen vom Programmierer - aber das ändert nichts an der Tatsache, dass Zeiger Zahlen sind und normalerweise der häufigste Zahlentyp (der mit so vielen Bits wie Ihre Prozessorarchitektur).
quelle
Ein Zeiger ist genau das - ein Zeiger. Es ist nichts anderes. Versuche nicht zu denken, dass es etwas anderes ist.
In Sprachen wie C, C ++ und Objective-C haben Datenzeiger vier Arten von möglichen Werten:
Es gibt auch Funktionszeiger, die entweder eine Funktion identifizieren oder Nullfunktionszeiger sind oder einen unbestimmten Wert haben.
Andere Zeiger sind "Zeiger auf Member" in C ++. Dies sind definitiv keine Speicheradressen! Stattdessen identifizieren sie ein Mitglied einer Instanz einer Klasse. In Objective-C haben Sie Selektoren, die so etwas wie "Zeiger auf eine Instanzmethode mit einem bestimmten Methodennamen und Argumentnamen" sind. Wie ein Member-Zeiger werden alle Methoden aller Klassen identifiziert, sofern sie gleich aussehen.
Sie können untersuchen, wie ein bestimmter Compiler Zeiger implementiert, aber das ist eine ganz andere Frage.
quelle
class A { public: int num; int x; }; int A::*pmi = &A::num; A a; int n = a.*pmi;
Die Variablepmi
wäre nicht sehr nützlich, wenn sie keine Speicheradresse enthalten würde, nämlich, wie die letzte Zeile des Codes festlegt, die Adresse des Mitgliedsnum
der Instanza
der KlasseA
. Sie können dies in einen normalenint
Zeiger umwandeln (obwohl der Compiler wahrscheinlich eine Warnung ausgibt) und die Referenzierung erfolgreich aufheben (um zu beweisen, dass es sich um syntaktischen Zucker für jeden anderen Zeiger handelt).Ein Zeiger ist ein Bitmuster, das ein im RAM gespeichertes Wort adressiert (zum Zwecke des Lesens oder Schreibens eindeutig identifiziert). Aus historischen und konventionellen Gründen besteht die Aktualisierungseinheit aus acht Bits, die auf Englisch als "Byte" oder auf Französisch eher logisch als Oktett bezeichnet werden. Dies ist allgegenwärtig, aber nicht inhärent; andere Größen haben existiert.
Wenn ich mich richtig erinnere, gab es einen Computer, der ein 29-Bit-Wort verwendete. Dies ist nicht nur keine Zweierpotenz, sondern sogar eine Primzahl. Ich dachte, das wäre SILLIAC, aber der entsprechende Wikipedia-Artikel unterstützt dies nicht. CAN-BUS verwendet 29-Bit-Adressen, aber gemäß Konvention werden Netzwerkadressen nicht als Zeiger bezeichnet, auch wenn sie funktional identisch sind.
Die Leute behaupten immer wieder, Zeiger seien ganze Zahlen. Dies ist weder inhärent noch wesentlich, aber wenn wir die Bitmuster als ganze Zahlen interpretieren, entsteht die nützliche Qualität der Ordinalität, die eine sehr direkte (und daher auf kleiner Hardware effiziente) Implementierung von Konstrukten wie "string" und "array" ermöglicht. Der Begriff des zusammenhängenden Gedächtnisses hängt von der ordinalen Nachbarschaft ab, und eine relative Positionierung ist möglich. Ganzzahlvergleich und arithmetische Operationen können sinnvoll angewendet werden. Aus diesem Grund besteht fast immer eine starke Korrelation zwischen der Wortgröße für die Speicheradressierung und der ALU (die Sache, die Ganzzahl-Mathematik macht).
Manchmal stimmen die beiden nicht überein. In frühen PCs war der Adressbus 24 Bit breit.
quelle
char*
zum Kopieren / Vergleichen in den Speicher undsizeof char==1
gemäß C-Standard sicher umgewandelt werden ), nicht auf das Wort (es sei denn, die CPU-Wortgröße entspricht der Bytegröße).Grundsätzlich ist jeder moderne Computer eine Bit-Push-Maschine. Normalerweise werden Bits in Datenclustern verschoben, die als Bytes, Wörter, Dwords oder Qwords bezeichnet werden.
Ein Byte besteht aus 8 Bits, einem Wort 2 Bytes (oder 16 Bits), einem Wort 2 (oder 32 Bits) und einem Wort 2 (oder 64 Bits). Dies ist nicht die einzige Möglichkeit, Bits anzuordnen. 128-Bit- und 256-Bit-Manipulationen treten häufig auch bei SIMD-Befehlen auf.
Montageanweisungen arbeiten mit Registern und Speicheradressen arbeiten normalerweise in einer der obigen Formen.
ALU (Arithmetic Logic Units) verarbeiten solche Bitbündel, als ob sie Ganzzahlen darstellen (normalerweise Two's Complement Format), und FPUs, als ob sie Gleitkommawerte darstellen (normalerweise IEEE 754-style
float
unddouble
). Andere Teile verhalten sich so, als wären sie gebündelte Daten eines Formats, von Zeichen, Tabelleneinträgen, CPU-Anweisungen oder Adressen.Auf einem typischen 64-Bit-Computer sind Bündel von 8 Bytes (64 Bit) Adressen. Wir zeigen diese Adressen konventionell als Hex-Format (wie
0xabcd1234cdef5678
) an, aber das ist nur eine einfache Möglichkeit für Menschen, die Bitmuster zu lesen. Jedes Byte (8 Bit) wird als zwei Hexadezimalzeichen geschrieben (entsprechend stellt jedes Hexadezimalzeichen - 0 bis F - 4 Bit dar).Was tatsächlich vor sich geht (für ein gewisses Maß an tatsächlichem) ist, dass es Bits gibt, die normalerweise in einem Register oder an benachbarten Orten in einer Speicherbank gespeichert sind, und wir versuchen nur, sie einem anderen Menschen zu beschreiben.
Das Folgen eines Zeigers besteht darin, den Speichercontroller aufzufordern, uns einige Daten an diesem Ort zu geben. Normalerweise fragt man den Speichercontroller nach einer bestimmten Anzahl von Bytes an einem bestimmten Ort (na ja, implizit einem Bereich von Orten, normalerweise zusammenhängend), und er wird über verschiedene Mechanismen bereitgestellt, auf die ich nicht eingehen werde.
Der Code gibt normalerweise ein Ziel für die abzurufenden Daten an - ein Register, eine andere Speicheradresse usw. - und normalerweise ist es eine schlechte Idee, Gleitkommadaten in ein Register zu laden, das eine Ganzzahl erwartet, oder umgekehrt.
Der Typ der Daten in C / C ++ wird vom Compiler verfolgt und ändert, welcher Code generiert wird. Normalerweise enthalten die Daten nichts Eigenes, was sie tatsächlich zu einem bestimmten Typ macht. Nur eine Sammlung von Bits (in Bytes angeordnet), die vom Code ganzzahlig (oder floatartig oder adressenartig) manipuliert werden.
Hiervon gibt es Ausnahmen. Es gibt Architekturen , bei denen bestimmte Dinge eine andere sind Art von Bits. Das gebräuchlichste Beispiel sind geschützte Ausführungsseiten - während Anweisungen, die der CPU mitteilen, was zu tun ist, Bits sind, können zur Laufzeit die (Speicher-) Seiten, die auszuführenden Code enthalten, nicht geändert werden, und Sie können keine Seiten ausführen, die nicht markiert sind als Ausführungsseiten.
Es gibt auch Nur-Lese-Daten (manchmal im ROM gespeichert, in die physisch nicht geschrieben werden kann!), Ausrichtungsprobleme (einige Prozessoren können keine
double
s aus dem Speicher laden, wenn sie nicht auf bestimmte Weise ausgerichtet sind, oder SIMD-Anweisungen, die eine bestimmte Ausrichtung erfordern) und Unmengen von andere architektonische Macken.Sogar die obige Detailebene ist eine Lüge. Computer bewegen sich nicht "wirklich" um Bits, sondern um Spannungen und Ströme. Diese Spannungen und Ströme erfüllen manchmal nicht das, was sie auf der Abstraktionsebene von Bits "tun" sollen. Die Chips sollen die meisten dieser Fehler erkennen und korrigieren, ohne dass die übergeordnete Abstraktion sich dessen bewusst sein muss.
Auch das ist eine Lüge.
Jede Abstraktionsebene verbirgt die folgende und lässt Sie über das Lösen von Problemen nachdenken, ohne Feynman-Diagramme berücksichtigen zu müssen, um sie auszudrucken
"Hello World"
.Bei einem ausreichenden Maß an Ehrlichkeit drücken Computer Bits, und diese Bits haben eine Bedeutung in der Art und Weise, wie sie verwendet werden.
quelle
Die Leute haben eine große Rolle gespielt, ob Zeiger ganze Zahlen sind oder nicht. Es gibt tatsächlich Antworten auf diese Fragen. Sie müssen jedoch einen Schritt in das Land der Spezifikationen tun, das nichts für schwache Nerven ist. Wir werfen einen Blick auf die C-Spezifikation ISO / IEC 9899: TC2
6.3.2.3 Zeiger
Eine Ganzzahl kann in einen beliebigen Zeigertyp konvertiert werden. Sofern nicht anders angegeben, ist das Ergebnis implementierungsdefiniert, möglicherweise nicht korrekt ausgerichtet, verweist möglicherweise nicht auf eine Entität des referenzierten Typs und ist möglicherweise eine Trap-Darstellung.
Jeder Zeigertyp kann in einen Integer-Typ konvertiert werden. Sofern nicht anders angegeben, ist das Ergebnis implementierungsdefiniert. Wenn das Ergebnis nicht im Integer-Typ dargestellt werden kann, ist das Verhalten undefiniert. Das Ergebnis muss nicht im Wertebereich eines Integer-Typs liegen.
Hierfür müssen Sie einige allgemeine Fachbegriffe kennen. "Implementierung definiert" bedeutet, dass jeder einzelne Compiler es anders definieren darf. Tatsächlich kann ein Compiler diese sogar auf unterschiedliche Weise definieren, abhängig von Ihren Compilereinstellungen. Undefiniertes Verhalten bedeutet, dass der Compiler absolut alles tun darf, von der Angabe eines Kompilierungsfehlers über unerklärliche Verhaltensweisen bis hin zur einwandfreien Funktion.
Daraus können wir ersehen, dass das zugrunde liegende Speicherformular nicht angegeben ist, außer dass möglicherweise eine Konvertierung in einen Integer-Typ erfolgt. Um ehrlich zu sein, stellt praktisch jeder Compiler unter der Sonne Zeiger unter der Haube als Ganzzahladressen dar (mit einer Handvoll Sonderfällen, in denen sie möglicherweise als 2 Ganzzahlen statt nur als 1 dargestellt werden), aber die Spezifikation erlaubt absolut alles, wie zum Beispiel das Darstellen Adressen als 10-stellige Zeichenfolge!
Wenn wir aus C heraus schnell vorspulen und uns die C ++ - Spezifikation ansehen, erhalten wir ein wenig mehr Klarheit
reinterpret_cast
, aber dies ist eine andere Sprache, sodass ihr Wert für Sie variieren kann:ISO / IEC N337: C ++ 11 Entwurfsspezifikation (ich habe nur den Entwurf zur Hand)
5.2.10 Besetzung neu interpretieren
Ein Zeiger kann explizit in einen ganzzahligen Typ konvertiert werden, der groß genug ist, um ihn zu halten. Die Zuordnungsfunktion ist implementierungsdefiniert. [Hinweis: Es ist nicht überraschend für diejenigen, die die Adressierungsstruktur des zugrunde liegenden Computers kennen. —End note] Ein Wert vom Typ std :: nullptr_t kann in einen ganzzahligen Typ konvertiert werden. Die Konvertierung hat die gleiche Bedeutung und Gültigkeit wie eine Konvertierung von (void *) 0 in den Integraltyp. [Hinweis: Mit einem reinterpret_cast kann kein Wert eines Typs in den Typ std :: nullptr_t konvertiert werden. —Ende Notiz]
Ein Wert vom Typ Integral oder vom Typ Aufzählung kann explizit in einen Zeiger konvertiert werden. Ein Zeiger, der in eine Ganzzahl von ausreichender Größe konvertiert wurde (sofern in der Implementierung eine solche vorhanden ist) und auf denselben Zeigertyp zurückgeht, hat seinen ursprünglichen Wert. Zuordnungen zwischen Zeigern und Ganzzahlen sind ansonsten durch die Implementierung definiert. [Hinweis: Außer wie in 3.7.4.3 beschrieben, ist das Ergebnis einer solchen Konvertierung kein sicher abgeleiteter Zeigerwert. —Ende Notiz]
Wie Sie hier sehen können, stellte C ++ nach einigen Jahren sicher, dass eine Zuordnung zu ganzen Zahlen vorhanden ist, sodass nicht mehr von undefiniertem Verhalten die Rede ist (obwohl es einen interessanten Widerspruch zwischen den Teilen 4 und 6 gibt) 5 mit der Formulierung "falls solche in der Implementierung vorhanden sind")
Nun, was solltest du davon wegnehmen?
Die beste Wette: Casting zu einem (char *). Die C- und C ++ - Spezifikationen sind voll von Regeln, die das Packen von Arrays und Strukturen spezifizieren, und beide erlauben immer das Casting eines Zeigers auf ein char *. char ist immer 1 Byte (nicht garantiert in C, aber in C ++ 11 ist es ein vorgeschriebener Teil der Sprache geworden, daher ist es relativ sicher anzunehmen, dass es überall 1 Byte ist). Auf diese Weise können Sie eine Zeigerarithmetik auf Byte-für-Byte-Ebene ausführen, ohne die implementierungsspezifischen Darstellungen von Zeigern kennen zu müssen.
quelle
char *
? Ich denke an eine hypothetische Maschine mit separaten Adressräumen für Code und Daten.char
ist in C immer 1 Byte. Zitat aus dem C-Standard: "Der Operator sizeof gibt die Größe (in Bytes) seines Operanden an" und "Wenn sizeof auf einen Operanden angewendet wird, der den Typ char, unsigned char oder signed char hat, (oder eine qualifizierte Version davon) ist das Ergebnis 1. " Vielleicht denken Sie, ein Byte ist 8 Bit lang. Das ist nicht unbedingt der Fall. Um dem Standard zu entsprechen, muss ein Byte mindestens 8 Bits enthalten.Auf den meisten Architekturen existiert der Typ eines Zeigers nicht mehr, sobald er in Maschinencode übersetzt wurde (mit Ausnahme von möglicherweise "Fettzeigern"). Daher wäre ein Zeiger auf einen
int
nicht von einem Zeiger auf einen zu unterscheidendouble
, zumindest nicht von sich aus . *[*] Allerdings kannst du immer noch Vermutungen anstellen, basierend auf den Arten von Operationen, die du auf sie anwendest.
quelle
Es ist wichtig zu verstehen, welche Typen C und C ++ tatsächlich sind. Sie geben dem Compiler lediglich an, wie ein Satz von Bits / Bytes zu interpretieren ist. Beginnen wir mit folgendem Code:
Abhängig von der Architektur erhält eine Ganzzahl normalerweise 32 Bit Platz, um diesen Wert zu speichern. Das bedeutet, dass der Speicherplatz im Speicher, in dem var gespeichert ist, etwa wie "11111111 11111111 11111010 11000111" oder hexadezimal "0xFFFFFAC7" aussieht. Das ist es. Das ist alles, was an diesem Ort gespeichert ist. Sie müssen dem Compiler lediglich mitteilen, wie diese Informationen interpretiert werden sollen. Zeiger sind nicht anders. Wenn ich so etwas mache:
Dann ruft der Compiler den Speicherort von var ab und speichert diese Adresse auf dieselbe Weise, wie das erste Code-Snippet den Wert -1337 speichert. Es gibt keinen Unterschied darin, wie sie gespeichert werden, nur darin, wie sie verwendet werden. Es ist nicht einmal wichtig, dass ich var_ptr zu einem Zeiger auf ein int gemacht habe. Wenn du wolltest, könntest du tun.
Dadurch wird der obige Hex-Wert von var (0xFFFFFAC7) an die Position kopiert, an der der Wert von var2 gespeichert ist. Wenn wir dann var2 verwenden würden, würden wir feststellen, dass der Wert 4294965959 ist. Die Bytes in var2 sind die gleichen wie in var, aber der numerische Wert unterscheidet sich. Der Compiler interpretierte sie anders, weil wir sagten, dass diese Bits ein vorzeichenloses Long darstellen. Sie können dasselbe auch für den Zeigerwert tun.
In diesem Beispiel würden Sie den Wert, der die Adresse von var darstellt, als vorzeichenloses int interpretieren.
Hoffentlich klärt dies die Dinge für Sie und gibt Ihnen einen besseren Einblick, wie C funktioniert. Bitte beachten Sie, dass Sie KEINE der verrückten Sachen machen SOLLTEN, die ich in den folgenden zwei Zeilen im tatsächlichen Produktionscode gemacht habe. Das war nur zur Demonstration.
quelle
Ganze Zahl.
Der Adressraum in einem Computer wird beginnend bei 0 fortlaufend nummeriert und um 1 erhöht. Ein Zeiger enthält also eine Ganzzahl, die einer Adresse im Adressraum entspricht.
quelle
Typen kombinieren.
Insbesondere lassen sich bestimmte Typen so kombinieren, als wären sie mit Platzhaltern parametrisiert. Array- und Zeigertypen sind wie folgt; Sie haben einen solchen Platzhalter, der der Typ des Elements des Arrays bzw. des Objekts ist, auf das verwiesen wird. Funktionsarten sind auch so; Sie können mehrere Platzhalter für die Parameter und einen Platzhalter für den Rückgabetyp haben.
Eine Variable, die einen Zeiger auf char enthalten soll, hat den Typ "Zeiger auf char". Eine Variable, die einen Zeiger auf einen Zeiger auf int enthalten soll, hat den Typ "Zeiger auf einen Zeiger auf int".
Ein (Wert von) Typ "Zeiger auf Zeiger auf int" kann durch eine Dereferenzierungsoperation in "Zeiger auf int" geändert werden. Der Begriff Typ ist also nicht nur ein Wort, sondern ein mathematisch signifikantes Konstrukt, das vorgibt, was wir mit Werten des Typs tun können (z. B. Dereferenzierung, Übergabe als Parameter oder Zuweisung an Variable; er bestimmt auch die Größe (Byteanzahl) von Indizierungs-, Arithmetik- und Inkrementierungs- / Dekrementierungsoperationen).
PS Wenn Sie tief in die Typen eintauchen möchten, probieren Sie diesen Blog aus: http://www.goodmath.org/blog/2015/05/13/expressions-and-arity-part-1/
quelle