Pagina 1 di 3 1 2 3 ultimoultimo
Visualizzazione dei risultati da 1 a 10 su 24

Discussione: [C] logica di fgets()

  1. #1
    Utente di HTML.it L'avatar di filips
    Registrato dal
    Sep 2011
    residenza
    Seconda stella a destra (questo e' il cammino)
    Messaggi
    155

    [C] logica di fgets()

    Usando questa funzione per raccogliere dallo standard input, per es.:

    fgets ( stringa, 30, stdin );

    ho il dubbio se, siccome una stringa termina con 0, anche la fgets agisca con questa logica apponendo lo 0.

    Purtroppo l'esempio che ho non chiarisce la cosa, anzi fa pensare che non venga inserita la terminazione stringa:

    codice:
    char nome[30];
    printf (“Inserisci il tuo nome e il tuo cognome: “);
    fgets ( nome, 30, stdin );
    nome[strlen(nome)-1] = 0;
    printf (“Ti chiami %s\n”, nome);
    //Il comando dato dopo fgets() è necessario perché tale funzione legge anche il
    carattere '\n', mettendolo alla fine della stringa.
    Per fare un tavolo ci vuole un fiore.

  2. #2
    La fgets:
    - termina sempre la stringa con il NUL (= il carattere 0);
    - legge tutti i caratteri dati in input fino al newline, compreso il newline a meno che il buffer che le hai passato non sia troppo corto, nel qual caso arriva a riempire fino al penultimo carattere del buffer (l'ultimo viene usato per il terminatore NUL);
    - se lo stream di input termina, non verrà "inventato" alcun newline.

    In pratica, fgets funziona così:
    codice:
    char *fgets(char *s, int size, FILE *stream) {
        int i, c;
        for(i=0; i<size-1; ++i) {
            c = getc(stream);
            if(c==EOF) break;
            s[i] = (char)c;
            if(c=='\n') break;
        }
        s[i]=0;
        if(ferror(stream) || (c==EOF && i==0)) return NULL;
        return s;
    }
    Per cui:
    - la stringa che ottieni è sempre una stringa C valida (ovvero, è sempre terminata con il NUL);
    - non vengono mai scritti caratteri oltre la dimensione che gli hai detto;
    - se c'è stata tutta la riga, l'ultimo carattere sicuramente è un newline;
    - se non c'è un newline in coda, o non c'è stata tutta la riga, oppure lo stream è terminato (o si è verificato un errore); puoi verificare questa condizione provando a leggere nuovamente e/o verificando lo stato dello stream con feof/ferror.

    Quindi,
    codice:
    nome[strlen(nome)-1] = 0;
    gestisce solo il caso "banale" - ovvero, c'è stato tutto e lo stream non è terminato; una gestione un po' più safe può essere:
    codice:
    int sz = strlen(nome);
    if(sz!=0 && nome[sz-1]=='\n') nome[sz-1]=0;
    - il check su sz!=0 è per essere sicuri che la stringa effettivamente contenga almeno un carattere prima di andare a vedere il penultimo carattere (modificare nome[-1] è undefined behavior);
    - il check sul fatto che il penultimo carattere sia effettivamente un newline è per non perdere l'ultimo carattere se siamo alla fine dello stream
    ... poi se vuoi gestire correttamente input più lunghi la cosa si fa un po' più complicata e richiede l'allocazione dinamica...
    Ultima modifica di MItaly; 22-09-2016 a 00:05
    Amaro C++, il gusto pieno dell'undefined behavior.

  3. #3
    Utente di HTML.it L'avatar di filips
    Registrato dal
    Sep 2011
    residenza
    Seconda stella a destra (questo e' il cammino)
    Messaggi
    155
    Ti ringrazio per questa bella risposta particolareggiata.

    Quote Originariamente inviata da MItaly Visualizza il messaggio
    La fgets:
    - legge tutti i caratteri dati in input fino al newline, compreso il newline a meno che il buffer che le hai passato non sia troppo corto, nel qual caso arriva a riempire fino al penultimo carattere del buffer (l'ultimo viene usato per il terminatore NUL);

    Non ho capito bene: intendi che il buffer passato sia coerente col size e tuttavia più corto dello stream?



    Quindi,
    codice:
    nome[strlen(nome)-1] = 0;
    gestisce solo il caso "banale" - ovvero, c'è stato tutto e lo stream non è terminato

    Temo che non sia esattamente questa l'intenzione dell'estensore, colpa mia per aver tagliato la nota all'esempio (ma era solo la prima parte rilevante per il mio dubbio):

    Il comando dato dopo fgets() è necessario perché tale funzione legge anche ilcarattere '\n', mettendolo alla fine della stringa. Se non si vuole terminare la stringa
    con un '\n' si piazza il carattere terminatore '\0' al suo posto (ovvero una posizione
    prima della fine della stringa).

    E comunque questa parte non mi sembrava/sembra molto chiara: il fatto che \n sia messo alla fine della stringa non sembra reggere col fatto che \0 lo rimpiazzi una posizione prima della fine della stessa. Per il resto mi sembra che volesse dire qualcosa tipo non terminare la stringa con '...\n\0', cioè se la si vuole terminare con esattamente l'ultimo carattere di input visualizzato sullo schermo.
    Ultima modifica di filips; 22-09-2016 a 02:15
    Per fare un tavolo ci vuole un fiore.

  4. #4
    Utente di HTML.it L'avatar di oregon
    Registrato dal
    Jul 2005
    residenza
    Roma
    Messaggi
    36,480
    La situazione mi sembra chiara ...

    Un metodo (un po' "rude") per rimediare è scrivere dopo la fgets

    strtok(nome, "\n");

    che risolve in ogni caso.
    No MP tecnici (non rispondo nemmeno!), usa il forum.

  5. #5
    Quote Originariamente inviata da filips Visualizza il messaggio
    Ti ringrazio per questa bella risposta particolareggiata.
    Non ho capito bene: intendi che il buffer passato sia coerente col size e tuttavia più corto dello stream?
    Più corto della riga da leggere.
    Temo che non sia esattamente questa l'intenzione dell'estensore, colpa mia per aver tagliato la nota all'esempio (ma era solo la prima parte rilevante per il mio dubbio):
    Nah, sono abbastanza sicuro che sia come dico, è un pattern diffuso.
    E comunque questa parte non mi sembrava/sembra molto chiara: il fatto che \n sia messo alla fine della stringa non sembra reggere col fatto che \0 lo rimpiazzi una posizione prima della fine della stessa. Per il resto mi sembra che volesse dire qualcosa tipo non terminare la stringa con '...\n\0', cioè se la si vuole terminare con esattamente l'ultimo carattere di input visualizzato sullo schermo.
    Non capisco cosa non ti è chiaro... se vuoi accorciare la "lunghezza logica" di una stringa basta che rimpiazzi il carattere a cui vuoi farla terminare con un \0; nel caso specifico, visto che la fgets ti restituisce un \n in coda, lo rimpiazzi con un \0, in modo da farlo sparire, scorciando la stringa di un carattere.

    La risposta di oregon andrebbe bene se non fosse che strtok non è né rientrante né thread-safe, per cui in linea di massima va evitata. Piuttosto, si può sfruttare la funzione (sconosciuta ai più, me compreso ) strcspn:
    codice:
    nome[strcspn(nome, "\n")] = 0;
    (strcspn(s, reject) restituisce la lunghezza del primo segmento di s che non contiene nessuno dei caratteri di reject, che è perfetta per il nostro caso, dato che di fatto restituisce la posizione del newline o, se non è presente, del NUL finale).
    Amaro C++, il gusto pieno dell'undefined behavior.

  6. #6
    Utente di HTML.it L'avatar di filips
    Registrato dal
    Sep 2011
    residenza
    Seconda stella a destra (questo e' il cammino)
    Messaggi
    155
    Quote Originariamente inviata da MItaly Visualizza il messaggio
    Nah, sono abbastanza sicuro che sia come dico, è un pattern diffuso.
    Eppure non si faceva presente il caso di non terminazione dello stream. A me sembra che volesse dire se non si vuole che nella stringa venga inserita una andata a capo. Tu però cosa intendi esattamente con

    "gestisce solo il caso "banale" - ovvero, c'è stato tutto e lo stream non è terminato"?

    con "tutto" intendi fino a \n e però non si è messo lo zero? però nel primo msg avevi detto "se lo stream di input termina, non verrà "inventato" alcun newline".. non mi è chiara questa cosa



    Non capisco cosa non ti è chiaro...
    il fatto che dica "mettendolo alla fine della stringa" e poi affermi "ovvero una posizione
    prima della fine
    della stringa". (Mi sembra che non abbia senso dire che \n sta alla fine se subito dopo si ammette che \0 la rimpiazza una posizione prima della fine; al limite poteva avere senso dire che piazzando lo zero al posto di \n, la stringa finiva una posizione prima della precedente.)
    Ultima modifica di filips; 26-09-2016 a 14:43
    Per fare un tavolo ci vuole un fiore.

  7. #7
    Quote Originariamente inviata da filips Visualizza il messaggio
    Eppure non si faceva presente il caso di non terminazione dello stream.
    Appunto, è per quello che dico che è incompleta, funziona correttamente nel caso "banale" in cui lo stream non è terminato.
    A me sembra che volesse dire se non si vuole che nella stringa venga inserita una andata a capo.
    Appunto-bis, vuole rimuovere l'a-capo che viene letto dalla fgets; il punto che dico è che non tiene conto che ci sono alcuni casi in cui questo a-capo non viene messo, che lui non gestisce.
    Tu però cosa intendi esattamente con

    "gestisce solo il caso "banale" - ovvero, c'è stato tutto e lo stream non è terminato"?

    con "tutto" intendi fino a \n e però non si è messo lo zero? però nel primo msg avevi detto "se lo stream di input termina, non verrà "inventato" alcun newline".. non mi è chiara questa cosa
    None! Intendo dire che gli hai passato un buffer lungo a sufficienza per leggere tutta la riga, newline compreso (e NUL compreso, che viene sempre messo), e che lo stream non è terminato, ovvero l'utente non ha premuto Ctrl-D (o Ctrl-Z su Windows) o, se l'input è stato rediretto da file, il file non è terminato. Non viene gestito invece il caso di stream terminato (cosa che capita sempre se si sta leggendo da file o in generale lo standard input è stato rediretto).
    il fatto che dica "mettendolo alla fine della stringa" e poi affermi "ovvero una posizione
    prima della fine
    della stringa". (Mi sembra che non abbia senso dire che \n sta alla fine se subito dopo si ammette che \0 la rimpiazza una posizione prima della fine; al limite poteva avere senso dire che piazzando lo zero al posto di \n, la stringa finiva una posizione prima della precedente.)
    È solo una confusione di terminologia. La stringa finisce dove sta il NUL. Se non c'è un NUL, non è una stringa C e non ha senso parlare di fine della stringa (esistono le stringhe counted, ma è un altro paio di maniche). Se rimpiazzi un carattere che attualmente sta prima del NUL con un NUL, la stringa si accorcia, troncando via il carattere che hai sostituito e tutti quelli che lo seguivano.
    Amaro C++, il gusto pieno dell'undefined behavior.

  8. #8
    Utente di HTML.it L'avatar di filips
    Registrato dal
    Sep 2011
    residenza
    Seconda stella a destra (questo e' il cammino)
    Messaggi
    155
    Non credo di aver capito appieno il discorso su ctrl-D/Z e la terminazione. Per es. da stdin non basta premere Enter per terminare e inserire la linea di input? Quella combinazione di tasti
    non dà forse un EOF? Cosa c'entra EOF? Praticamente stai dicendo che tutti i caratteri inseriti ci sono stati, a parte EOF (successivo a Enter?)?

    Se è giusto tutto questo allora credo di aver capito che con "non gestisce" tu intendessi il caso in cui ci sia stato tutto fatta eccezione per EOF, visto che strlen(nome)-1, nell'intenzione dell'esempio di beccare il newline, deve appunto per forza corrispondere al newline, segno quindi che dopo non c'è nessun EOF, ma solo il null. Se è corretto ripeto tuttavia il mio dubbio: a che serve mettere ctrl-D/Z per affermare che così un input da tastiera sia terminato? Questo credo avrebbe significato solo nel caso in cui lo stream dovesse venir accolto in un file, mentre l'esempio non sembra riferirsi a questa circostanza, ma a una semplice stringa nome.
    Ultima modifica di filips; 17-10-2016 a 07:35
    Per fare un tavolo ci vuole un fiore.

  9. #9
    Utente di HTML.it
    Registrato dal
    Nov 2005
    Messaggi
    11
    Quote Originariamente inviata da filips Visualizza il messaggio
    Non credo di aver capito appieno il discorso su ctrl-D/Z e la terminazione. Per es. da stdin non basta premere Enter per terminare e inserire la linea di input? Quella combinazione di tasti
    non dà forse un EOF? Cosa c'entra EOF? Praticamente stai dicendo che tutti i caratteri inseriti ci sono stati, a parte EOF (successivo a Enter?)?
    No, ENTER genera un '\n'.
    Tra l'altro EOF non è un carattere ma una condizione, ossia, quando si legge oltre la fine del file.
    Ti sembrerà strano ma ha senso parlare di EOF anche quindo l'input è da tastiera.

    Semplicemente fgets smette di leggere solo in due casi: quando incontra '\n' o si ha la condizione di EOF.
    In tutti i due casi il buffer conterrà sempre il carattere NULL e se c'è spazio e la condizione non è di EOF ci sara anche il '\n'. Tutto questo è scritto nelle specifiche di fgets.

  10. #10
    Utente di HTML.it L'avatar di filips
    Registrato dal
    Sep 2011
    residenza
    Seconda stella a destra (questo e' il cammino)
    Messaggi
    155
    Scusa un ultimo dubbio..
    Allora ho interpretato bene (in effetti era esattamente quello che c'era scritto nel tuo penultimo msg), il "non è terminato" si riferiva a EOF. Ma come si fa?: prima premo Enter e poi cttl+D.. Ma se premo Enter si termina e si spedisce l'input a destinazione.. Non credo che abbia qualche effetto ulteriore EOF. Inoltre se non è un carattere, la lunghezza della stringa non dovrebbe variare: 'abc\nEOF' dovrebbe misurare 4. Infine, che utilità dà EOF in un input da tastier se prima c'è già Newline che sembra assolvere lo stesso compito? Non è una specie di doppione?
    Per fare un tavolo ci vuole un fiore.

Tag per questa discussione

Permessi di invio

  • Non puoi inserire discussioni
  • Non puoi inserire repliche
  • Non puoi inserire allegati
  • Non puoi modificare i tuoi messaggi
  •  
Powered by vBulletin® Version 4.2.1
Copyright © 2025 vBulletin Solutions, Inc. All rights reserved.