Du musst unterscheiden zwischen wo der Wert des Pointers gespeichert wird und wohin er zeigt. Ein Wegweiser Richtung Frankfurt muss ja auch nicht in Frankfurt stehen.
der Pointer bleibt, wo er ist. malloc reserviert Platz auf dem Heap und ptr wird die Adresse dieses Platzes zugewiesen.
Der mit malloc reservierte Platz bleibt übrigens bestehen, auch wenn der ptr nicht mehr darauf zeigt. Wenn die Funktion func verlassen wird, ohne dass Du den Platz mit free(ptr) zurückgegeben hast, entsteht eine Speicherleiche. Rufst Du func häufig auf, ist irgendwann der heap voll.
in der letzen zeile wird malloc verwendet. Damit sollte ptr im Heap
gespeichert werden. Wird dieser dann gleichzeitig aus dem .stack
entfernt?
Ein Pointer ist nur eine Variable, die eine Adresse eines Objekts enthält, also die Information, wo es zu finden ist. Die Daten selbst stehen nicht im Zeiger. Der Zeiger bleibt deshalb genau da, wo du ihn angelegt hast. malloc erzeugt dynamisch, also zur Laufzeit des Programms, ein neues Objekt, das selbst keinen Namen hat und gibt dessen Adresse zurück. Die (also nur die Adresse) speicherst zu dann in ptr. Du könntest z.B. auch schreiben:
1
charc=5;
2
char*ptr=&c;
Dann enthält ptr die Adresse von c, und du kannst darüber dann auch auf c zugreifen.
Wo ein Compiler was ablegt ist seine Sache. Er bekommt vom Standard nur Vorgaben, wie sich bestimmte Konstrukte verhalten müssen. Natürlich gibt es für vieles naheliegende und erprobte Vorgehensweisen. Lokale Variablen auf den Stack zu legen, ermöglicht es einfach rekursive Funktionen anbieten zu können. Doof nur, wenn man einen Compiler für eine Gurke mit 128 Byte oder weniger auf dem Stack entwickeln muss. Da kann man schon mal auf die Idee kommen, ein eigenes Schlüsselwort "reentrant" einzuführen und allen lokalen Variablen nicht damit gekennzeichneter Funktionen feste Adressen im RAM zuzuweisen. Dann nur noch schauen, welche Variablen nie gleichzeitig gebraucht werden und den Platz im Ram doppelt vergeben. Ups, ich schweife ab.
ptr bekommt normalerweise einen Platz im RAM auf dem Stack. Dort ist der Zeiger hinterlegt. Der zeigt auf anderen Speicher. Du ahnst es schon, dazu nimmt man gerne die Adresse des Elements, auf das gezeigt werden soll. Bitter wird es erst, wenn man einen Compiler für irgendwas mit Harvard Architektur schreiben soll und man verschiedene Adressräume hat. Eigentlich muss man dann neben der Adresse auch abspeichern, aus welchem Adressraum sie ist, weil es ja dabei Adresse 42 im Datenspeicher und im Programmspeicher geben kann, wobei beide unterschiedliche Speicherzellen meinen. Eigentlich schreibe ich, weil es auch andere grausame proprietäre Methoden gibt, um zu entscheiden, wohin der Zugriff geht.
Bei Dir wird aber vermutlich ptr eine Adresse im Ram bekommen, in dem Bereich wo sich der Stack befindet und dort steht die Adresse einer anderen Ram-Zelle, die zum Heap gehört.
Der mit malloc reservierte Platz bleibt übrigens bestehen, auch wenn der
ptr nicht mehr darauf zeigt. Wenn die Funktion func verlassen wird, ohne
dass Du den Platz mit free(ptr) zurückgegeben hast, entsteht eine
Speicherleiche. Rufst Du func häufig auf, ist irgendwann der heap voll.
Und wenn Du über einen Pointer auf den Speicher zugreifst, nachdem Du free(ptr) aufgerufen hast oder über einen Pointer auf eine lokale Variable einer Funktion, die schon zurück gekehrt ist, dann nennt man das "use after free". Das ist sowas wie Leichenschänderei und u.U. ein ernstes Sicherheitsproblem.
Damit sollte ptr im Heap
gespeichert werden. Wird dieser dann gleichzeitig aus dem .stack
entfernt?
Schreib' doch einfach noch diese Zeile in deine Funktion und ruf' sie (die Funktion) auf:
1
printf("%p\n%p\n%p\n%p\n",&e,&f,&ptr,ptr);
Da kommt z.b. bei mir sowas raus:
0x7fff6f70a588
0x7fff6f70a58c
0x7fff6f70a590
0x5654da0562a0
Die absoluten Werte werden bei jedem anders sein, aber man kann erkennen, dass die ersten 3 Variablen an aufeinanderfolgenden Adressen liegen und der Pointer wo ganz wo anders hinzeigt...
Der mit malloc reservierte Platz bleibt übrigens bestehen, auch wenn der
ptr nicht mehr darauf zeigt.
ja die gute alte garbadge collection (PET2001) ist ja aus der Mode gekommen.
Man hat also nur die Wahl neuerding den heap zu überwachen und alles selbst in die Hand zu nehmen.
Ich reserviere trotzdem immer Platz für die Variablen statisch die ich ständig brauche, dann überrascht es mich nicht das der heap ausgeht oder so zerpfückt ist das er unbrauchbar wird.
Der Code ist Unsinn, da ptr nur lokal definiert ist.
Malloc reserviert quasi ein Zimmer und weist den Schlüssel ptr zu. Mit Ende der Funktion wird ptr ungültig, d.h. Du schmeißt den Schlüssel weg und das Zimmer bleibt auf ewig verschlossen.
Auch kostet malloc Verwaltungsplatz. Man nimmt es daher nur für größere Häppchen (>16Byte). Und natürlich nur, wenn der Speicher über das Funktionsende hinaus benutzt wird.
Wo ein Compiler was ablegt ist seine Sache. Er bekommt vom Standard nur
Vorgaben, wie sich bestimmte Konstrukte verhalten müssen.
Ja, insbesondere sind Begriffe wie "heap" und "stack" dort nicht definiert - zumindest nicht in diesem Zusammenhang.
Es gibt nach C-Standard drei Arten von Speicher bzw. Objektlebensdauern, nämlich statisch, automatisch und dynamisch.
Statische Objekte existieren über die gesamte Programmlaufzeit hinweg. Das sind globale Variablen und lokale Variablen, die mit dem Zusatz "static" definiert sind. Automatische Variablen sind alle, die ohne "static" lokal in einer Funktion definiert sind. Diese leben bis zum Ende des Blocks, in dem sie definiert sind. Danach wird deren Speicher automatisch wieder freigegeben. Dynamisch ist alles, was mit malloc() und co erzeugt wird. Das lebt so lange, bis man es explizit wieder mit free() freigibt.
In der Praxis nutzen die Compiler meist eine Kombination aus Stack und Registern für die automaischen Variablen und nennen den Speicherbereich mit den dynamischen Objekten "heap". Wie das genau funktioniert, bleibt aber dem jeweiligen Compiler überlassen.
ptr bekommt normalerweise einen Platz im RAM auf dem Stack.
Ich würde davon ausgehen, dass es oft in einem Register landet.
Bitter wird es erst, wenn man einen Compiler für irgendwas mit Harvard
Architektur schreiben soll und man verschiedene Adressräume hat.
Harvard wäre eigentlich kein Problem, wenn es denn eine reine Harvard-Architektur wäre, also eine, wo Daten auch wirklich nur aus dem Daten- und nicht aus dem Programmspeicher gelesen werden.
Eigentlich muss man dann neben der Adresse auch abspeichern, aus welchem
Adressraum sie ist, weil es ja dabei Adresse 42 im Datenspeicher und im
Programmspeicher geben kann, wobei beide unterschiedliche Speicherzellen
meinen.
Die Methode gibt es auch, hat aber in der Regel den Nachteil, dass sie die zeigerbasierten Speicherzugriffe erheblich verlangsamt - abgesehen vom erhöhten Speicherbedarf.
Pointer zeigen auf/in ein Objekt. (Oder auf deren Ende, oder auf 0).
Der C Standard definiert nicht, wo das liegt, oder wie diese Referenz/Adresse im Pointer gespeichert ist.
Meistens ist es einfach eine Nummer, ein index in einen linearen Adressraum. Aber das muss nicht so sein. Theoretisch könnte eine C Implementation auch eine Objekt ID und einen Offset nehmen, oder sonst was.
"int a;" ist ein Objekt. "int a[100];" ist auch ein Objekt. "malloc" gibt einen Pointer auf ein Objekt zurück.
Was der Standard definiert, ist die "storage duration" (Speicher Dauer), und die "lifetime" (Lebenszeit) von Objekten.
Bei der storage duration gibt es: static, thread, automatic, and allocated. Die legt in der Regel die "lifetime" des Objekts fest:
"static" ist quasi während der ganzen Programmlaufzeit gültig.
"automatic" sind die Objekte, die temporär in einer Funktion angelegt werden. Sie sind Gültig, solange der Block, in dem sie Definiert wurden, nicht verlassen wurde.
"allocated" Das gilt für Funktionen wie malloc/free. Ist gültig, solange nicht freigegeben.
Es gibt aber einen Spezialfall. Temporary lifetime (trotz automatic storage duration).
Eine Expression kann entweder eine rvalue oder eine lvalue sein. Bei einer lvalue gehen Zuweisungen (lvalue=wert) und das Referenzieren (&lvalue). Das hier "(int){0}=1" ist also OK. Bei einer Expression wie "1 + 2" hat man eine rvalue, da geht "&(1 + 2)" also nicht. Gleiches beim Rückgabewert von Funktionen.
Wenn nun eine Funktion ein Struct mit einem Array darin zurück gibt, und man auf dieses zugreift, hat man den Spezialfall. Wenn bei "int* x=f().a" a z.B. ein "int[1]" ist, dann wird ein Pointer daraus. Man bekommt eine Referenz auf eine rvalue, etwas, was normalerweise nicht möglich wäre. Das Objekt hat dann Temporary lifetime.
Temporary lifetime heisst, das Objekt ist nur gültig, bis die ganze Expression fertig ist:
Dann nur noch schauen, welche Variablen nie gleichzeitig gebraucht
werden und den Platz im Ram doppelt vergeben. Ups, ich schweife ab.
Genauso macht das der Compiler. Darum hat er Einschränkungen bei Funktionspointern. Du kannst die zwar beliebig verschachteln (Eine Funktion ruft per FPtr eine Funktion auf, die per FPtr. eine Funktion aufruft, die per FPtr. ... . Es müssen aber alles FPtr. mit verschiedener Signatur sein ;-)
ja die gute alte garbadge collection (PET2001) ist ja aus der Mode
gekommen.
Nein. Aktuelle objektorientierte Sprachen kümmern sich genau darum. Das von Dir offenbar kritisierte C stammt übrigens von 1972 und somit sogar fünf Jahre älter als der PET 2001. Alle moderneren C-Nachfolger (C++, C#, Objective C, Java, usw.) bzw. die zugehörigen Standardbibliotheken enthalten natürlich auch dynamisch verwalteten Speicher mit Garbage Collection.
Man hat also nur die Wahl neuerding den heap zu überwachen und alles
selbst in die Hand zu nehmen.
"Neuerdings" bedeutet bei C somit den Zeitraum zwischen 1972 und 1979, dem Erscheinungsjahr von C++.
Ich reserviere trotzdem immer Platz für die Variablen statisch die ich
ständig brauche, dann überrascht es mich nicht das der heap ausgeht oder
so zerpfückt ist das er unbrauchbar wird.
Was denn nun? Dynamischen Speicher propagieren, aber doch nicht verwenden?!
Die Vorgehensweise, Variable mit langer Lebensdauer statisch bzw. sogar global zu definieren, wende ich bei kleinen Microcontrollern auch an, um besser den Überblick über die Speicherbelegung zu behalten.
Es ist wichtig sich darum zu kümmern...
Ist es doch der schlampige Umgang mit Zeigern, der mit die größten Probleme in C verursacht.
C++ hat daraus gelernt!
Wurden doch RAII und smarte Pointer erfunden.
Mein Rat, verlasse C und verwende C++.
Natürlich kann man sich auch in C++ mit Pointern selber ins Bein schießen, aber man kann sich auch viel besser um die raw Pointer rum drücken.
ich lese mich gerade im Memory ein (also welche Segmente gibt es und
was wird wo und wann gespeichert).
Und wozu glaubst du das wissen zu müssen? Innerhalb eines C-Programms kann dir das eigentlich egal sein, weil der Compiler das für dich erledigt. Du sprichst Variablen über den Namen an. Und fertig.
Deine Annahmen stimmen alle nicht. param wird in der Funktion nicht verwendet, darum wird der Compiler das wegoptimieren. Dito f.
Die Zeilen "int e" und "char* ptr" legen selber gar nichts an, das passiert erst, wenn den Variablen Werte zugewiesen werden. In diesem "Programm" kann der Compiler lediglich den Aufruf von malloc() nicht wegoptimieren. Aber der Returnwert wird ja nirgendwo mehr verwendet, wird vom Compiler also kommentarlos verworfen.
Des weiteren werden Funktionsparameter auch gern in Registern übergeben, insbesondere wenn es wenige und kurze sind. Diese Vereinbarung nennt sich ABI (deutsch → Binärschnittstelle).
Das gleiche gilt für lokale Variablen. Da das der generierte Maschinencode als einziger wissen muß, kann der Compiler das frei entscheiden. Und er entscheidet das möglicherweise für verschiedene Compiliervorgänge unterschiedlich, nämlich nach der jeweils gültigen Optimierungsstufe.
Last not least: die Existenz oder Namen von Speichersegmenten sind architekturabhängig. C existiert auch für Architekturen, die gar keinen Stack besitzen.
In diesem
"Programm" kann der Compiler lediglich den Aufruf von malloc() nicht
wegoptimieren. Aber der Returnwert wird ja nirgendwo mehr verwendet,
wird vom Compiler also kommentarlos verworfen.
Womit die komplette Funktion und ihre sämtlichen Aufrufe ebenfalls wegoptimiert werden dürfen. ;)
in der letzen zeile wird malloc verwendet. Damit sollte ptr im Heap
gespeichert werden.
ptr lebt auf dem Stack (oder in einem Register).
*ptr, d.h. die Adresse, die ptr hält, lebt auf dem Heap.
Der Rückgabewert von malloc wird also auf dem Stack (oder in einem Register des Prozessors) gehalten.
1
>voidfunc(intparam)// .stack
Wo und wie Funktionsargumente übergeben werden legt das ABI (Application Binary Interface) fest. Üblicherweise werden zumindest die paar ersten Argumente in Registern übergeben, weil das effizienter ist (kein Speicherzugriff erfordert, weniger Speicher belegt, etc.), und weil ABIs i.d.R so gestaltet sind, dass sie auf der Zielhardware einigermaßen effizient sind.
Die Vorgehensweise, Variable mit langer Lebensdauer statisch bzw. sogar
global zu definieren, wende ich bei kleinen Microcontrollern auch an, um
besser den Überblick über die Speicherbelegung zu behalten.
das meinte ich auch, dann werde ich weniger überrascht