Forum: Mikrocontroller und Digitale Elektronik wo wird ein pointer gespeichert


Announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
von Chandler B. (chandler)


Lesenswert?

Hallo,
ich lese mich gerade im Memory ein (also welche Segmente gibt  es und 
was wird wo und wann gespeichert).
1
void func (int param)      // .stack
2
{
3
  int e;                   // .stack
4
  int f=0;                 // .stack
5
  char* ptr;               // .stack
6
  ptr = malloc(1);
7
}

in der letzen zeile wird malloc verwendet. Damit sollte ptr im Heap 
gespeichert werden. Wird dieser dann gleichzeitig aus dem .stack 
entfernt?

von Flunder (flunder)


Lesenswert?

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.

von Jens M. (dl4aas) Benutzerseite


Lesenswert?

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

von Rolf M. (rmagnus)


Lesenswert?

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
char c = 5;
2
char* ptr = &c;
Dann enthält ptr die Adresse von c, und du kannst darüber dann auch auf 
c zugreifen.

von Flunder (flunder)


Lesenswert?

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.

von Flunder (flunder)


Lesenswert?

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.

von Dergute W. (derguteweka)


Lesenswert?

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

von Joachim B. (jar)


Lesenswert?

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.

von Peter D. (peda)


Lesenswert?

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.

von Rolf M. (rmagnus)


Lesenswert?

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.

von Daniel A. (daniel-a)


Lesenswert?

Pointer können auf alles mögliche zeigen. Hier mal ein Struct, das auf 
sich selbst zeigt:
1
#include <stdio.h>
2
typedef struct r r;
3
struct r { r* r; const char* str; };
4
int main(){
5
  r r[] = {{r, "Hello World!"}};
6
  puts(r->r->r->r->r->r->r->r->r->r->r->r->r->r->r->r->r->r->r->str);
7
}

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:
1
struct tmp { char str[32]; };
2
struct tmp f(void){ return (struct tmp){"Hello World!"}; }
3
int main(){
4
  puts(f().str); // Gültig
5
  char* x = f().str;
6
  puts(x); // UB / Undefiniert
7
}

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.

von Bruno V. (bruno_v)


Lesenswert?

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 ;-)

von Andreas S. (Firma: Schweigstill IT) (schweigstill) Benutzerseite


Lesenswert?

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.

von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

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.

von Axel S. (a-za-z0-9)


Lesenswert?

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.

: Bearbeitet durch User
von Michi S. (mista_s)


Lesenswert?

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. ;)

von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

Michi S. schrieb:
> ...

Zumindest sollte man das memory leak zustopfen.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Chandler B. schrieb:
1
>   char* ptr;               // .stack
2
>   ptr = malloc(1);
3
> }
> 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
> void func (int param)      // .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.

: Bearbeitet durch User
von Rolf M. (rmagnus)


Lesenswert?

Johann L. schrieb:
> *ptr, d.h. die Adresse, die ptr hält, lebt auf dem Heap.

Nicht die Adresse, sondern das, was an der Adresse steht.

von Joachim B. (jar)


Lesenswert?

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

von G. K. (zumsel)


Lesenswert?


Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.