Newsgroup: it.comp.lang.c
Titolo: domanda dichiaratamente stupida...ma...

salve a tutti... sto facendo un programmino che manipola strincghe immesse
dall'utente...e mi e' venuto uno di quei dubbi atroci che ti colgono
all'improvviso e non sai come risolverli...
spiego:
io devo accettare l'input di una stringa da parte dell'utente... questa
stringa deve essere messa in un'array stringa (ovviamente) che io
definisco all'inizio del programma...
ammettiamo che io inizializzi l'array a 100 bytes.. ma l'utente introduca
120 bytes... ovviamente l'input traborda dalla variaile e mi va ad
invadere parte della memoria, sovrascrivendo altre variabili 
ecco: il dubbio atroce e' questo... come faccio a controllare l'input
dell'utente PRIMA di metterlo nella variabile, per verificare un eventuale
overflow e bloccarlo in tempo?
le funzioni gets e scanf non controllano l'overflow... quindi?
grazie infinite... a volte sono le cose stupide che sono quelle che uno
non sa come affrontare... :)
saluto
---------------------------
quela:~# mount -t vfat /dev/hda1 /windows
mount: error mounting /dev/hda1... mmmh...It's full of rubbish !!
Try this instead:     mke2fs -c /dev/hda1 <block size>
It will work better !
---------------------------
                                          Stefano Borini

non sa come affrontare... :)
Per carita`, e` *tutt'altro* che una cosa stupida!!!
Magari tutti i programmatori avessero l'attenzione e
la cura che dimostri tu ponendoti questi problemi!!!
Ah, a proposito, e per il beneficio di quelli che dicono
che non val la pena di usare il C++ per "programmi
corti": in C++ standard, l'equivalente del brano di
programma in questione e`:
    getline(stdin, lastringa);
(dove lastringa, naturalmente, e` una std::string, che
si rialloca allungandosi alla bisogna).  Eventuali
errori sono automaticamente segnalati attraverso
"eccezioni" (che, volendo, possono essere gestite
comodamente anche in una routine chiamante).
Anche se tutto il resto di un breve programma non 
usasse alcuna altra feature del C++, non varebbe
gia` la pena di avere il C++ Standard anche solo per
fare un input decentemente robusto senza tutta
'sta fatica...?  Meditate, gente, meditate...!
Alex

Borini Stefano (
stu2528@NOSPAM_ME_OR_ILL_BOMB_YOU.student.unife.it
) wrote:
[ snip ]
: io devo accettare l'input di una stringa da parte dell'utente... questa
: stringa deve essere messa in un'array stringa (ovviamente) che io
: definisco all'inizio del programma...
: ammettiamo che io inizializzi l'array a 100 bytes.. ma l'utente introduca
: 120 bytes... ovviamente l'input traborda dalla variaile e mi va ad
: invadere parte della memoria, sovrascrivendo altre variabili 
: ecco: il dubbio atroce e' questo... come faccio a controllare l'input
: dell'utente PRIMA di metterlo nella variabile, per verificare un eventuale
: overflow e bloccarlo in tempo?
: le funzioni gets e scanf non controllano l'overflow... quindi?
Infatti l'uso di gets e' sconsigliato proprio per questo motivo.
Puoi usare qualcosa tipo "fgets(buffer, sizeof(buffer), stdin);"
man fgets per chiarimenti.
Ciao ciao
Andrea
-- 
"...Unix, MS-DOS, and Windows NT (also known as the Good, the Bad, and 
 the Ugly)."
(by Matt Welsh)

Alex Martelli (
al...@magenta.com
) wrote:
[ snip ]
: E fai _benissimo_ a farteli venire, questi dubbi!!!  E` una delle
: aree piu` delicate e fragili del C, e non sai quanti programmatori
: non si pongono mai dubbi in merito, e di conseguenza finiscono
: per scrivere programmi che "si rompono" appena uno gli
: passa un input appena un poco "strano".
Hai ragione, ma se il programmatore e' scadente, fara' danni in qualsiasi
linguaggio (anche se il C tende ad ingigantire questa sua 'caratteristica').
[ snip si parla di gets e scanf ]
: Quindi, *non le usi*.  Sembra incredibile, ma la libreria standard
: del C contiene queste due funzioni, usare le quali e` quasi
: una *garanzia* di avere scritto un programma fragilissimo, del
: tutto inaffidabile, un "giocattolo" professionalmente ben lontano
: dall'essere accettabile.  Praticamente l'unica eccezione e`
: quando "sai" da dove viene lo standard input del tuo programma,
: cioe` NON dall'utente ma da un altro programma pure sotto il
: tuo controllo, o simili -- ma anche in quel caso, MOLTO meglio
: cautelarsi.
: fgets ti permette, almeno, di dire quanto spazio hai riservato
: per il tuo buffer, e quindi di evitare i crash peggiori; la linea
: immessa dall'utente viene pero` "troncata" se e` piu` lunga
: dell'array stesso, nel senso che i caratteri "in piu`" restano
: nella coda di input, e, se non prendi precauzioni, li vedrai
: poi come un'*altra* linea di input successiva, il che puo` dare
: i suoi problemi.
Fin qua tutto perfetto. 
(come al solito, verrebbe da scrivere 8)
: Una soluzione completa ti porta inevitabilmente a leggere
: i caratteri uno alla volta, sino all'EOF o sino a che non hai
: incontrato il fine-riga; devi inoltre garantirti che l'array dove
: li metti sia allocato con malloc, per poterlo ingrandire ad
: esempio con realloc (e non scordarti di controllare gli
: errori che possono verificarsi, sia nell'input, sia nella
: allocazione).  Una piccola ottimizzazione: prova prima a
: leggere con fgets nell'array di dimensione fissa; poi,
: pero`, prima di procedere, verifica (ad esempio con
: strchr) che ci sia un '\n' -- se c'e`, hai gia` tutta la riga,
: e puoi procedere -- se non c'e`, devi passare al caso
: generale.  Indicativamente:
Qui sono perplesso: secondo me e' meglio leggersi *tutta* la linea in
un colpo solo, e poi fare qualsiasi analisi occorra con comodo nel
buffer.
[ snip programma di esempio ]
Cioe' qualcosa del tipo:
...................
#include <stdio.h>
#include <string.h>
#include <limits.h>
...................
/* LINE_MAX e' definita come la massima lunghezza in byte 
 * di una linea di input */
char buffer[LINE_MAX+1];
char *line;
int len;
..................
        if(fgets(buffer, sizeof(buffer), stdin)) {
                len = strlen(buffer);
                if(buffer[len-1] == '\n') {
                        buffer[--len] = '\0';
                }
                if((line = malloc(len+1)) == NULL) {
			/* errore */
		}
                strcpy(line, buffer);
	} else {
		/* EOF oppure errore */
	}
	/* elabora line */
..................
	/* free(line); quando e' il caso */
.................
: Ah, a proposito, e per il beneficio di quelli che dicono
: che non val la pena di usare il C++ per "programmi
: corti":
[ snip ]
Ti chiameremo 'il missionario del C++', costantemente all'opera per 
cercare nuovi proseliti in it.comp.lang.c 8-P


        if(fgets(buffer, sizeof(buffer), stdin)) {
Se l'utente immette una linea piu` lunga di sizeof(buffer)-1,
questo, come spiegavo, porta a trovarsi l'"eccesso", alla
prossima passata del ciclo, come falsa "linea successiva"
(e separata).
Il risultato, a seconda di quel che uno deve fare con le
linee che sta leggendo, puo` essere arbitrariamente
disastroso... anche peggio di un crash del programma.
Supponi, ad esempio, che il nostro programma tratti
come commenti tutte le linee di input che iniziano con
asterisco, mentre quelle che iniziano con un punto
esclamativo vengono spedite in esecuzione tramite
"system", eccetera eccetera per altri tipi ancora di
linee d'ingresso.  Ebbene, in questo caso, se l'utente
immette qualcosa come...:
* un innocuo commento ...omissis...! cat /dev/zero>/dev/hd1
e ha la scalogna che gli omissis hanno proprio la lunghezza
giusta, in modo che il punto esclamativo appaia come inizio
della inesistente "riga successiva", si puo` cosi` annientare
un intero disco fisso... all'anima del "commento"!
In generale, non c'e` modo di _impedire_ all'utente di
immettere linee di lunghezza arbitrariamente elevata; quindi,
un programma robusto sara' pronto ad accettare (al limite,
proprio come minimo, con una diagnostica di errore, e
garantendo comunque di non combinare disastri) linee
di qualsiasi lunghezza -- non solo sino a LINE_MAX...
qualsiasi cosa questo LINE_MAX possa essere.
Questa mancanza assoluta di limiti arbitrari, ad esempio,
e` stata da sempre una caratteristica del perl -- e non la
minima delle ragioni per cui esso e` diventato il numero
uno fra gli strumenti piu` preziosi dell'amministratore di
sistema.
Alex

...
:     [snip]
: >Qui sono perplesso: secondo me e' meglio leggersi *tutta* la linea in
: >un colpo solo, e poi fare qualsiasi analisi occorra con comodo nel
: >buffer.
: SAREBBE meglio, se si potesse, ma il punto e` proprio 
: che, in C, non c'e` un modo sicuro per farlo.
O.K. hai ragione.
: >/* LINE_MAX e' definita come la massima lunghezza in byte 
: > * di una linea di input */
: Da chi e` definita? Non e` nello standard ISO/ANSI, non 
: e` nel compilatore che uso di solito (VC++), non e` in 
: gcc/egcs (versione mingw32)... un compilatore (o libreria
: standard) che ponesse un simile limite violerebbe tutti
: gli standard a me noti.
Sotto Linux, e' definita in "/usr/include/posix2_lim.h", ed e' 
equivalente a _POSIX2_LINE_MAX, che a sua volta e' definita dallo 
standard POSIX.2 come la massima lunghezza di una linea di testo 
gestibile.


gestibile.
Trovato, grazie; non ho un Linux sotto mano, ma altri sistemi
Posix si`, e nella forma _POSIX2_LINE_MAX la costante si
trova.  Non ho sotto mano lo standard Posix.2, ma penso che
quel che si intende e` che un'utility puo` dare un messaggio
d'errore per linee piu` lunghe di cosi` (ma dovra` pur sempre
garantire di non fare disastri...), quindi un approccio sarebbe:
-- dimensionare il buffer a _POSIX2_LINE_MAX caratteri,
-- leggere con fgets,
-- cercare il \n nel buffer con strchr,
-- se non si trova, la linea di input era piu` lunga, quindi dare
    un messaggio d'errore e terminare l'esecuzione del
    programma (unico modo di evitare sicuramente disastri
    senza dover *gestire* linee di lunghezza arbitraria),
-- se si trova, sostituirlo con un byte di 0 di terminazione e
    proseguire con l'elaborazione della linea.
E` un approccio decisamente piu` semplice di quello che in
C sarebbe necessario per "digerire" qualsiasi input; per
quanto possa ancora "lasciare l'amaro in bocca", e` certo
meglio che non rischiare i crash (con gets) o le "linee di
input fantasma" (non terminando a fronte di linee lunghe).
Certo che se uno fa (come e` spesso buona prassi) il
prototipo dell'utility in perl, che gestisce linee arbitrarie,
dispiacerebbe poi molto imporre un tale limite passando
alla versione "di produzione" dell'utility, riscritta in C.  Chi
si trova spesso davanti a tali problemi preferira` di certo
sviluppare una sua libreria personale per la gestione di
stringhe, e in essa l'immissione di una linea di lunghezza
arbitraria non potra` certo mancare.  
E, gia` che siamo in discorso...: altre funzioni che
non _dovrebbero_ mancare in una simile librerietta di
uso personale sono versioni delle varie funzioni strXYZ,
che rimedino al disastroso "design bug" delle versioni
standard: infatti, le varie strcat eccetera ritornano, in
modo completamente inutile, l'indirizzo iniziale del buffer
che ricevono, invece dell'indirizzo _finale_ (quello dello
zero di terminazione), che servirebbe eccome, e che
esse di necessita` hanno internamente calcolato; inoltre,
naturalmente, e' importante che ogni funzione che
gestisce stringhe abbia come input la dimensione dei
buffer su cui scrive, per poter garantire di non far
mai "debordare" i dati dai buffer di destinazione.
Per coloro ai quali non e` chiaro il "design bug" delle
strXYZ standard C: costruire una singola stringa di
output concatenando tante stringhe di input, usando la
standard strcat, e` un'operazione che finisce per
essere O(N quadro) sulla lunghezza complessiva,
invece di O(N) come sarebbe facile ottenere con
un'altra impostazione progettuale (se ogni strcat,
ad esempio, tornasse l'indirizzo dello 0 di terminazione
che ha scritto nel buffer)...
Alex

Alex Martelli (
mart...@cadlab.it
) wrote:
[ snip ]
: Non ho sotto mano lo standard Posix.2, ma penso che
: quel che si intende e` che un'utility puo` dare un messaggio
: d'errore per linee piu` lunghe di cosi` (ma dovra` pur sempre
: garantire di non fare disastri...), quindi un approccio sarebbe:
: -- dimensionare il buffer a _POSIX2_LINE_MAX caratteri,
: -- leggere con fgets,
: -- cercare il \n nel buffer con strchr,
: -- se non si trova, la linea di input era piu` lunga, quindi dare
:     un messaggio d'errore e terminare l'esecuzione del
:     programma (unico modo di evitare sicuramente disastri
:     senza dover *gestire* linee di lunghezza arbitraria),
: -- se si trova, sostituirlo con un byte di 0 di terminazione e
:     proseguire con l'elaborazione della linea.
Non potrebbe bastare riempire il buffer e scartare tutto il resto ?
Qualcosa del tipo: 
[ riprendendo il precedente codice ]
..................................................
             if(fgets(buffer, sizeof buffer, stdin)) {
                     len = strlen(buffer);
                     if(buffer[len-1] == '\n') {
                              buffer[--len] = '\0';
                     } else {
                              int c;
		              /* svuoto la coda di input */		
                              while(((c = getchar()) != EOF)&&(c != '\n'))
                                      ;
                     }
..................................................
[ snip ]
[ si parla di strcat ]
A proposito, qualcuno sa se nello Standard C prossimo venturo, ci
saranno novita' su questo argomento ? 


saranno novita' su questo argomento ? 
Non che io sappia, per quel poco che so dal seguire
comp.std.c.
Alex

 le funzioni gets e scanf non controllano l'overflow... quindi?
Mi vengono in mente tre soluzioni. La prima e' la piu' semplice:
    char s[100];
    ...
    while( scanf("%99s", s) == 1 )
        printf("-->%s<--\n", s);
Qui i caratteri successivi al 99-esimo finiscono nella stringa
seguente. Il problema e' che il limite "hard-coded" nella stringa di
formato di scanf() non e' una soluzione molto elegante, ma quello che
piu' mi da' fastidio e' che scanf() ha un modo tutto suo di stabilire
quando finisce una stringa. Ad es., uno spazio e' considerata la fine
della stringa, e questo non e' bene per un programma interattivo. Un
piccolo perfezionamento permette di avere il comportamento voluto:
    while( scanf("%99[^\n]\n", s) == 1 )
        printf("-->%s<--\n", s);
Il secondo metodo risolve anche il problema del limite statico:
    char s[100];
    int l;
    ...
    while( fgets(s, 100, stdin) != NULL )
        /* elimina il '\n' finale, se presente: */
        l = strlen(s);
        if( l>0 && s[l-1] == '\n' )  s[l-1] = 0;
        printf("-->%s<--\n", s);
    }
Il terzo metodo permette stringhe di lunghezza arbitraria e considera
terminata una stringa solo quando si raggiunge '\n' oppure l'EOF:
    char * ReadStr()
    {
        int size=0, len=0, c;
        char * s=NULL;
        do{
            c = getchar();
            if( len >= size ){
                s = realloc(s, size+=10);
                if( s == NULL ){
                    fprintf(stderr, "out of memory\n");
                    exit(1);
                }
            }
            if( c == EOF ){
                if( len > 0 ){
                    s[len] = 0;
                    return s;
                } else
                    return NULL;
            } else if( c == '\n' ){
                s[len] = 0;
                return s;
            } else
                s[len++] = c;
        } while(1);
    }
La funzione ritorna il puntatore alla prossima stringa letta, oppure
ritorna NULL in caso di EOF o di i/o error. Si usa cosi':
    main()
    {
        char * s;
        while( (s = ReadStr()) != NULL ){
            printf("-->%s<--\n", s);
            free(s);
        }
        if( ferror(stdin) != 0 )
            fprintf(stderr, "input file error\n");
        printf("fine.\n");
    }
Ciao,
- Salsi