Created
December 6, 2013 22:10
-
-
Save wilhelmy/7832997 to your computer and use it in GitHub Desktop.
People kept asking, thought I could give explaining things a shot for once
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
How2Pointer | |
=========== | |
Speicher besteht aus linear angeordneten einzelnen Zellen, diese Zellen haben | |
Adressen, quasi der Index an der ein Wert steht. Adresswerte werden | |
üblicherweise im Hexadezimalsystem angegeben. Jede Zelle ist dabei 8 bit = 1 | |
byte breit. Im unten stehenden Diagramm sind dabei 4 1-byte-Werte | |
zu einem 32-bit breiten sog. "Maschinenwort" zusammengefasst, die Adressen | |
zwischen diesen Adressen zeigen aber dazwischen (z.B. sind sowohl 0x0F5023A0 als | |
auch 0x0F5023A1 gültige Adressen, obwohl unten einige Werte dazwischen | |
übersprungen werden). Die Breite des Maschinenwortes entspricht dabei nicht | |
notwendigerweise der Adressbreite der Architektur (sicher habt ihr schonmal | |
etwas von 32-bit und 64-bit Prozessoren gehört), diese definiert dagegen, wie | |
groß Adressen sind die der Prozessor maximal ansprechen kann. Offensichtlich | |
kann es aber sinnvoll sein, Adressbreite und Breite des Maschinenwortes gleich | |
zu wählen, wenn man einen Prozessor designt. (Muss aber nicht, z.B. wegen | |
Abwärtskompatibilität) | |
Speicherzelle Adresse | |
+-------------+ | |
| | 0x00000000 NULL-Pointer (*) | |
| undefiniert | 0x00000001 (dieser Bereich ist nicht notwendig undefiniert, | |
| | 0x00000002 aber es gibt Speicher, der nicht definierte Werte | |
| | 0x00000003 enthält. Beim Programmstart sind alle Werte un- | |
. . . definiert bzw. uninitialisiert, daher gibt | |
. . . int x; printf("%d", x); irgendwelchen Blödsinn | |
. . . aus) | |
| 0x000000012 | 0x0F5023A0 | |
| 0x000000011 | 0x0F5023A4 | |
| 0x000000042 | 0x0F5023A8 Hier stehen irgendwelche Werte | |
| 0x000001337 | 0x0F5023AB | |
| | 0x0F5023B0 | |
| | 0x0F5023B4 | |
. . | |
. . | |
. . | |
+-------------+ | |
Im Adressbereich zwischen 0x0F5023A0 und 0x0F5023AB stehen dabei Werte, die im | |
Programm benutzt werden. (Unter der Annahme, dass dieser Speicher im | |
Adressbereich des Programms liegt. Da auf einem Computer mehrere Programme | |
gleichzeitig geladen sein können, wird der Adressraum unterteilt und jedem | |
Programm ein Adressbereich zugewiesen, der später bei Bedarf vergrößert oder | |
verkleinert werden kann. Das tun in C z.B. malloc, realloc und free bzw. die | |
systemspezifischen Funktionen, die diese drei intern aufrufen). | |
Ein Pointer auf int, z.B. int *a deklariert dabei eine Variable a vom Typ | |
"int *", was nicht das gleiche wie int ist. Dabei wird ein Eintrag im Speicher | |
angelegt, der breit genug ist um einen Zeiger aufzunehmen, also üblicherweise so | |
breit wie die Adressbreite der benutzten Prozessorarchitektur. Dieser Eintrag | |
nimmt Adressen auf, an denen ein int steht. Beispiel: | |
int a; | |
int *A = &a; | |
Hier wird eine int-Variable deklariert (d.h. Speicherplatz reserviert, auf | |
Prozessoren mit intel x86-Architektur typischerweise 4 byte = 32 bit), die nicht | |
initialisiert wird, also keine sinnvollen Daten enthält und anschließend wird | |
eine int*-Variable A deklariert, der die Adresse der Variablen a zugewiesen | |
wird. Anschließend kann indirekt über den Zeiger auf den Inhalt von a | |
zugegriffen werden, z.B.: | |
*A = 12; | |
printf("%d %d\n", a, *A); | |
Dies gibt 12 12 aus. | |
Insbesondere ist es wichtig, die Operatorpräzedenz (d.h. Ausführungsreihen- | |
folge) zu beachten: Der Inrekement-Operator ++ hat höhere Präzedenz als der | |
Zeiger-Dereferenzierungs- Operator *, d.h. | |
*A++ | |
hat möglicherweise nicht den erwarteten Effekt, dass zuerst A dereferenziert | |
wird und anschließend der an dieser Position stehende Wert inkrementiert wird, | |
stattdessen wird die Zeigervariable selbst um die Größe des Typs in byte erhöht, | |
auf den sie zeigt (in diesem Fall int, also 4) und anschließend dereferenziert. | |
Dies greift im obigen Fall schon auf undefinierten Speicher zu, weil hinter a | |
nicht unbedingt etwas im Speicher steht. Richtig wäre in diesem Fall: | |
(*A)++ | |
Wichtig ist im Pointer-Kontext auch, dass Arrays sequentiell im Speicher liegen, | |
wenn also A ein Pointer auf den ersten Wert eines int-Arrays ist, zeigt A+1 auf | |
den zweiten. Dabei wird ebenfalls die Größe des von A referenzierten Typs | |
berücksichtigt, d.h. wenn A vom Typ int* ist, zeigt A+1 4 bytes weiter. Der | |
Index-Operator [] tut nichts anderes als den Wert, der in den Klammern steht | |
wiefolgt zu dereferenzieren: | |
A[i] == *(A + i) | |
also insbesondere zeigen A+i und A[i] auch auf die gleiche Adresse: | |
&A[i] == A+i | |
Diese Gleichheit gilt immer, wenn der Index-Operator benutzt wird, also auch | |
auf Arrays, bzw ist der Indexoperator tatsächlich genau so definiert, dass | |
A[ausdruck] | |
zu | |
*(A + (ausdruck)) | |
expandiert wird. | |
-- | |
Autor: moritz barfooze de, insert an at and a dot |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment