Gets() dinamico senza sfondamento del bu

di il
15 risposte

Gets() dinamico senza sfondamento del bu

In un altro filone di discussione, si poneva la più che opportuna questione dell'inaffidabilità di gets(). E allora perché non fare una versione di gets() che non soffra del difetto dovuto alla possibilità di "sfondare" i limiti del buffer passato per l'immissione? Ci ho provato e questo è il risultato:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

size_t gets_dinamico( char **out_s );

int main() {
    char *s = NULL;
    size_t dim;

    dim = gets_dinamico( &s );

    if( dim != ((size_t)-1) ) {
        printf( "%s (%u caratteri)\n", s, dim );
        free( s );
    }
    else {
        printf( "Errore.\n" );
    }

    printf( "Premi \"invio\" per uscire...   " );
    getchar();
    return 0;
}

/*==============================================================================
La funzione si propone di costituire un'alternativa alla funzione standard del C
gets(), impiegando la memoria dinamica per evitare la ben nota possibilita' di
"sfondamento" del buffer.
Il parametro out_S e' un puntatore a puntatore a char destinato a ricevere, in
uscita, il puntatore a un'area della memoria dinamica allocata dinamicamente per
contenere la stringa immessa in console. Spetta al chiamante liberare la memoria
allocata.
Il valore di ritorno segnala la quantita' dei caratteri presenti nella stringa
immessa in console (escluso il carattere terminatore '\0') e, di riflesso, le
dimensioni del buffer diminuite di una unita'.
In caso d'errore, *out_s non viene modificato e la funzione restituisce
((size_t)-1) (il valore massimo rappresentabile per mezzo di una variabile di
tipo size_t).
==============================================================================*/

size_t gets_dinamico( char **out_s ) {
    if( out_s ) { /* parametro valido? */
        const size_t incremento = 256;
        size_t dimBuff = incremento;
        size_t lFGetS = 0;
        size_t indice = 0;

        /* alloca la memoria "di lavoro" */
        char *sTmp = calloc( dimBuff, sizeof(*sTmp) );

        while( sTmp ) {
            char *pAux; /* un puntatore ausiliario */

            if( fgets(sTmp+indice,incremento,stdin) ) {
                lFGetS = strlen( sTmp+indice );

                if( (sTmp+indice)[lFGetS-1]!='\n' ) {
                    /* rialloca e controlla la memoria "di lavoro" */
                    if( (pAux=realloc(sTmp,dimBuff+lFGetS)) ) {
                        sTmp = pAux;
                        dimBuff += lFGetS;
                    }
                    else { /* riallocazione fallita */
                        if( sTmp ) {
                            free(sTmp);
                            sTmp = NULL;
                        }
                    }
                }
                else { /* '\n' in coda al buffer - fine immissione */
                    (sTmp+indice)[lFGetS-1] = '\0'; /* elimina '\n' */
                    /* "contrae" il piu' possibile la memoria di lavoro */
                    if( (pAux=realloc(sTmp,indice+lFGetS)) )
                        sTmp = pAux;
                    break;
                }
            }
            else { /* fgets() ha restituito NULL */
                if( sTmp ) {
                    free(sTmp);
                    sTmp = NULL;
                }
            }
        }

        if( sTmp ) {
            *out_s = sTmp; /* comunica l'esito in *out_s */
            return indice+lFGetS-1; /* quantita' dei caratteri nella stringa */
        }
    }

    return (size_t)-1; /* errore, segnala un valore un po'... eccessivo! */
}
Può tornare utile?

15 Risposte

  • Re: Gets() dinamico senza sfondamento del bu

    No. Stai solo complicando le cose. Ci sono talmente tanti errori che non saprei da quale iniziare, senza contare che il codice è assolutamente illeggibile e di solito ignoro post contenenti codice così mal indentato; è che non riesco a prendere sonno e mi annoio

    Riassumendo: non è forse ora di acquistare quella dozzina di manuali indispensabili sul linguaggio C ed un'altra dozzina di testi sull'ingegneria del software? Potresti iniziare da un manuale di informatica language-agnostic per poi continuare col C seguendo le famose bibliografie sull'argomento scopiazzate un po' ovunque nel presente forum ed altrove. In seguito puoi pensare ai vari Sommerville, Pressman, Meyer, Glenford, Knuth, Sedgewick, CLRS etc etc
    Fare così "a orecchio" non porta da nessuna parte.
  • Re: Gets() dinamico senza sfondamento del bu

    Può tornare utile?
    A parte tutte le considerazioni già espresse e un certo effetto rilassante su di te, esiste la fgets ... perché mai dovrebbe servire quello che hai scritto?
  • Re: Gets() dinamico senza sfondamento del bu

    Loopunrolling, accetto di buon grado la critica perché so chi sono e so quali sono i miei limiti. Per aiutarmi a superarli, me ne proponi una versione che assolva gli stessi compiti ma ben scritta? Magari così me la studio e imparo qualcosa. Finché mi dici "brucco, cacca, diavolo" non è che ne ricavi gran che.

    L'idea è:

    a) una funzione (una) che b) allochi un buffer su misura per contenere esattamente una stringa in ingresso da console di lunghezza qualsiasi, 3) ne collochi l'indirizzo in un puntatore passato come parametro e 4) restituisca la lunghezza della stringa immessa o un valore convenzionale per indicare eventuali errori.

    Sul male indentato, invece, non ti seguo proprio: ho ricontrollato senza trovare un rientro fuori posto (ci sto molto attento, perché se no poi finisco per perdermi e non ci capisco più niente). Se mi dici qualcosa in più ci ripenso, diversamente... boh?

    Oregon, il fatto è che se è vero che fgets() salvaguarda dagli sfondamenti del buffer, fallisce se il buffer stesso ha dimensioni insufficienti, nel senso che riporta solo una parte dell'immissione. In più c'è la seccatura del dover verificare a posteriori la lunghezza della stringa immessa, di dover tenere conto della presenza o meno del '\n' in coda e del fare i conti con gli eventuali caratteri rimasti "pendenti" nello stream. Ho cercato di aggirare queste caratteristiche, che a me sembrano poco pratiche, per avere una funzione che, chiamata, dia con certezza una stringa ben dimensionata e ne comunichi la lunghezza. Magari poi uno se la inserisce in una libreria, la precompila e non ci pensa più.
  • Re: Gets() dinamico senza sfondamento del bu

    La gets e la fgets nascono per l'input delle righe di testo da tastiera/file. Quando hai usato un buffer di 1000 caratteri in una variabile locale, hai soddisfatto il 99,999 % delle necessità derivanti dal suo uso. Non ha alcun senso prevedere buffer dinamici.

    Esclusa la tua soddisfazione personale, tornando alla tua domanda originale - torna utile - ti ribadisco di no.
  • Re: Gets() dinamico senza sfondamento del bu

    Una robetta scolastica di poco conto potrebbe essere questa:
    io.h
    
    /*
     * To change this license header, choose License Headers in Project Properties.
     * To change this template file, choose Tools | Templates
     * and open the template in the editor.
     */
    
    /* 
     * File:   io.h
     * Author: debian
     *
     * Created on 29 luglio 2017, 11.21
     */
    
    #ifndef IO_H
    #define IO_H
    #define IO_DEBUG
    /* Tipo booleano definito per il C89*/
    typedef enum {FALSE, TRUE} boolean;
    /* Struttura per la memorizzazione dell'input*/
    typedef struct
    {
        char *mem;
        int size;
    }
    io_buff;
    /*Legge una linea da stdin*/
    boolean readline(io_buff *line);
    #endif /* IO_H */
    
    readline.c
    
    /*
     * To change this license header, choose License Headers in Project Properties.
     * To change this template file, choose Tools | Templates
     * and open the template in the editor.
     */
    #include <stdio.h>
    #include <stdlib.h>
    #include "io.h"
    
    #define READLINE_DEBUG
    /*
    ** Legge una riga da stdin come da manuale di fgets (man fgets).
    ** Accetta come parametro *line, dove line->size e' la dimensione del vettore,
    ** mem è un puntatore al vettore stesso.
    ** readline (nome infelice poiche' gia' esistente in unix) restituisce TRUE in
    ** caso di successo, FALSE diversamente. (Attenzione a *size* ;) )
    */
    boolean readline(io_buff *line)
    {
        if(NULL == line || NULL == line->mem)
        {
    #if defined(IO_DEBUG) || defined(READLINE_DEBUG)
            (void) fputs("**********DEBUG**********\n",stderr);
            (void) fputs("Errore in readline.c: NULL pointer non previsto!\n", stderr);
            (void) fputs("boolean readline(io_buff *line)\n", stderr);
            (void) fputs("                 ~~~~~~~~^^^^^\n", stderr);
            if(NULL == line)
            {
                (void) fputs("(iobuff) *line = NULL\n", stderr);
            }
            else
            {
                (void) fputs("(iobuff) line->mem = NULL\n", stderr);
            }
            (void) fputs("**********FINE DEBUG**********\n",stderr);
    #endif
            return (FALSE);
        }
        /*Legge finalmente la stringa*/
        if(NULL == fgets(line->mem, line->size, stdin))
        {
    #if defined(IO_DEBUG) || defined(READLINE_DEBUG)
            (void) fputs("**********DEBUG**********\n",stderr);
            (void) fputs("Errore in readline.c: fgets ha restituito NULL!\n", stderr);
            (void) fputs("**********FINE DEBUG**********\n",stderr);
    #endif
            return (FALSE);
        }
        return (TRUE);
    }
    
    main.c
    
    /*
     * To change this license header, choose License Headers in Project Properties.
     * To change this template file, choose Tools | Templates
     * and open the template in the editor.
     */
    
    /* 
     * File:   main.c
     * Author: debian
     *
     * Created on 29 luglio 2017, 11.21
     */
    
    #include <stdio.h>
    #include <stdlib.h>
    #include "io.h"
    #define SIZE_MAX (128)
    /*
     * Driver per la lettura di stringhe da stdin.
     * Questo non è un test!
     */
    int main(void)
    {
        char buff[SIZE_MAX];
        io_buff line;
        boolean success;
        
        line.mem = buff;
        line.size = SIZE_MAX;
        
        (void) fputs("Inserisci una stinga: ", stdout);
        success = readline(&line);
        if(FALSE == success)
        {
            (void) fputs("Errore: impossibile leggere la stringa!\n", stderr);
            /* Error recovering qui, ma per semplificare usciamo*/
            return (EXIT_FAILURE);
        }
        (void) fputs("Hai inserito: ", stdout);
        (void) fputs(buff, stdout);
        return (EXIT_SUCCESS);
    }
    
    Rifletti su size comunque. Il codice proposto, mi ripeto, va bene per un compito in classe ma non oltre, anche se passa il controllo con SPLint. Sicuramente non è conforme al MISRA/C.
  • Re: Gets() dinamico senza sfondamento del bu

    @loopunrolling
    Va be', però ha caratteristiche diverse da quelle che avevo indicato. Sarebbe come se ti chiedessi di mostrarmi un motocarro e tu i portassi ad esempio una motocicletta.

    P.S. Grazie invece per avermi portato a conoscenza di SPLint e MISRA/C (non ne avevo mai sentito parlare). Oggi pomeriggio leggo qualcosa in merito e vedo cosa posso ricavarne.
  • Re: Gets() dinamico senza sfondamento del bu

    Oregon, lasciamo quindi da parte le dimensioni del buffer e la sua allocazione dinamica. Le altre caratteristiche?
    Comunque non c'è problema, la terrò per i miei passatempo.
  • Re: Gets() dinamico senza sfondamento del bu

    Semplicemente non è compito della tua funzione allocare memoria per la stringa. È anche questione di riusabilità, infatti se io volessi usare tua funzione ma senza allocazione dinamica, dovrei scriverne un'altra solo per poter usare un semplice vettore statico e ciò va contro i principi dell'Ingegneria del Software.
    Ad ogni fgets effettua controlli sui parametri e restituisce NULL in caso di errore, ma questo comportamento potrebbe essere implementation-defined, ossia dipendente dalla libreria C adoperata.

    PS: per formattare il codice C puoi usare AStyle http://astyle.sourceforge.ne
  • Re: Gets() dinamico senza sfondamento del bu

    Conosco astyle, che è incluso nell'ide che son solito impiegare. Non mi piace quel tipo di formattazione, la trovo troppo dispersiva nella sua componente "verticale" (nel senso che occupa troppo spazio e porta troppo presto il codice oltre le dimensioni del monitor, il che per me è un problema).

    In merito allo standard, ho letto diverse fonti, e tutte sono allineate su qualcosa di questo tipo, pur con diverse formulazioni:
    char * fgets ( char * str, int num, FILE * stream );
    Reads characters from stream and stores them as a C string into str until (num-1) characters have been read or either a newline or the end-of-file is reached, whichever happens first.
    A newline character makes fgets stop reading, but it is considered a valid character by the function and included in the string copied to str.
    A terminating null character is automatically appended after the characters copied to str.
    On success, the function returns str.
    If the end-of-file is encountered while attempting to read a character, the eof indicator is set (feof). If this happens before any characters could be read, the pointer returned is a null pointer (and the contents of str remain unchanged).
    If a read error occurs, the error indicator (ferror) is set and a null pointer is also returned (but the contents pointed by str may have changed).
    In questo caso specifico la fonte è http://www.cplusplus.com/reference/cstdio/fgets
    Non si può dunque dare per scontato che una funzione standard sia conforme allo standard?

    Anche se Oregon ha detto chiaramente che secondo lui non è molto sensato ricorrere all'allocazione dinamica, per spura speculazione vale la pena notare che la funzione che ho proposto nasce proprio con quello scopo, e lo si capisce fin dal nome: gets_dinamico(). E' di nuovo il discorso del motocarro e della motocicletta: se devo portare tre materassi in discarica scelgo il motocarro, se devo infilare piega su piega col ginocchio a terra tra le curve del passo del Bracco scelgo la motocicletta. Non per niente uno si chiama motocarro e l'altra motocicletta.

    La readline() che mi hai proposto non riscrive meglio gets_dinamico(), è un'altra cosa. Mi è senz'altro utile per vedere un tipo diverso d'approccio, in particolare per il modo in cui hai inserito quei comportamenti per il debug (nei manuali che ho letto e che uso come riferimento non se ne fa cenno), però non è attinente con la finalità che volevo raggiungere -- togliermi dai piedi con una sola funzione confinata entro se stessa il pesantore di certi passi di routine richiesti dall'uso di fgets().
  • Re: Gets() dinamico senza sfondamento del bu

    Va beh ... ho deciso che mi dedicherò ad insegnare musica, per distrarmi durante l'estate ... meglio della settimana enigmistica...
  • Re: Gets() dinamico senza sfondamento del bu

    Faresti un'ottima cosa! Insegnarla magari no, ma praticarla sì. E' una visione elitaria quella di chi pensa che siano musicisti degni di far musica solo Bach, Beethoven e Stravinsky, anche perché i grandi ci mettono il genio, ma chi ascolta(va) o strimpella(va) la loro musica ci mette(va) i denari per mantenerli (particolare non secondario). Ovvio che AldoBaldo non suonerà mai come Maurizio Pollini, ma questo dovrebbe indurre tutti gli AldiBaldi a tenersi alla larga dalla pratica musicale? Per fortuna ci sono anche i liceali che strimpellano "Vorrei ma non posto" e ancor più per fortuna si divertono un mondo. Intanto i geni continuano a dar vita alle loro genialate nonostante le loro stecche più o meno plateali.

    P.S. Poi ci sono i vecchietti che si danno al karaoke onorando Modugno o addirittura Natalino Otto con la loro passione.
  • Re: Gets() dinamico senza sfondamento del bu

    No ... no ... intendo proprio "insegnare" ... io ci tento ... mi documenterò per un po' con qualche guida su Internet, qualche pdf, magari un testo letto velocemente ...

    Se qualcuno mi dirà, a cose fatte, che non è il caso, nemmeno per passare il tempo, non so ... vedremo ...
  • Re: Gets() dinamico senza sfondamento del bu

    D'accordo. Dirò al mio cuginetto di dodici anni di smettere di giocare a pallone in cortile, perché se no Maradona e Totti potrebbero risentirsi.
  • Re: Gets() dinamico senza sfondamento del bu

    Non capisco in cosa differisca la tua funzione dalla già esistente getline: .
Devi accedere o registrarti per scrivere nel forum
15 risposte