Forum: PC-Programmierung C - fgets - verständnisproblem


von Raphael R. (raphael)


Lesenswert?

hallo leute,

folgenden prozedur liefert auf grund da input nur " 51 " ist müll
zurück!
darunter findet ihr dann ein beispiel welches läuft, aber ich verstehe
fgets anscheinend nicht richtig! warum funktionieren nicht beide?
denn der wert wie gross der string ist, sollte doch mir überlassen
sein! woran scheitert es?

BSP1.:
--------------------------------------------------------------------
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX 51

char *eingabe(char *str){
     char input[MAX];

     printf("bitte \"%s\" eingeben!\n",str);
     fgets(input, MAX, stdin);
     return strtok(input, "\n");
}

int main(void){
    char *ptr;

    ptr = eingabe("vorname");
    printf("hallo %s\n", ptr);

getchar();
return EXIT_SUCCESS;
}

BSP2.:
---------------------------------------------------------------

diese hier bei welcher MAX den wert 255 (also sehr gross zum obigen
beispiel wo der wert nur auf 51 war) hat, läuft korrekt!


/* ptr13.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX 255
char *eingabe(char *str) {
   char input[MAX];
   printf("Bitte \"%s\" eingeben: ",str);
   fgets(input, MAX, stdin);
   return strtok(input, "\n");
}
int main(void) {
   char *ptr;
   ptr = eingabe("Vorname");
   printf("Hallo %s\n", ptr);
   ptr = eingabe("Nachname");
   printf("%s, interssanter Nachname\n", ptr);
   return EXIT_SUCCESS;
}

WARUM laufen nicht beide programme einwandfrei?

danke schon im voraus für eure hilfe

>Raphael

von Stefan (Gast)


Lesenswert?

Beide Beispiele sind fehlerhaft. Der Puffer char input[MAX]; ist bei
deinen Beispielen nicht statisch. Sobald eingabe() verlassen wird, darf
darauf nicht mehr zugegriffen werden!

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Du gibst in der Funktion "eingabe" einen Pointer auf eine lokale
Variable dieser Funktion zurück. Die liegt auf dem Stack und ist nach
Verlassen der Funktion ungültig.

Entweder das Array "input" als "static" oder aber außerhalb der
Funktion "eingabe" deklarieren.

von Raphael R. (raphael)


Lesenswert?

ich weiss was ihr meint!

jedoch, da das programm funktioniert (zumindest programm nummer 2, in
welchem MAX 255 gross ist, wobei ich nicht verstehe warum programm 1
nicht funktioniert in dem MAX relativ klein 51 ist) kann anscheinend
doch auf die variable auch nach ende des funktionsaufrufes
zurückgegriffen werden!!!

das programm BSP2.: stammt aus dem buch c von a bis z.

ich weiss, dass normalerweise auf eine

von Raphael R. (raphael)


Lesenswert?

fortsetzung:

ich weiss, dass normalerweise auf eine lokale variable nach beendigung
der funktion in welcher sie deklariert wurde nicht mehr zugegriffen
werden kann...

aber hier geht es!!!

warum?

von Raphael R. (raphael)


Lesenswert?

aber erstmals vielen dank

nun mit der GLOBAL deklarierten variable "char input[MAX]" läuft auch
programm BSP1.

dürfte ein komischer zufalle gewesen sein, dass BSP2 auch so richtig
gelaufen ist!!!

komisch

liebe grüße und danke

>Raph

von mplusplus (Gast)


Lesenswert?

Hi,

> dürfte ein komischer zufalle gewesen sein, dass BSP2 auch so
> richtig gelaufen ist!!!

Ja, das war Zufall. Sowas klappt nur dann, wenn die Information noch
auf Stack vorhanden ist, worauf man sich aber nie verlassen kann und
auch nicht sollte.

Grüße

mplusplus

von Raphael R. (raphael)


Lesenswert?

ALLES FALSCH

jetzt hab ichs!!!

in diesem beispiel wurde ein Zeiger als Argument übergeben!
darum muss die variable "char input[MAX]" nicht als STATIC oder
GLOBAL deklariert sein!

Die Funktion eingabe() gibt eben einen solchen Speicherbereich (lokales
Feld) zurück, der sich ebenfalls auf diesem Stack-Frame befindet bzw.
befand – und somit bei Beendigung der Funktion nicht mehr vorhanden
ist. Wollen Sie also einen Zeiger auf einen Speicherbereich
zurückgeben, haben Sie folgende Möglichkeiten. Sie verwenden ...

-einen statischen Puffer (static).
-einen beim Aufruf der Funktion als Argument übergebenen Puffer.
-einen mittels malloc() reservierten Speicher

Bsp.:
Hierzu ein Beispiel, welches alle drei Möglichkeiten demonstrieren
soll:

/* ptr14.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* Fehler: Funktion gibt die Adresse
 * einer lokalen Variablen zurück */
char *test1(void){
   char buffer[10];
   strcpy(buffer, "testwert");
   return buffer;
}
/* Möglichkeit1: Statische Variable */
char *test2(void){
   static char buffer[10];
   strcpy(buffer, "testwert");
   return buffer;
}
/* Möglichkeit2: Speicher vom Heap verwenden */
char *test3(void){
   char *buffer = (char *) malloc(10);
   strcpy(buffer, "testwert");
   return buffer;
}
/* Möglichkeit3: Einen Zeiger als Argument übergeben */
char *test4(char *ptr){
   char buffer[10];
   ptr = buffer;
   strcpy(buffer, "testwert");
   return ptr;
}
int main(void) {
   char *ptr;
   ptr = test1();
   printf("test1: %s\n", ptr); // Datenmüll
   ptr = test2();
   printf("test2: %s\n", ptr);
   ptr = test3();
   printf("test3: %s\n", ptr);
   test4(ptr);
   printf("test4: %s\n", ptr);
   return EXIT_SUCCESS;
}

das war nun die erklärung/lösung meines problems!!!

lg >Raphael

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Nein, Du hast es nicht.

Möglichkeit 4 unterscheidet sich inhaltlich nicht von der fehlerhaften
Möglichkeit 1, denn der Puffer, auf den der Pointer verweist, ist nach
wie vor ein lokaler Puffer. Und ist damit ebenso falsch.

Mäßig korrekt sind somit nur 2 und 3, wobei bei 3 nach Aufruf der
Funktion tunlichst der per malloc angeforderte Speicher auch
wiederfreigegeben werden sollte, sonst gibt es Speicherlecks.

von Stefan (Gast)


Lesenswert?

@ Raphael Reu

> das programm BSP2.: stammt aus dem buch c von a bis z.

In der aktuellen 2. Druckauflage oder in der Online-Version (
http://www.pronix.de/pronix-4.html )?

Wenn ja, schreibe dem Jürgen Wolf, dass du einen Fehler gefunden hast
;-)

von Karl heinz B. (kbucheg)


Lesenswert?

> das programm BSP2.: stammt aus dem buch c von a bis z.

Ich hoffe es ist dort als abschreckendes Beispiel gezeigt, das
verdeutlicht das 'undefiniertes Verhalten' (so heist das in
der C-Fachsprache) auch bedeuten kann: "Sieht auf den ersten
Blick so aus als ob es gehen würde, ist aber trotzdem falsch."

von Raphael R. (raphael)


Lesenswert?

hi leute,

ist in der online fassung und in meinem buch gleich! also wenn es
tatsächlich ein fehler ist, dann in allen versionen!

ich hab ja den "fehler" nicht gefunden! den habt ja ihr gefunden :-)

leute ich weiss was undefiniertes verhalten bedeutet... das einzige was
ich nicht kann ist anständig zu programmieren!!! darum arbeite ich ja
hart daran :-)

es war mich auch unklar, aber ich dachte durch

return strtok(variable, "\n"); /* auch wenn sie "nur" in der
aufgerufenen funktion deklariert ist, bleibt der speicher erhalten!
... da jürgen wolf in seinem buch ja auch extra angegeben hatte EINEN
ZEIGER ALS ARGUMENT ÜBERGEBEN .. dachte ich dies ist eine andere
vorgehensweise!

nun gut, ich werde jürgen wolf darauf aufmerksam machen
mal sehn was er dazu sagt...

:-) in zukunft werd ich mit static oder GLOBAL arbeiten!!!

danke leute

>Raphael

von Stefan (Gast)


Lesenswert?

Warte noch! Es ist deutlich als ein abschreckendes Beispiel
gekennzeichnet, so wie Karl Heinz Buchegger gemeint hat!

Auszug aus dem Text:

"Normalerweise sollte hier der Compiler schon meckern, dass etwas
nicht stimmt. Spätestens aber, wenn Sie das Beispiel ausführen, werden
Sie feststellen, dass anstatt des Strings, den Sie in der Funktion
eingabe() eingegeben haben nur Datenmüll ausgegeben wird."

http://www.pronix.de/pronix-743.html

von Stefan (Gast)


Lesenswert?

Aber im "Hierzu ein Beispiel, welches alle drei Möglichkeiten
demonstrieren soll:" sind Fehler und Unschönheiten drin, wie Rufus T.
Firefly schon geschrieben hat.

von Raphael R. (raphael)


Lesenswert?

ja leute war mein fehler, sehr peinlich

ich dachte er meinte als abschreckendes beispiel folgenden auszug
welcher kurz nach dem eigentlich programm kam

char *eingabe(char *str){
     char input[MAX];

     printf("Bitte \"%s\" eingeben: ", str);
     fgets(input, MAX, stdin);
     return input;
}

tut mir total leid leute
ich hab mir diese zeilen auch gelesen... nur ich dachte er meinte das
folgende

ich tip die listen chronologisch ab, und das buch find ich auch sehr
gut und ausführlich, nur er gibt oft negativbeispiele an... in diesem
fall hab ichs nicht erkannt!!!

sorry leute!!!

bin normal vorsichtig beim posten

von mplusplus (Gast)


Lesenswert?

@Stefan

dieser Satz bezieht sich auf den Fall, dass die lokal definierte
Zeichenkette input direkt als Rückgabewert verwendet wird, also ohne
strtok().

Kann es vieleicht sein, dass strtok() den extrahierten String selber
nochmal als static speichert und den Zeiger auf diese (noch gültigen
Daten) zurückgibt? strtok() verwendet ja je nach Übergabewerten Daten
aus vorherigen Aufrufen. Ein Link dazu:
http://ivs.cs.uni-magdeburg.de/bs/lehre/wise0102/usp/seminare/seminar3.shtml

Grüße

mplusplus

von Raphael R. (raphael)


Lesenswert?

das hab ich ja anfangs auch vermutet!!!

aber da anscheinend BSP2 zufällig richtig läuft und BSP1 nicht
denke ich nicht dass strtok() den extrahierten string selber nochmal
als static speichert!

aber ich weiss es nicht...

ich sollt mich mit was leichterem befassen!!!

soweit bin ich wie man merkt noch nicht :-)

von Stefan (Gast)


Lesenswert?

Diese Beschreibung ist nicht eindeutig. Ich würde hierzu empfehlen nach
mehr Beschreibungen und Codebeispielen zu suchen.

Ich lese die Beschreibung in der Weise, dass strtok() sich die Adresse
des Strings merkt und mit der gemerkten Adresse arbeitet, solange wie
als erstes Argument NULL übergeben wird.

Ich entnehme das aus dem Hinweis, dass der übergebene Puffer
manipuliert wird. Die Funktion setzt nämlich ein Nullbyte an die Stelle
des gefundenen Trennzeichens im *Original*-Puffer.

von Karl heinz B. (kbucheg)


Lesenswert?

> :-) in zukunft werd ich mit static oder GLOBAL arbeiten!!!

Beides sind Varianten, die du nicht machen solltest!

DIe Methode: Einen Zeiger auf die Speicherfläche in die Funktion
hinein zu übergeben, ist die Methode, die du benutzen solltest.

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

Aber wenn du das schon so machst, dann falle nicht in die
gets() - fgets() Falle hinein:
Frage: Was ist falsch an gets() und wurde mit fgets() behoben?
Antwort: Man hat verabsäumt bei gets() einen weiteren Parameter
mit aufzunehmen, der gets darüber informiert wie gross den nun
der Buffer ist, in den es schreiben darf.

Genau wie bei test4()
test4 hat keine Möglichkeit festzustellen, wie gross den die Speicher-
fläche ist, auf die es einen Pointer bekommen hat. Daher kann test4
sich nicht davor schützen, diesen Buffer zu überlaufen.

Richtig wäre:

/* Möglichkeit3: Einen Zeiger als Argument übergeben */
char *test4(char *ptr, size_t BufferSize){
   if( BufferSize < 9 )
     return NULL;
   strcpy(buffer, "testwert");
   return ptr;
}

Und auf der Aufrufseite folgerichtig:

int main()
{
  char Buffer[4];

  if( test4( Buffer, sizeof( Buffer ) ) == NULL )
    printf( "Fehler: Buffer zu klein. Machen Sie das Array
groesser\n" );
}

von mplusplus (Gast)


Lesenswert?

@Stefan am 25.09.2006 14:31

Ja, das sehe ich jetzt nach einem Blick in den K&R auch so. Dann wird
wohl nur der Zeiger auf den Original-Puffer statisch gespeichert, nicht
aber der Puffer selber in einen statischen Puffer kopiert.

Die Rückgabe des Zeigers über strtok() ist im hier diskutierten Fall
demnach also genauso sinnlos wie den lokal definierten String direkt
zurückzugeben.

Grüße

mplusplus

von Raphael R. (raphael)


Lesenswert?

@karl heinz buchegger

ok werd ich machen!!!

denn wie heissts so schön:
---> "so LOKAL wie möglich, so GLOBAL wie nötig!!!"

lg und danke

>Raph

von Klaus____ (Gast)


Lesenswert?

@Karl Heinz B.:
Frage: Was ist falsch an strcpy() und wurde mit strncpy() behoben?
Antwort: ___________(Wissen wir alle)

Schönen Tag noch! Klaus

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

... die Liste ließe sich fortsetzen:

strcat / strncat
sprintf / snprintf

Beitrag #6958931 wurde von einem Moderator gelöscht.
Beitrag #6958947 wurde von einem Moderator gelöscht.
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.