Welche Art von Daten enthalten Zeiger in der Sprache C?

30

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.)

Gold_Sky
quelle
17
Zeiger sind keine Variablen , sondern Werte . Variablen enthalten Werte (und wenn ihr Typ ein Zeigertyp ist, ist dieser Wert ein Zeiger und kann die Adresse einer Speicherzone sein, die etwas Bedeutendes enthält). Eine bestimmte Speicherzone kann verwendet werden, um verschiedene Werte verschiedener Typen zu speichern.
Basile Starynkevitch
29
"Die Adressen sind hexadezimal formatiert" Nein, das ist nur der Debugger oder eine Bibliothek, die Bits formatiert. Mit dem gleichen Argument könnte man sagen, dass sie binär oder oktal sind.
usr
Fragen Sie besser nach dem Format , nicht nach dem Typ . Daher hier ein paar Antworten von der Piste ... (obwohl Kilian's genau das Richtige ist).
Leichtigkeit Rennen mit Monica
1
Ich denke, das tiefere Problem hier ist das Verständnis der Art von OP . Wenn es darum geht, sind die Werte, die Sie in Ihrem Programm bearbeiten, nur Bits im Speicher. Typen sind die Art und Weise, wie der Programmierer dem Compiler mitteilt, wie diese Bits beim Generieren von Assembler-Code zu behandeln sind.
Justin Lardinois
Ich nehme an, es ist zu spät, um es mit all diesen Antworten zu bearbeiten, aber diese Frage wäre besser gewesen, wenn Sie die Hardware und / oder das Betriebssystem eingeschränkt hätten, zum Beispiel "unter x64 Linux".
Hyde

Antworten:

64

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.

pjc50
quelle
2
Sie dürfen auch den Unterschied zwischen zwei Zeigern nehmen p1-p2. Das Ergebnis ist ein vorzeichenbehafteter Integralwert. Insbesondere&(array[i])-&(array[j]) == i-j
MSalters
13
Tatsächlich wird auch die Konvertierung in einen ganzzahligen Typ angegeben, intptr_tund zwar uintptr_tfür Zeigerwerte, die garantiert "groß genug" sind.
Matthieu M.
3
Sie können sich auf die Konvertierung in Arbeit verlassen, aber die Zuordnung zwischen Ganzzahlen und Zeigern ist implementierungsspezifisch. (Die einzige Ausnahme ist 0 -> null, und selbst das wird nur angegeben, wenn die 0 eine konstante IIRC ist.)
cHao
7
Das Hinzufügen des pSpezifizierers zu printf macht das Abrufen einer für den Menschen lesbaren Darstellung eines Leerzeigers zu einem definierten, wenn implementierungsabhängigen Verhalten in c.
dmckee
6
Diese Antwort hat die allgemein richtige Idee, scheitert aber an den spezifischen Ansprüchen. Das Erzwingen eines Zeigers auf einen ganzzahligen Typ ist kein undefiniertes Verhalten, und Windows HANDLE-Datentypen sind keine Zeigerwerte (es handelt sich nicht um Zeiger, die in ganzzahligen Datentypen verborgen sind, sondern um Ganzzahlen, die in Zeigertypen verborgen sind, um Arithmetik zu verhindern).
Ben Voigt
44

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).

Kilian Foth
quelle
26
Adressen sind definitiv nicht nur ganze Zahlen. Genauso wie Gleitkommazahlen definitiv nicht nur ganze Zahlen sind .
gnasher729
8
Tatsächlich. Das bekannteste Gegenbeispiel ist der Intel 8086, bei dem Zeiger zwei ganze Zahlen sind.
MSalters
5
@Rob In einem segmentierten Speichermodell kann ein Zeiger entweder ein einzelner Wert (eine Adresse relativ zum Beginn des Segments; ein Offset) mit dem implizierten Segment oder ein Segment / Selektor / Offset- Paar sein . (Ich glaube, Intel hat den Begriff "Selektor" verwendet. Ich bin zu faul, um ihn nachzuschlagen.) Auf dem 8086 wurden diese als zwei 16-Bit-Ganzzahlen dargestellt, die zusammen eine physikalische 20-Bit-Adresse bilden. (Ja, Sie könnten dieselbe Speicherzelle auf viele, viele verschiedene Arten adressieren, wenn Sie dazu geneigt wären: address = (Segment << 4 + Offset) & 0xfffff.) Dies wird im Real-Modus über alle x86-Kompatiblen übertragen.
ein CVn
4
Als langjähriger Assembler-Programmierer kann ich bezeugen, dass der Arbeitsspeicher des Computers nichts anderes als Speicherorte mit ganzen Zahlen ist. Es ist jedoch wichtig, wie Sie sie behandeln und nachverfolgen, was diese ganzen Zahlen darstellen. Beispiel: Auf meinem System wird die Dezimalzahl 4075876853 als x'F2F0F1F5 'gespeichert. Dies ist die Zeichenfolge' 2015 'in EBCDIC. Die Dezimalzahl 2015 wird als 000007DF gespeichert, während x'0002015C 'die Dezimalzahl 2015 im gepackten Dezimalformat darstellt. Als Assembler-Programmierer müssen Sie diese nachverfolgen. Der Compiler erledigt dies für HL-Sprachen.
Steve Ives
7
Adressen können in eine Eins-zu-Eins-Korrespondenz mit ganzen Zahlen gesetzt werden, aber auch alles andere auf einem Computer :)
hobbs
15

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:

  1. Ein Zeiger kann die Adresse eines Objekts sein.
  2. Ein Zeiger kann direkt hinter das letzte Element eines Arrays zeigen.
  3. Ein Zeiger kann ein Nullzeiger sein, was bedeutet, dass er auf nichts zeigt.
  4. Ein Zeiger kann einen unbestimmten Wert haben, mit anderen Worten, es ist Müll, und alles kann passieren (einschließlich schlimmer Dinge), wenn Sie versuchen, ihn zu verwenden.

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.

gnasher729
quelle
4
Es gibt Zeiger auf Funktionen und in C ++ Zeiger auf Member.
Sdenham
C ++ - Zeiger auf Mitglieder sind keine Speicheradressen? Sicher sind sie. class A { public: int num; int x; }; int A::*pmi = &A::num; A a; int n = a.*pmi;Die Variable pmiwäre nicht sehr nützlich, wenn sie keine Speicheradresse enthalten würde, nämlich, wie die letzte Zeile des Codes festlegt, die Adresse des Mitglieds numder Instanz ader Klasse A. Sie können dies in einen normalen intZeiger 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).
dodgethesteamroller
9

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.

Peter Wone
quelle
Nitpick, heutzutage unter normalen Betriebssystemen, gibt den Speicherort im virtuellen Speicher an und hat nichts direkt mit einem physischen RAM-Wort zu tun (der virtuelle Speicherort existiert möglicherweise nicht einmal physisch, wenn er sich auf einer Speicherseite befindet, von der bekannt ist, dass das Betriebssystem alle Nullen aufweist ).
Hyde
@hyde - Ihre Argumentation hat in dem Kontext, in dem Sie sie offensichtlich beabsichtigt haben, ihre Berechtigung, aber die dominierende Form des Computers ist der eingebettete Controller, in dem Wunder wie virtueller Speicher aktuelle Innovationen mit begrenzter Bereitstellung sind. Außerdem hilft das, worauf Sie in keiner Weise hingewiesen haben, dem OP, die Hinweise zu verstehen. Ich dachte, ein historischer Kontext würde alles viel weniger willkürlich machen.
Peter Wone
Ich weiß nicht, ob das Sprechen über RAM dem OP hilft, zu verstehen, da die Frage spezifisch ist, was Zeiger wirklich sind. Oh, ein weiterer Nitpick in c zeigt per Definition auf das Byte (kann char*zum Kopieren / Vergleichen in den Speicher und sizeof char==1gemäß C-Standard sicher umgewandelt werden ), nicht auf das Wort (es sei denn, die CPU-Wortgröße entspricht der Bytegröße).
Hyde
Grundsätzlich handelt es sich bei den Zeigern um Hash-Schlüssel für die Speicherung. Dies ist sprach- und plattformunabhängig.
Peter Wone
Die Frage handelt von c- Zeigern . Und Zeiger sind definitiv keine Hash-Schlüssel, weil es keine Hash-Tabelle und keinen Hash-Algorithmus gibt. Es handelt sich natürlich um eine Art Karten- / Wörterbuchschlüssel (für eine ausreichend breite Definition von "Karte"), jedoch nicht um Hash- Schlüssel.
Hyde
6

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 floatund double). 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 doubles 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.

Yakk
quelle
3

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

  1. 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.

  2. 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

  1. 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]

  2. 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 genaue Darstellung der Zeiger ist implementierungsdefiniert. (in der Tat, nur um es unordentlicher, einige kleine Embedded Computer repräsentieren den Null - Zeiger, (void ) 0, als Adresse 255 einige Adressen Aliasing Tricks unterstützen sie verwenden) *
  • Wenn Sie nach der Darstellung von Zeigern im Gedächtnis fragen müssen, sind Sie wahrscheinlich nicht an einem Punkt Ihrer Programmierkarriere, an dem Sie mit ihnen experimentieren möchten.

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.

Cort Ammon - Setzen Sie Monica wieder ein
quelle
Können Sie unbedingt einen Funktionszeiger auf a setzen char *? Ich denke an eine hypothetische Maschine mit separaten Adressräumen für Code und Daten.
Philip Kendall
@ PhilipKendall Guter Punkt. Ich habe diesen Teil der Spezifikation nicht aufgenommen, aber Funktionszeiger werden aufgrund genau des von Ihnen angesprochenen Problems als etwas völlig anderes behandelt als Datenzeiger in der Spezifikation. Zeiger von Mitgliedern werden auch unterschiedlich behandelt (aber sie verhalten sich auch sehr unterschiedlich)
Cort Ammon - Reinstate Monica
A charist 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.
David Hammen
Die Spezifikation beschreibt die Konvertierung zwischen Zeiger- und Ganzzahltypen. Es sollte immer bedacht werden, dass eine "Konvertierung" zwischen Typen keine Gleichheit der Typen impliziert, und auch nicht, dass eine binäre Darstellung der beiden Typen im Speicher dasselbe Bitmuster haben würde. (ASCII kann in EBCDIC "konvertiert" werden. Big-Endian kann in Little-Endian "konvertiert" werden.
Usw.
1

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 intnicht von einem Zeiger auf einen zu unterscheiden double, zumindest nicht von sich aus . *

[*] Allerdings kannst du immer noch Vermutungen anstellen, basierend auf den Arten von Operationen, die du auf sie anwendest.

Rufflewind
quelle
1

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:

int var = -1337;

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:

int* var_ptr = &var;   //the ampersand is telling C "get the address where var's value is located"

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.

unsigned int var2 = *(unsigned int*)var_ptr;

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.

unsigned int var3 = (unsigned int)var_ptr;

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.

NULL
quelle
1

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.

galois
quelle
1

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/

Erik Eidt
quelle