Zeiger zurückgeben

#4064559
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.
Moderator Persönliche Seite #4064563
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.
#4064566
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.
#4064578
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
#4064581
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.
#4064600
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.
#4064601
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...
#4064609
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.
#4064612
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.
#4064616
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.
#4064645
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.
#4064716
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.
#4064726
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
}

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