Nein.
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.
Servus Chandler,
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.
Gruß
Jens
Chandler B. schrieb:> 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.
Jens M. schrieb:> 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.
Moin,
Chandler B. schrieb:> 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...
Gruss
WK
Jens M. schrieb:> 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.
Chandler B. schrieb:> void func (int param) // .stack> {> int e; // .stack> int f=0; // .stack> char* ptr; // .stack> ptr = malloc(1);> }
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.
Flunder schrieb:> 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:
Wenn ein Pointer auf ein Objekt zeigt, dessen "lifetime" abgelaufen ist,
ist undefiniert, was passiert, wenn man dieses benutzt.
Wo die Objekte dann letztendlich abgelegt werden, usw., ist Sache der
konkreten C Implementation.
Flunder schrieb:> Doof nur, wenn man einen Compiler für> eine Gurke mit 128 Byte oder weniger auf dem Stack entwickeln muss.
Oder Controller ohne Stack, wie PIC16 und 18
> 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 ;-)
Joachim B. schrieb:> 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.
Chandler B. schrieb:> Damit sollte ptr im Heap> gespeichert werden.
Nein!
Wahrscheinlicher ist, dass ptr auf dem, Stack angelegt wird oder in den
Registern steckt und dort bis zum Verfall auch bleibt.
Chandler B. schrieb:> // .stack
Überall wo bei dir .Stack steht, sollte "Stack oder Register" stehen.
Chandler B. schrieb:> pointer
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.
Chandler B. schrieb:> 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.
> void func (int param) // .stack> {> int e; // .stack> int f=0; // .stack> char* ptr; // .stack> ptr = malloc(1);> }
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.
Axel S. schrieb:> 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.
Andreas S. schrieb:> 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