Forum: PC-Programmierung C Anfänge: User input string, fgets(), max. Arraygröße


von Gerd (Gast)


Lesenswert?

Hi,

beim Einlesen in Strings und Strings per Benutzereingabe bin ich auf 
eine Frage gestoßen (die ich aber erst am Ende stellen werde :) ).

Ziel ist es, dass der User über die Tastatur einen String eingeben soll. 
Das char Array beträgt 12 Elemente. Wenn der String > 12 Zeichen hat, 
sollen nur die ersten 12 gespeichert werden - das ist OK (dynamische 
Arraygröße etc wurde noch nicht behandelt).

Ich weiß, dass scanf() idR. nur ein einziges Wort einliest ("Gerd Gerd" 
geht zB nicht, sondern hört beim ersten Gerd auf).
Daher gets(), der einen ganzen String einliest.

Sowohl scanf() und gets() haben das Risiko mit dem Bufferüberlauf, wenn 
das char Array nicht groß genug für den String ist, und der Zeiger 
anschließend im Nirvana landet.

Jetzt gibt es die Möglichkeit, fgets() zu verwenden, bei der man die 
Arraygröße mit angeben kann. Es wird also nur bis zur Arraygröße 
eingelesen. Weil im Input Line aber immer noch Zeichen vorhanden sein 
können (wenn die Stringeingabe größer als das Array ist), kann man nicht 
unmittelbar danach fgets() erneut verwenden für eine neue Eingabe.

Wie löst man das am geschicktesten/elegantesten?

Ich habe bin mit Hilfe des Buches (C Primer Plus) auf folgenden Ansatz 
gekommen, und würde mich über Feedback freuen:
1
#include <stdio.h>
2
#include <stdlib.h>
3
#include <string.h>
4
5
void getInput(char * myName);
6
7
#define STLEN 12
8
9
int main()
10
{
11
    char myName[STLEN];
12
    printf("Enter your name: ");
13
    getInput(myName);
14
    printf("Your name is: %s\n",myName);
15
    printf("Enter your name: ");
16
    getInput(myName);
17
    printf("Your name is: %s\n",myName);
18
    return 0;
19
}
20
21
void getInput(char * words)
22
{
23
    int i = 0;
24
    while(fgets(words,STLEN,stdin) != NULL && words[0] != '\n')
25
    {
26
        i=0;
27
        while(words[i] != '\n' && words[i] != '\0')
28
        {
29
            i++;
30
        }
31
        if(words[i]=='\n')
32
        {
33
            words[i] = '\0';
34
            break;
35
        }
36
        else
37
        {
38
            while(getchar() != '\n')
39
            {
40
                continue;
41
            }
42
            break;
43
        }
44
    }
45
}

Geht das einfacher/eleganter?

Danke und Gruß,

von cppbert (Gast)


Lesenswert?

allgemein erstmal scanf_s verwenden - schon mal sicherer

z.B.
1
char string[4];
2
scanf("%3s", string);

oder
1
char string[4];
2
scanf("%*s", sizeof(string)-1, string);

von Dirk B. (dirkb2)


Lesenswert?

Wenn noch mehr Zeichen im Eingabestrom stehen, dann hat fgets das '\n' 
nicht im Array abgelegt, da dafür kein Platz mehr war.

von foobar (Gast)


Lesenswert?

fgets ist nichts Magisches, die benutzt auch nur fgetc/getchar um eine 
Zeile einzulesen.  Mach dir halt eine eigene Routine, z.B.:
1
// Get one line and store first size-1 chars in buf.
2
// Returns buf or NULL on EOF.
3
char *ngets(char *buf, int size)
4
{
5
    int i, c;
6
7
    i = 0;
8
    while ((c = getchar()) != EOF && c != '\n')
9
        if (i+1 < size)  // +1 for the '\0' below
10
            buf[i++] = c;
11
12
    if (i < size)
13
        buf[i] = '\0';
14
15
    return (c == EOF && i == 0) ? NULL : buf;
16
}

von Bauform B. (bauformb)


Lesenswert?

foobar schrieb:
> return (c == EOF && i == 0) ? NULL : buf;

Warum i==0? Was passiert bei einem I/O-Error oder wenn die letzte Zeile 
nicht mit '\n' aufhört?

Außerdem fehlen jede Menge { }. Das if am Ende könnte ich notfalls noch 
durchgehen lassen, aber sowas macht nur ein Gefahrensucher:
1
while ((c = getchar()) != EOF && c != '\n')
2
        if (i+1 < size)  // +1 for the '\0' below
3
            buf[i++] = c;

von foobar (Gast)


Lesenswert?

> Warum i==0? Was passiert bei einem I/O-Error oder wenn die letzte Zeile
> nicht mit '\n' aufhört?

Die Routine verhält sich wie ANSI gets: Wenn schon Zeichen gelesen 
wurden, werden die auf jeden Fall zurückgegeben; NULL gibt's nur bei EOF 
als erstem Zeichen.

von Bauform B. (bauformb)


Lesenswert?

foobar schrieb:
> Die Routine verhält sich wie ANSI gets

Nichts ist so unbrauchbar, dass es nicht als schlechtes Beispiel dienen 
könnte... Die Steigerung davon sind Funktionen, die mal und mal nicht 
die '\0' mitliefern. Eigentlich sollte es ja längst (seit C11) gets_s() 
geben, was ist denn daraus geworden?

von foobar (Gast)


Lesenswert?

>> Die Routine verhält sich wie ANSI gets
>
> Nichts ist so unbrauchbar, dass es nicht als schlechtes Beispiel dienen
> könnte...

Nicht das ich die stdio-Routine gerne verteidige, aber dieses Verhalten 
bzgl EOF ist in Ordnung.  Es stellt sicher, dass keine Zeichen verloren 
gehen (z.B. die von dir genannte "letzte Zeile ohne \n" wird geliefert). 
Erkauft wird das damit, dass ein EOF oder I/O-Error evtl erst beim 
nächsten Aufruf gemeldet wird.

...

Oh, gerade mal im avr-libc-1.8.0 nachgeschaut: da wird es falsch 
behandelt, ein EOF macht augenblicklich ein "return NULL;" - die letzte 
Zeile ohne \n geht verloren.

von wangnick (Gast)


Lesenswert?

Bauform B. schrieb:
> Nichts ist so unbrauchbar, dass es nicht als schlechtes Beispiel dienen
> könnte... Die Steigerung davon sind Funktionen, die mal und mal nicht
> die '\0' mitliefern.

Unter welchen Umständen wird denn kein '\0' angehängt?

LG, Sebastian

von Bauform B. (bauformb)


Lesenswert?

wangnick schrieb:
> Bauform B. schrieb:
>> Nichts ist so unbrauchbar, dass es nicht als schlechtes Beispiel dienen
>> könnte... Die Steigerung davon sind Funktionen, die mal und mal nicht
>> die '\0' mitliefern.
>
> Unter welchen Umständen wird denn kein '\0' angehängt?
>
> LG, Sebastian

Das ging erstmal gegen das alte gets() und dann, weil ich gerade am 
meckern war, allgemein gegen Funktionen, die mal keine '\0' liefern. 
foobar hätte halt gets() nicht erwähnen sollen ;) Sein ngets() ist ja 
deutlich braver.

von zitter_ned_aso (Gast)


Lesenswert?

foobar schrieb:
> if (i < size)
>         buf[i] = '\0';
>
>     return (c == EOF && i == 0) ? NULL : buf;

reicht da nicht einfach:
1
      buf[i] = '\0';
2
 
3
     return (i == 0) ? NULL : buf;

von foobar (Gast)


Lesenswert?

> reicht da nicht einfach: [...]

Das "if" schützt vor size<=0 und ohne das c==EOF würde auch eine 
Leerzeile ein NULL generieren.

von zitter_ned_aso (Gast)


Lesenswert?

foobar schrieb:
> Das "if" schützt vor size<=0

ach so, klar ;-)

foobar schrieb:
> ohne das c==EOF würde auch eine
> Leerzeile ein NULL generieren.

Ja, kompiziert.

Dieses "EOF" hat ja was mit Files zu tun. Und die Eingabe kann ja per 
Tastatur aber auch per File erfolgen.
1
./my_program < input.txt


Und falls "input.txt" leer ist, dann gibt es Probleme. Dann werden die 
Ausgaben
1
   ngets(name, NAME_LEN);                                        
2
   printf("%s\n", name);
und
1
    printf("%s\n", ngets(name, NAME_LEN));

untershiedlich sein. Wegen der letzten drei Zeilen.

von Dirk B. (dirkb2)


Lesenswert?

zitter_ned_aso schrieb:
> Dieses "EOF" hat ja was mit Files zu tun.

Du kannst EOF auch mit der Tastatur erzeugen Ctrl-D auf Linux und Ctrl-Z 
auf Windows.

zitter_ned_aso schrieb:
> Und falls "input.txt" leer ist, dann gibt es Probleme. Dann werden die
> Ausgaben
>    ngets(name, NAME_LEN);
>    printf("%s\n", name);
>
> und    printf("%s\n", ngets(name, NAME_LEN));
>
> untershiedlich sein.

Was soll da unterschiedlich sein?

von foobar (Gast)


Lesenswert?

> Dieses "EOF" hat ja was mit Files zu tun.

Kann evtl aber auch über die Tastatur simuliert werden, bei Linux z.B. 
default-mäßig per CTRL-D.

> Dann werden die Ausgaben [...] untershiedlich sein.

Korrekt.  Und zwar gewollt, um ein Ende der Datei überhaupt erkennen zu 
können.  Was sollte er sonst liefern?  Unendlich viele Leerzeilen?

Die typische Nutzung sähe z.B. so aus:
1
    char buf[32];  // short lines ...
2
    int i = 0;
3
4
    printf("Anfang\n");
5
6
    while (ngets(buf, sizeof(buf)))
7
        printf("Zeile %d: '%s'\n", ++i, buf);
8
9
    printf("Ende\n");


> Wegen der letzten drei Zeilen.

Nur die letzte.  Die beiden davor kümmern sich darum, dass der Buffer 
immer schön 0-terminiert ist.

von Bauform B. (bauformb)


Lesenswert?

zitter_ned_aso schrieb:
> Und falls "input.txt" leer ist, dann gibt es Probleme. Dann werden die
> Ausgaben
>    ngets(name, NAME_LEN);
>    printf("%s\n", name);
>
> und    printf("%s\n", ngets(name, NAME_LEN));
>
> untershiedlich sein.

Man könnte das auch als Feature betrachten und eher das
printf("%s\n", ngets(name, NAME_LEN));
als Problem. Nicht jedes printf() druckt "(null)" und es ist auch nicht 
klar, ob das ein Feature ist.

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.