Forum: Compiler & IDEs Zeiger zurückgeben


von Joachim .. (joachim_01)


Lesenswert?

Das hier fand ich bei:
http://openbook.rheinwerk-verlag.de/c_von_a_bis_z/012_c_zeiger_006.htm

Kapitel 12.6.1, Zeiger als Rückgabewert
1
/* ptr14.c */
2
#include <stdio.h>
3
#include <stdlib.h>
4
#include <string.h>
5
6
/* Fehler: Funktion gibt die Adresse
7
 * einer lokalen Variablen zurück. */
8
char *test1(void){
9
   char buffer[10];
10
   strcpy(buffer, "testwert");
11
   return buffer;
12
}
13
14
/* Möglichkeit1: Statische Variable */
15
char *test2(void){
16
   static char buffer[10];
17
   strcpy(buffer, "testwert");
18
   return buffer;
19
}
20
21
/* Möglichkeit2: Speicher vom Heap verwenden */
22
char *test3(void){
23
   char *buffer = (char *) malloc(10);
24
   strcpy(buffer, "testwert");
25
   return buffer;
26
}
27
28
/* Möglichkeit3: Einen Zeiger als Argument übergeben */
29
char *test4(char *ptr){
30
   char buffer[10];
31
   ptr = buffer;
32
   strcpy(buffer, "testwert");
33
   return ptr;
34
}
35
36
int main(void) {
37
   char *ptr;
38
39
   ptr = test1();
40
   printf("test1: %s\n", ptr); // meistens Datenmüll
41
   ptr = test2();
42
   printf("test2: %s\n", ptr);
43
   ptr = test3();
44
   printf("test3: %s\n", ptr);
45
   test4(ptr);
46
   printf("test4: %s\n", ptr);
47
   return EXIT_SUCCESS;
48
}

In der Tat funktionieren die Beispiele so wie sie niedergeschrieben 
sind. ABER:
1. Muß ich in test3() nicht mit free(buffer) den Speicher wieder 
freigeben? Wenn ich das mache bekomme ich in main in der Zeile
printf("test2: %s\n", ptr); für den Zeiger nichts angezeigt.

2. test4 scheint nur zu funktionieren weil ptr vorher im Speicher 
gehalten wurde. Mit:
1
   char *ptr2;
2
   test4(ptr2);
3
   printf("test4: %s\n", ptr2);
bekomme ich:
test4: (null)

Ich verwende gcc mit Codeblocks und default-Einstellungen.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Joachim ... schrieb:
> /* Möglichkeit2: Speicher vom Heap verwenden */
> char *test3(void){
>    char *buffer = (char *) malloc(10);
>    strcpy(buffer, "testwert");
>    return buffer;
> }

Nachteil: Die aufrufende Funktion muss den free() machen.

> /* Möglichkeit3: Einen Zeiger als Argument übergeben */
> char *test4(char *ptr){
>    char buffer[10];
>    ptr = buffer;
>    strcpy(buffer, "testwert");
>    return ptr;
> }

Fehler: buffer ist nicht static, sondern liegt auf dem Stack. Nach 
Verlassen der Funktion ist buffer "zerstört", d.h. nicht mehr anwendbar.

von Max H. (hartl192)


Lesenswert?

Joachim ... schrieb:
> 1. Muß ich in test3() nicht mit free(buffer) den Speicher wieder
> freigeben?
Genau, und wenn du es richtig machen willst den Pointer auch noch auf 
NULL prüfen, bevor du ihn verwendest.

> 2. test4 scheint nur zu funktionieren weil ptr vorher im Speicher
> gehalten wurde. Mit:
>
>    char *ptr2;
>    test4(ptr2);
>    printf("test4: %s\n", ptr2);
>
> bekomme ich:
> test4: (null)
Test4 kann ptr2 nicht verändern, da dieser by Value und nicht bei 
Reference übergeben wird.
1
  char *ptr2;
2
  ptr2 = test4(ptr2);
3
  printf("test4: %s\n", ptr2);
Wird aber auch nicht funktionieren, da der Pointer auf eine 
auto-Variable im Stack zeigt und diese freigegen wird wenn test4() 
verlassen wird. Wenn man prt2 sofort nach dem Funktionsaufruf verwendet 
bevor etwas Neues auf den Stack gelegt wurde kann es funktionieren, 
saubere Programmierung ist es aber auf keinen Fall.

: Bearbeitet durch User
von Oliver S. (oliverso)


Lesenswert?

Den mit malloc reservierten Speicher aus test3 sollte man wieder 
freigeben, aber erst, wenn er nicht mehr benötigt wird.

test4 hängt damit zusammen, und ist für sich alleine Unsinn. Die 
Funktion gibt einen pointer auf den lokal angelegten Buffer zurück, was 
nicht zulässig ist. Allerdings wird der Rückkgabewert nicht benutzt. Der 
Parameter ptr wird per reference übergeben, und ändert damit außerhalb 
von test4 seinen Wert nicht. Das auf test4 folgende printf gibt den 
String aus test3 damit nochmal aus.

Ein free auf dem Speicher aus test3 darf daher erst nach test4 
passieren.

Wenn du die Strings in den Funktionen von "testwert" in "testwert1" bis 
"testwert4" änderst, erkennst du deutlicher was passiert.

Oliver

von A. H. (ah8)


Lesenswert?

Versuch es mal mit
1
/* Möglichkeit3: Einen Zeiger als Argument übergeben */
2
char *test4(char **ptr){
3
   char buffer[10];
4
   *ptr = buffer;
5
   strcpy(buffer, "testwert");
6
   return *ptr;
7
}

und
1
   test4(&ptr);

Und ein genereller Tipp: Verwende unterschiedliche Testwerte in 
unterschiedlichen Testfunktionen. Dein test4 dürfte nur funktionieren 
weil vorher test3 gelaufen ist.

: Bearbeitet durch User
von Max H. (hartl192)


Lesenswert?

A. H. schrieb:
> Versuch es mal mit
> ...
Das Problem, dass buffer[10] nach dem Verlassen der Funktion vom Stack 
genommen wird bleibt aber bestehen.
Und man sollte ptr nach dem Aufruf von test4 freen, weil der Pointer 
geändert wird und der in test3 angeforderte Speicher somit für immer 
verloren ist.

von Joachim .. (joachim_01)


Lesenswert?

Vielen Dank für die Erläuterungen. Ein echt klasse Forum.

Interessant ist, was sich so alles in Lehrbüchern findet...

von Rolf M. (rmagnus)


Lesenswert?

Joachim ... schrieb:
> 1. Muß ich in test3() nicht mit free(buffer) den Speicher wieder
> freigeben? Wenn ich das mache bekomme ich in main in der Zeile
> printf("test2: %s\n", ptr); für den Zeiger nichts angezeigt.

Wenn du das tust, hast du ungefähr den gleichen Fall wie bei test1. Der 
Speicher wurde wieder freigegeben, bevor du ihn in main() benutzt. Du 
hast zwar recht, daß du den Speicher mit free() freigeben musst, aber 
natürlich erst, wenn du ihn wirklich nicht mehr brauchst.

Es gibt in C generell drei Arten von Speicher, nämlich statisch, 
automatisch und dynamisch.
Variablen im statischen Speicher existieren über die gesamte 
Programmlaufzeit hinweg. Das macht sich test2() zunutze. Zu beachten ist 
hier, daß ein weiterer Aufruf von test2() die Daten vom letzten mal 
überschreibt.
Automatische Variablen sind lokale Variablen, die nicht static sind. 
Diese werden beim Verlassen des Blocks, in dem sie definiert sind, 
automatisch wieder freigegeben. Das ist das Problem bei test1(). Der 
Aufrufer darf den zurückgegebenen Zeiger nicht nutzen, weil die 
Variable, auf die er zeigt, mit Verlassen der Funktion aufhört zu 
existieren.
Dynamischen Speicher bekommt man von malloc() und Konsorten. Dieser 
bleibt so lange verfügbar, bis man ihn explizit mit free() wieder 
freigibt. Damit hat man die größte Flexibilität, was die Lebensdauer 
betrifft, aber muß sich selbst drum kümmern, es wieder freizugeben, wenn 
man es nicht mehr braucht.

von Joachim .. (joachim_01)


Lesenswert?

Autor: A. H. (ah8) schrieb:
>Versuch es mal mit
1
/* Möglichkeit3: Einen Zeiger als Argument übergeben */
2
char *test4(char **ptr){
3
   char buffer[10];
4
   *ptr = buffer;
5
   strcpy(buffer, "testwert");
6
   return *ptr;
7
}


Aufruf mit:
1
  test4(&ptr);
Danke. Das hat funktioniert. Jetzt muß ich nur noch verstehen, warum ** 
hier die Lösung ist...

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Joachim ... schrieb:
> 1. Muß ich in test3() nicht mit free(buffer) den Speicher wieder
> freigeben?

Nein. Nicht in test3(), sondern nachdem Du etwas mit dem 
zurückgegebenen Pointer angestellt hast, also nachdem Du in Deinem 
main() die Zeile
>    printf("test3: %s\n", ptr);

aufgerufen hast.

Da gehört das

free(ptr);

hin.

von Oliver S. (oliverso)


Lesenswert?

Joachim ... schrieb:
> Danke. Das hat funktioniert. Jetzt muß ich nur noch verstehen, warum **
> hier die Lösung ist...

Du solltest lieber versuchen, zu verstehen, warum das in dem Fall NICHT 
die Lösung ist, auch wenn es zufällig funktioniert.

Oliver

von Max H. (hartl192)


Lesenswert?

Joachim ... schrieb:
> Danke. Das hat funktioniert. Jetzt muß ich nur noch verstehen, warum **
> hier die Lösung ist...
Weil du damit den Pointer by Reference, und nicht by Value übergibst und 
die Zuweisung *ptr = buffer dadurch auch außerhalb der Funktion Wirkung 
hat.
So hast du aber das gleiche Problem wie in test1: buffer[10] wird vom 
Stack genommen wenn du test4 verlässt. Das kann wie gesagt gut gehen, 
saubere Programmierung ist es aber auf keinen Fall.

: Bearbeitet durch User
von Markus F. (mfro)


Lesenswert?

Es gibt noch eine weitere Möglichkeit für die nach dem "Array return 
Suchenden":
1
struct text
2
{
3
    char txt[10];
4
};
5
6
struct text string_return()
7
{
8
  struct text my_text;
9
10
  strcpy(my_text.txt, "Hallo");
11
12
  return my_text;
13
}

Auch wenn das auf den ersten Blick genauso falsch aussieht, wie test4() 
(das einen Zeiger auf eine lokale Variable zurückgibt, und - sollte das 
tatsächlich aus einem C-Buch stammen - dem Autor um die Ohren gehauen 
gehört), ist das korrektes und funktionierendes C (structs können als 
einziger komplexer Datentyp "by value" übergeben werden).

Trotzdem sollte man das nicht unbedingt in großem Stil benutzen, weil es 
natürlich ziemlich ineffizient ist, größere Datenmengen über den Stack 
zu schieben.

von Dirk B. (dirkb2)


Lesenswert?

Die Beispiele sind in Ordnung, wenn sie zum erklären der möglichen 
Fehler dienen.

Werden diese Fehler im Buch nicht erklärt, schmeiß es weg.

Du lernst dadurch Müll und merkst es nicht.

Gerade dieses Buch (und auch der Autor) sind bei 
https://www.c-plusplus.net/forum/ verrufen, da das nicht die einzige 
Stelle ist, wo das Buch Falsches vermittelt.

Gerade das Beispiel von 12.6.1 mit strtok beweist, dass der Autor keine 
Ahnung hat.
1
char *eingabe(char *str) {
2
   char input[MAX];
3
4
   printf("Bitte \"%s\" eingeben: ",str);
5
   fgets(input, MAX, stdin);
6
   return strtok(input, "\n");
7
}
wird als richtig erklärt, während die Funktion ohne strtok mit    return 
input; als falsch beschrieben wird.

Klar ist das return input falsch, aber das strtok liefert denselben Wert 
zurück.

von A. H. (ah8)


Lesenswert?

Max H. schrieb:
> A. H. schrieb:
>> Versuch es mal mit
>> ...
> Das Problem, dass buffer[10] nach dem Verlassen der Funktion vom Stack
> genommen wird bleibt aber bestehen.

Das ist richtig, darum geht hier aber nicht, denn das Problem wurde 
bereits in test1 diskutiert und ist hier nicht falscher als dort. Man 
könnte noch viel über die 4 Funktionen und deren Sinn (oder Unsinn) 
schreiben, das drängendste Problem in diesem Fall war aber die 
fehlerhafte Parameterübergabe (und die Möglichkeiten, dass zu erkennen) 
und auf das habe ich mich beschränkt.

: Bearbeitet durch User
von Bitflüsterer (Gast)


Lesenswert?

Die Sau mit dem falschen Beispiel wird nun schon seit Jahren durch die 
Foren getrieben. Hier Beitrag "C pointer frage" ein 
Beispiel vom 27.7.2010. Seltsam, dass der Rheinwerk-Verlag das noch 
nicht geändert hat.

von Daniel A. (daniel-a)


Lesenswert?

Joachim ... schrieb:
> 1. Muß ich in test3() nicht mit free(buffer) den Speicher wieder
> freigeben?
Sobald er nichtmehr benötigt wird.

> Wenn ich das mache bekomme ich in main in der Zeile
> printf("test2: %s\n", ptr); für den Zeiger nichts angezeigt.

Das free mus nach dem printf stehen

Joachim ... schrieb:
> test4 scheint nur zu funktionieren weil ptr vorher im Speicher gehalten
> wurde. Mit:
   ...
> bekomme ich:
> test4: (null)

Richtig. So könnte test4 alles sein, müsste nicht null sein. Trotzdem 
überschreibst du irgendeinen Speicherbereich. Das orriginalbeispiel geht 
wegen dem vorherigen malloc. Eine bessere Variante des Beispiels wäre:
1
   char ptr2[256]; // 256 byte grosses array
2
   test4(ptr2); // addresse, pointer auf array start übergeben
3
   printf("test4: %s\n", ptr2);
Dann zeigt ptr2 garantiert auf freie 256 bytes Speicher.

von Max H. (hartl192)


Lesenswert?

Daniel A. schrieb:
> char ptr2[256]; // 256 byte grosses array
>    test4(ptr2); // addresse, pointer auf array start übergeben
>    printf("test4: %s\n", ptr2);
> Dann zeigt ptr2 garantiert auf freie 256 bytes Speicher.
So funktioniert das aber nicht, test4 schreibt in char buffer[10];
Damit es funktioniert müsste test4 so aussehen:
1
char *test4(char *ptr)
2
{
3
  strcpy(ptr , "testwert");
4
  return ptr;
5
}

: Bearbeitet durch User
von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Max H. schrieb:
>
1
> char *test4(char *ptr)
2
> {
3
>   strcpy(ptr , "testwert");
4
>   return ptr;
5
> }
6
>


Und spätestens hier sollte man ein zusätzliches Argument in Erwägung 
ziehen, um einen Buffer-Overflow zu verhindern - nämlich die Angabe der 
Größe des übergebenden Buffers.

von Daniel A. (daniel-a)


Lesenswert?

Max H. schrieb:
> test4 schreibt in char buffer[10];

Sorry, das hatte ich übersehen. Zusätzlich hatte mich der Kommentar über 
der Funktion irregeführt.

Danke für den Hinweis. Da habe ich wohl einen Fehler gemacht.

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.