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

Gast #6207893
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ß,
Gast #6207925
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);
Gast #6207992
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
}
#6208027
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;
Gast #6208038
> 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.
Gast #6208100
>> 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.
Gast #6208111
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
#6208129
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.
Gast #6208279
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.
#6208290
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?
Gast #6208308
> 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.
#6208324
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.

Antwort schreiben

Eine eigene Frage? Dieser Thread ist über 6 Jahre alt. Stell sie in einem eigenen Thread – dort lesen mehr Leute mit, und sie wird öfter und schneller beantwortet.

Neuen Thread starten

Eine Lösung, Ergänzung oder Rückmeldung zu diesem Thread? Dann antworte hier.

Formatierung
  • [c]C-Code[/c]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] – Link zu einem Artikel
  • Verweis auf einen anderen Beitrag: Rechtsklick auf dessen Beitrags-ID (#8078628), „Adresse kopieren“, in den Text einfügen

Mehr zur Formatierung

Längeren Sourcecode als Anhang, nicht in den Text. JPG nur für Fotos und Scans; Zeichnungen und Screenshots als PNG oder GIF. Siehe Bildformate.

Schon registriert? Anmelden

Wird nicht angezeigt. Wir schicken dir einen Link; sobald du damit bestätigst, erscheint dein Beitrag.

Mit dem Abschicken legst du einen kostenlosen Account an und erkennst die Nutzungsbedingungen und die Datenschutzerklärung an.