wo wird ein pointer gespeichert

OP #7819944
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?

Persönliche Seite #7819962
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

#7819982
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.

#7819983
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.

#7819991
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.

#7819992
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

#7819993
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.

#7819994
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.

#7819996
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.

#7820038
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.

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

(Firma: Schweigstill IT) Persönliche Seite #7820050
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.

(Firma: Gast) #7820063
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.

#7820064
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.

Persönliche Seite #7820568
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.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren