Notizen zu Zeigern in C

Mo., 03/06/2019 - 23:25
Body
Info: Alte ungeprüfte Notizen zu C und Zeigern. Erstellt damals als mir geholfen hatte.
 
Die Bestandteile einer Variablen

1. Bezeichner: Damit ist der Name der Variablen gemeint (z.B zahl1)
2. Bitbreite: int ,char, float, double
3. Interpretationswerk: Die Interpretation besagt, dass die 8 bit an Adresse xy
als char verstanden werden sollen, dass 1 bit Vorzeichen und der Rest Mantisse sind.
4. Adresse: Die Adresse im Speicher (z.b 1380545)
5. Sichtbarkeitsbereich: Lokale Variablen existieren nur in einem Sichtbarkeitsbereich (Globale Variablen sind dagegen überall sichtbar (verfügbar))
6. Klassifizierer: static, const, volatile usw. Klassifizierer machen verschiedenes und sind nur ein flagset. Const besagt z.B. das die
Zahl nie verändert wird und so im Maschinencode freizügig optimiert werden darf.
7. Anzahl: Anzahl bedeutet wieviele davon an der genannten Adresse zu finden sind. Bei normalen Variablen ist die immer 1. Zeiger haben immer 0 als (also unbekannte) Anzahl. Bsp. int[50] hätte als Anzahl 50.

Stack oder Heap:
Bei einer Programmierung wo man von Anfang an die größen von Arrays und Variablen zur Compilierzeit feststehen benutzt man den STACk Speicher. Wenn man Speicher während der Laufzeit anfordert kommt dieser von HEAP. Wenn du z.B mit malloc() die Adresse von neuem, dynamischen SPeicher anforderst dann kommt dieser von der HEAP (Halde). Malloc() gibt die dann ein void* (Adresse ohne Interpretation) zurück.
Die größe des Stacks muss beim Compilieren feststehen und steht im Dateiheader jeder .exe und .dll Datei. Wenn ein Programm geladen wird, wird automatisch 'lokaler' Speicher mit der Größe aus dem Header angelegt. Parameter, die Funktionen bei ihrem aufruf übergeben landen auch auf dem Stack. Da der aber eine feste Größe hat, kann eine Rekursive Ffunktion den Stack 'sprengen'. Der typische Stack Overflow. Stack ist nach einem bestimmten System sortiert. Globale und statische Variablen landen unten und werden mit einem positiven 'Index' angesprochen. Wenn in deinem code eine '}' erreicht wird, werden alle lokalen Variablen, wie seit der letzten '{' erstellt wurden gelöscht. Sie werden vom Stabel entfernt

Beispiele für die Bestandteile von Variablen

int i;
Bezeichner: i
Bitbreite : 32 bit (4 byte)
Interpretationswerk: int
Adresse: z.b 2457399
Stack oder Heap: Stack
Sichtbarkeitsbereich: lokal
Klassifizierer: keiner
Anzahl: 1

int *pInt;
Bezeichner: pInt
Bitbreite : 32 bit (4 byte)
Interpretationswerk: int
Adresse: noch keine
Stack oder Heap: Stack
Sichtbarkeitsbereich: lokal
Klassifizierer: keiner
Anzahl: 1

void* pUnknown = NULL;
Bezeichner: pUnknown
Bitbreite : noch keine
Interpretationswerk: noch keines
Adresse: 0 (ungültig)
Stack oder Heap: Stack
Sichtbarkeitsbereich: lokal
Klassifizierer: keiner
Anzahl: 1

Pointer

Back2basic: Jede 'normale' Variable erstellt beim Anlegen Speicher für den Wert und die Adresse in der Variablenbeschreibung ist automatisch die Adresse, an der der Speicher erstellt wurde. Jeder Pointer erstellt keinen Speicher für einen Wert und daher ist die Adresse erstmal zufall (Datenmüll). Ein Zeiger der nicht void ist, weiss welche Interpretation und Bitbreite der Wert an der Adresse hat.Man spricht davon, dass der Zeiger typenbehaftet ist, wenn ein Zeiger z.B int ist. Mmit typenbehafteten Adressen kann man sogenannte Adress-Algorithmik machen.Die ist rasend schnell und erlaubt viele Tricks, die in anderen Sprachen unmöglich sind.

Besipiel 1

  1. 1
  2. 2
  3. 3
  1. <strong> int</strong> nArray[20];
  2. <strong> int</strong>* pInt = nArray;
  3.  pInt++;

Was tut dieser Codeteil ? pInt zeigt nArray[0]. Nach dem ersten Durchlauf zeigt pInt auf nArray[1]. Nun die Frage wie dass zustande kommt. Ganz einfach, pInt zeigt auf die Adresse von nArray[0]. Das pInt++ bewirkt nun in diesem fall nicht das zu pInt eins dazu addiert wird. Es bewirkt, dass sich pInt um 4 byte (wegen int) verschiebt und nun auf die Adresse von nArray[1] zeigt.

MERKE: DER NAME EINES ARRAYS IST EIN ZEIGER AUF'S ERSTE ELEMENT
Pointer benutzen also in ihrer Variablenbeschreibung einfach Adressen von anderen Variablen die dort ihre eigenen Daten abgelegt haben

Zeigeralgorithmik:

Jeder Zeiger der typenbehaftet ist kann Zeigeralgorithmik. Bei pInt+1 (pInt++) wird nicht die Adresse einfach plus 1 gerechnet sondern die Adresse um die Bytegröße des Typen verschoben (in unserem obigen Besispiel 4 byte) .Damit zeigt pPointer+1 immer auf das 2. Element im Array, egal wie groß der Datentyp ist. Mit * vor einem Zeiger wandelt man diesen auf Werteebene und greift auf den Wert über das entsprechende Interpretationswerk zu.

Beispiel 2

  1. 1
  2. 2
  3. 3
  1. <strong> int</strong> i = 5;
  2. <strong> int</strong> *pInt = &i;
  3.  *pInt = 6;

Was geschieht hier? Wir weisen i den Wert 5 zu. Dann weisen wir einem Pointer auf int mit Namen pInt die Adresse von i zu. Anschließen weisen wir pInt den Wert 6 zu. Wenn man die Variable i nun ausgeben würde würde man nichtmehr den Wert 5 erhalten sondern 6.

MERKE:

Kein Pointer weiss, wie gross das Array ist, auf das er zeigt. Folgendes Beispiel erzeugt also einen Buffer Overflow!!

  1. 1
  2. 2
  3. 3
  4. 4
  1. <strong> int</strong> i;
  2. <strong> int</strong> *pInt = &i;
  3.  pInt++;
  4.  pInt = 4;

pInt zeigt hier auf einen einzelnen Integer.Wenn man pInt um die Grösse einer int verschiebt, ist man mit der Adresse irgendwo. MERKE: Kein Zeiger weiss, wieviele Elemente an der Stelle sind wo er hinzeigt und auch nicht auf das wievielte Element eines Arrays er zeigt. Er kennt nur Bitbreite, Interpretation und Adresse aber nicht mehr.


Beispiel 3

  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14
  15. 15
  16. 16
  17. 17
  18. 18
  19. 19
  20. 20
  21. 21
  22. 22
  23. 23
  24. 24
  25. 25
  26. 26
  27. 27
  28. 28
  29. 29
  1. /* Dieses Programm zählt alle Zeichen die in einer Textdatei vorhanden sind*/
  2.  
  3. <strong>#include <stdio.h></strong>
  4. <strong>#include <string.h></strong>
  5.  
  6. <strong>int</strong> main()
  7. <strong>{</strong>
  8.  
  9. <strong>char</strong> szPattern[256];
  10. <strong>int</strong> g_Lookup[256];
  11. <strong>char</strong> *lookup = szPattern;
  12.  
  13.  
  14. strcpy(szPattern , "Hallo!");
  15. memset(g_Lookup, 0, <strong>sizeof</strong>(<strong>int</strong>)*256);   // Speicher des gesamten Arrays auf '0' setzen
  16.  
  17. <strong>while</strong>(*lookup)                          // Solange der Inhalt der Adresse keine 0 enthält ('/0')
  18.  <strong>{</strong>                                      // ZUSATZ: *szPattern == &szPattern[0]
  19.   ++g_Lookup[*lookup];                  // Inkrementieren wir den entsprechenden Eintrag im Lookup
  20.   ++lookup;                             // und schieben die Adresse um die Breite einer <strong>int</strong> weiter
  21.  <strong>}</strong>
  22.  
  23.  
  24.  <strong>for</strong>(<strong>int</strong> i=0; i<256; i++)
  25.   <strong>{</strong>
  26.    printf("Der Buchstabe %c ist %d mal vorhanden\n",i , g_Lookup[i]);
  27.   <strong>}</strong>
  28.  <strong>return</strong> 0;
  29. <strong>}</strong>

Dies hier ist ein erweitertes Zeiger Beispiel. Das Programm zählt alle Zeichen welche mit Hilfe von strcpy(); in szPattern kopiert wurden. In Zeile 11 wird der Zeiger lookop deklariert. Er zeigt nun auf den Anfang des Arrays, da szPattern die Adresse des ersten Elements des Arrays enthält. Hier wird nicht wie bei Variablen ein & vor den Name verwendet da es sich um ein Array handelt.

MERKE: Der Name eines Arrays wird impliziert in einen Zeiger auf das erste Element umgewandelt. Damit ist der Name eines Arrays automatisch auf Adressebene.

In Zeile 17 wird eine while Schleife eingeleitet welche solange durchlaufen wird bis eine '/0' ( 0 entspricht FALSE) zurück gegeben wird. Das tritt dann ein wenn das Ende des Textes erreicht ist. Der Stern vor lookup schaltet, wie wir bereits wissen auf Werteebene um. Beim ersten Durchlauf ist der erste Buchstabe ist in diesem Fall das H. *lookup wird zu erst aufgelößt und liefert nun den Wert von H ( siehe Ascii Tabelle: 72). D.h nun steht da ++g_Lookup[72]. Das bedeutet das wir den Wert in g_Lookup[72] um 1 erhöhen. D.h nun ist g_Lookup[72] = 1. Diese Schleife arbeitet nun alle Buchstaben ab und zählt g_Lookup an der entsprechenden Stelle hoch (g_Lookup[ascii Wert] = Anzahl). Zeile 20 inkrementiert den Zeiger lookup um den wert eins Ints.

Ab Zeile 24 werden die Ergebnisse dan ganz einfach mit Hilfe einer for Schleife ausgegeben.

 

Implezierte und Explizierte Typenkonvertierung

Implezierte Konvertierung passiert automatisch oder im Hintergrund. Der Progger merkt manchmal gar nicht dass da was umgewandelt wird. Explizierte Konvertierung wird vom Progger extra geschrieben und somit von aussen in die Wege geleitet. (siehe Bestandteile einer Variablen, Anzahl)

Nehmen wir als Beispiel int a[50];. a ist hier nicht direkt ein Zeiger auf das erste Element, da ein Zeiger KEINE ANzahl kennt. a ist ein eigener Eintrag. Wenn man nun jedoch a als Zeiger benutzt wird impliziert (automatisch im Hintergrund runterkonvertiert). Das geht immer da nur die Anzahl wefallen muss. Daher passiert das auch im Hintergrund.

MERKE: Der name eine Arrays ist ein Zeiger auf's erste Element, das zusätzlich die Anzahl kennt und daher 100%ig kompatibel zu einem Zeiger auf's erste Element ist. Der eizigste Unterschied ist das a[] halt noch die Anzahl kennt und a* nicht. Daher sind es 2 verschiedene Einträge in der Typendatenbank, aber nicht 100%ig das gleiche. a[] ist abwärtskompatibel zu a*, aber a* nicht aufwärtskompatibel zu a[]



Array/ Zeiger Infos

nArray[3] ist das gleiche wie *(pArray+3)
[] sind eine kürzere Schreibweise für *(pInt+0)

 

Zusatz : IV ARBEIT

Bei einer Operationen werden char, short (und ihre unsigned Gegenstücke) in int konvertiert, falls der alle Werte aufnehmen kann, ansonsten in unsigned int, ansonsten in long, ansonsten in unsigned long.

Übung

  1. 1
  2. 2
  3. 3
  1. <strong>#define LAENGE 26</strong>
  2. <strong>char</strong> string [LAENGE] = "Informationsverarbeitung2" ;
  3. <strong>char</strong> * char_ptr = NULL ;

Frage: Welchen Typ und welchen Wert hat &string[LAENGE-1] - &string[0]
Antwort:
Zeiger werden immer in der Größe ihres Datentypen verschoben mit ++ oder -- oder auch +25. Wenn man 2 Zeiger voneinander subtrahiert passiert genau das gleiche. Also die Adresse vom 25. Element wird minus die Adresse vom 0. Element gerechnet. Damit ist string[0] der Bezugspunkt für string[25]. Und deshalb kommt da 25 raus obwohl die Adresse von string[0]=0xfe23232 sein kann, aber string[25] liegt 25 Elemente hinter der Adresse von string[0] und deswegen lautet die Lösung Typ: int, Wert: 25 .

Frage: Welchen Typ und welchen Wert hat *(char_ptr = ((&string[2])+10))
Antwort:
Zu erst lösen wir den rechten Teil auf. Mit +10 wird der Zeiger um 10 Elemente weitergeschoben (10 mal sizeof(char)). Hier steht dann string[12], was nun dem char_ptr zugewiesen wird. Durch den * wird das ganze nun auf Werteebene umwandelt wird.

 

Adden

 

char[] kann aber immer in char* impliziert umgewandelt werden da nur die Anzahl der Elemente wegfallen muss
char* legt schon Speicher an, nämlich den für eine Adresse. :)
char[n] legt Speicher für mindestens n char an.
logo - ich meinte... du weisst schon
kann auch char** oder char[][] sein
Tagging
Webdevelopment