Netzflut

Webdesign, Webentwicklung, Drupal & Zeuchs

Notizen zu Zeigern in C

Inhaltsverzeichniss

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

    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
    2
    3
    
    #define LAENGE 26
    char string [LAENGE] = "Informationsverarbeitung2" ;
    char * 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

    [21:40] <ChTerka> [17:58] <ChTerka|afk> char[] kann aber immer in char* impliziert umgewandelt werden da nur die Anzahl der Elemente wegfallen muss
    [21:40] <ChTerka> [17:59] <Freundlich> char* legt schon Speicher an, nämlich den für eine Adresse. :)
    [21:40] <ChTerka> [17:59] <Freundlich> char[n] legt Speicher für mindestens n char an.
    [21:40] <ChTerka> [17:59] <ChTerka|afk> logo - ich meinte... du weisst schon
    [21:41] <ChTerka> [18:01] <ChTerka|afk> kann auch char** oder char[][] sein

     

     

    Design&Code: 
    Tagging: