Frage zu free(*Zeiger)



  • c_newbie schrieb:

    Hallo,

    Ein überarbeitetes Beispiel des ersten Beitrags, denn so kann ich dass ja nicht stehen lassen.

    #include <stdio.h>
    
    #define FULL 0xFF
    
    int main(){
    	int *x = NULL;
    	int **y = NULL;
    	int ***z = NULL;
    
    	x = malloc(1);
    	*x = FULL;
    	y = &x;
    	z = &y;
    	free(**z);
    	return 0;
    }
    

    syntaktisch richtig, semantisch aber falsch.

    mcr hat schon angemerkt, ist aber kein echter Fehler. malloc reserviert 1 Byte, ein int benötigt aber mindestens 4, d.h. der Zugrif auf diesen Speicherbereich über einen int Zeiger kann schief gehen, weil man auf mehr Bytes zugreift, als man reserviert hat und womöglich fremden Speicherbereich überschreiben (sehr böse!!!)

    Bei *x = FOO; tritt genau dieses Problem auf, denn es wird 0000 0000 0000 0000 0000 0000 FFFF FFFF geschrieben, aber nur die ersten 8 Bits dürfen von dir tatsächlich geschrieben werden.

    Betrachte folgendes:

    int *x, *ptr;
    
    x = malloc(1);
    ptr = malloc(sizeof(sizeof(int));
    
    Sei in der Abbildung m(x) == malloc(x);
    |---m(x)----| der Speicherbereich, der mit malloc(x) reserviert wurde und worauf der Zeiger zeigt
    
    lsb steht für Least Significant Byte und Bit
    msb steht für Most Significant Byte und Bit
    
    die Speicheradresse inkrementiert sich von links nach rechts!
    
    lsb                                        msb
    |--m(1)--|----------m(sizeof(int)------------|
    |........|........|........|........|........|
    

    Dein aufruf x = malloc(1); reserviert nur ein Byte, x zeigt auf den Anfang dieses Speicherbereiches. Du hast danach ptr = malloc(sizeof(int)) getan, dann werden sizeof(int) Bytes reserviert und ptr zeigt auf den Anfang dieses Speicherbereiches. Nun ist es aber so, dass der Anfang des zweiten reservierten Speicherbereiches gleich neben den ersten liegt.

    ACHTUNG!!!
    Theoretisch möglich, ob 2 malloc Aufrufe hinterher diese Konstellation hervorrufen, weiß ich nicht. Siehe http://www.c-plusplus.net/forum/viewtopic-var-p-is-1411608.html#1411608

    So nun machst du folgendes

    *ptr = 0xFF00AA00;
    

    Der Speicher wird also so aussehen:

    lsb                                        msb
    |--m(1)--|----------m(sizeof(int)------------|
    |........|FFFFFFFF|00000000|01010101|00000000|
    

    Und der Code geht so weiter

    *x = 0xFF;
    
    if(*ptr == 0)
    {
      starte_einen_Welt_Krieg();
      toete_so_viele_Zivilisten_wie_moeglich();
    } else 
      mehr_Brot_fuer_die_Welt();
    

    Der Speicher wird also so aussehen:

    lsb                                        msb
    |--m(1)--|----------m(sizeof(int)------------|
    |FFFFFFFF|00000000|00000000|00000000|00000000|
    

    ich hoffe, dass du dann ins Gefängnis wanderst, du Kriegsverbrecher 😉


  • Mod

    CStoll schrieb:

    JDHawk schrieb:

    Stimmt das so?

    Nein - offiziell vorgegeben sind nur Minimalgrößen für die Datentypen (afair short und int >= 2 Byte, long >= 4 Byte) und die Hierarchie char*<=short<=int<=long.

    * bei char ist definiert, daß es immer 1 Byte groß ist - und daß dieses Byte mindestens 8 Bit hat.

    Die Mindestgrößen ergeben sich indirekt über die (Mindest-)Zahlenbereiche, die in diesen Datentypen speicherbar sein müssen. Das sind daher Größen in Bits. Ein long hat mindestens 32 bit, denn der Zahlenbereich von -(232-1)...(232-1) [^ hier für Potenz] muss darin darstellbar sein. Über den Wert von siezof sagt das erst etwas aus, wenn man die Anzahl der Bits von char kennt - sollte ein char zufällig auch 32 bit haben, könnte sizeof(long) ohne Weiteres 1 sein.


  • Mod

    supertux schrieb:

    Nun ist es aber so, dass der Anfang des zweiten reservierten Speicherbereiches gleich neben den ersten liegt.

    Unsinn. Es gibt keinerlei Garantien hinsichtlich der relativen Lage beider Speicherbereiche (abgesehen davon, dass letzterer hinreichend ausgerichtet für ein int sein muss, was deine Darstellung extrem unwahrscheinlich macht).



  • camper schrieb:

    supertux schrieb:

    Nun ist es aber so, dass der Anfang des zweiten reservierten Speicherbereiches gleich neben den ersten liegt.

    Unsinn. Es gibt keinerlei Garantien hinsichtlich der relativen Lage beider Speicherbereiche (abgesehen davon, dass letzterer hinreichend ausgerichtet für ein int sein muss, was deine Darstellung extrem unwahrscheinlich macht).

    deswegen habe ich meinen Beitrag editiert und darauf hingewiesen, dass dieses Szenario möglich ist. Aber es geht nicht darum, ob zwei mallocs diese Konstellation erzeugen, sondern dass wenn du so etwas hast, dann überschreibst du fremden Speicher.

    Ich geb's zu, es ist sogar unwahrscheinlich, dass dieses Szenario so auftaucht, aber deswegen sollte man nicht schlamping sein und ein int *ptr = malloc(1); machen. Und darum geht es.



  • camper schrieb:

    Über den Wert von siezof sagt das erst etwas aus, wenn man die Anzahl der Bits von char kennt - sollte ein char zufällig auch 32 bit haben, könnte sizeof(long) ohne Weiteres 1 sein.

    Interessant zu wissen. Aber wann hat ein char 32bit?
    CStoll sagt ein char ist immer fest 1Byte groß.

    Sorry bin Anfänger und muss das fragen.



  • Hallo,

    mcr schrieb:

    x = malloc(1);
    

    Hier forderst du einen Speicherbereich von 1 Byte an. Jedoch macht es
    nur Sinn ein vielfaches von 4 Byte zu allozieren.

    Das bedeutet so wie der Zeiger Deklariert wird, muß ich ihn in malloc angeben?
                                   |                                         | 
                                   V                                         V 
    wenn                           int *x;              dann   malloc(sizeof(int));
    wenn                           unsigned char *x;    dann   malloc(sizeof(unsigned char));
    

    Auch wenn ich mich jetzt in die Nesseln setz, ein Zeiger hat doch eine feste Größe, also ob er jetzt als int, char, double,... Deklariert wird ist gleich und könnte im Fall von C auch während des Programm Ablaufs auf unterschiedliche Datentypen Zeigen, wenn der Zugriff vom Programm gesteuert wird?

    MFG matthi



  • JDHawk schrieb:

    Interessant zu wissen. Aber wann hat ein char 32bit?
    CStoll sagt ein char ist immer fest 1Byte groß.

    Ja, aber niemand schreibt vor, daß 1 Byte genau 8 Bit hat - laut Ansi-Standard sind das mindestens 8 Bit.

    Edit:

    c_newbie schrieb:

    Auch wenn ich mich jetzt in die Nesseln setz, ein Zeiger hat doch eine feste Größe, also ob er jetzt als int, char, double,... Deklariert wird ist gleich und könnte im Fall von C auch während des Programm Ablaufs auf unterschiedliche Datentypen Zeigen, wenn der Zugriff vom Programm gesteuert wird?

    Der Zeiger hat feste Größe, der Speicherbereich, auf den er verweist, normalerweise nicht. Und ein "T*" geht davon aus, mindestens sizeof(T) Platz zu haben, in den er Daten schreiben kann.

    (übrigens ist es normalerweise unsinnig, Platz für einzelne Werte über malloc() anzufordern - da reicht es aus, eine Stack-Variable anzulegen)



  • CStoll schrieb:

    JDHawk schrieb:

    Interessant zu wissen. Aber wann hat ein char 32bit?
    CStoll sagt ein char ist immer fest 1Byte groß.

    Ja, aber niemand schreibt vor, daß 1 Byte genau 8 Bit hat - laut Ansi-Standard sind das mindestens 8 Bit.

    Hehe das ist ja cool. Habe mal gehört: "C hält sich an keine Standards, C hält sich nur an Kernighan & Richie". Jetzt weis ich warum.

    Also ich gehe mal davon aus, dass sich alle seid den 60er Jahren zumindest darauf geeinigt haben das 1B=8b ist.



  • JDHawk schrieb:

    Also ich gehe mal davon aus, dass sich alle seid den 60er Jahren zumindest darauf geeinigt haben das 1B=8b ist.

    Die Zeit der 9-Bit-"Bytes" ist wohl vorbei, aber es gibt noch den einen oder anderen DSP mit 32-Bit-"Bytes".



  • CStoll schrieb:

    c_newbie schrieb:

    Auch wenn ich mich jetzt in die Nesseln setz, ein Zeiger hat doch eine feste Größe, also ob er jetzt als int, char, double,... Deklariert wird ist gleich und könnte im Fall von C auch während des Programm Ablaufs auf unterschiedliche Datentypen Zeigen, wenn der Zugriff vom Programm gesteuert wird?

    Der Zeiger hat feste Größe, der Speicherbereich, auf den er verweist, normalerweise nicht. Und ein "T*" geht davon aus, mindestens sizeof(T) Platz zu haben, in den er Daten schreiben kann.

    (übrigens ist es normalerweise unsinnig, Platz für einzelne Werte über malloc() anzufordern - da reicht es aus, eine Stack-Variable anzulegen)

    Was ich eigentlich machen wollte ist eine Verkettete Liste, wobei es dem Listen Element gleich sein soll "was" im Zeiger *daten gespeichert wird denn dass weiß ich vorher nicht bzw. weiß ich nicht wie groß das sein wird.

    typedef struct liste{
        int *daten;
        struct liste *l_next;
    }
    

    Wenn wir dass ganze nun umdrehen und in einem zu kleinen Zeiger einen zu großen Speicherbereich "ablegen" könnten doch keine Daten mehr überschrieben werden?

    Funktioniert in so einem Fall free(*Z) noch?

    MFG matthi



  • c_newbie schrieb:

    typedef struct liste{  //<-- typedef, dann auch ...
        int *daten;
        struct liste *l_next;
    }  // <-- name angeben!!!
    

    Wenn wir dass ganze nun umdrehen und in einem zu kleinen Zeiger einen zu großen Speicherbereich "ablegen" könnten doch keine Daten mehr überschrieben werden?

    Funktioniert in so einem Fall free(*Z) noch?

    Jein, du kannst immer noch über den von dir allozierten Bereich zugreifen,
    aber dafür musst der Programmierer sorgen, dass es nicht passieren kann.
    In dem anderen Fall ist es einfach nur falsch, bzw. macht es einfach keinen
    Sinn.

    Es gilt immer: Bei einem Zugriff auf eine Speicherstelle, musst du dir sicher
    sein, dass du den Speicher angelegt hast und du auch drauf zugreifen darfst.

    Zu deiner zweiten Frage: Ja, das funktioniert, da malloc() sich die Größe
    speichert!

    Vielleicht solltest du mal über folgenden Typen nachdenken:

    struct liste{
        void *daten;   // <-- void*
        struct liste *l_next;
    }
    

    Damit ist der Typ der Daten dem Benutzer überlassen. Und der Benutzer
    muss sich darum kümmern, dass die Daten alloziert und wieder freigegeben
    werden. Die Liste kümmert sich nur um die Aufbewahrung der Daten.

    Üblicherweise schreibt man Funktionen wie:

    struct liste * add (struct liste *l, void *daten);
    void * get_first(struct liste *l, struct liste **cur);
    void * get_next(struct liste **cur);
    

    Gruß mcr


Anmelden zum Antworten