Newsgroup: it.comp.lang.c
Titolo: Testo da console: perchè funziona ?

Nel tentativo di trovare una funzione per leggere il testo da console,
ho provato questo codice, scritto da me:
----------------------------------------------
1. #include <stdio.h>
2. 
3. main() {
4.      char *c;
5.
6.      c=(char *) malloc (sizeof(char));
7.
8.      scanf("%s",c);
9.      printf("\nHai digitato: %s\n",c);
10. }
-----------------------------------------------
Ora la domanda č: perchč funziona ?
Cioč questo codice mi consente di inserire una stringa di testo di lunghezza
qualsiasi.
Nella riga 6 ho allocato in memoria lo spazio necessario (1 byte) per la
variabile c, puntatore di tipo char.
Se nello scanf inserisco una stringa piů lunga di 1 carattere, a runtime non
mi ritorna alcun errore, nonostante lo spazio allocato sia solo di un byte.
Perchč ?
Come vedete, nella riga 9, la variabile c non č preceduta da *. Solo cosě,
infatti, riesco a stampare la stringa senza ricevere "Segmentation Fault".
Se metto l'* davanti alla variabile c (come avevo inizialmente fatto), mi va
in "segmentation fault".
Se invece la riga 9 la sostituisco con 
printf("\nHai digitato: %c\n",*c); 
tutto funziona correttamente e secondo logica; allora il formato "%s"
richiede una variabile di tipo puntatore per funzionare correttamente ?
Infine ho fatto questa prova: mettendo dopo lo scanf questa riga
                for (k=0;*(c+k)!='\0';k++) printf("%d\n",(c+k));
che dovrebbe darmi l'indirizzo di memoria delle celle della variabile c, mi
ritornano tutti valori sequenziali. Com'e' possibile ?
Insomma, nella riga 6 ho allocato un solo spazio di memoria per la variabile
c, allora č lo scanf a trovare un nuovo spazio di memoria per la variabile
c ?
-- 
Ciao,
        Fabrizio
<=- 
http://www.negusweb.it
 -=>

 -----------------------------------------------
Rileggendo piu' volte il manuale ho visto che (char *) equivale ad un array
di caratteri.
Quindi, togliendo la riga 6 che č completamente inutile, una funzione che
rispecchia il codice scritto sopra, c'e' qualche contro nell'utilizzare una
funzione di questo tipo per leggere una stringa da console ?


 c ?
No, come gia` detto e` un caso `fortuito'.
In conclusione: per leggere una stringa in maniera sicura usando le 
funzioni standard ci sono due alternative:
     char buf[256]; /* oppure: char *buf = malloc(256); */
     scanf("%255s", buf); /* nota bene: 255 = 256 - 1 in quanto devi
                             lasciare spazio per lo '\0' */
oppure:
     fgets(buf, 256, stdin); /* nota bene: 256 perche` fgets si preoccupa
                                da sola di lasciare lo spazio per lo
                                '\0' */
La differenza tra le due soluzioni e` che la prima legge la stringa 
(fino ad un max di 255 caratteri) solo fino al primo spazio bianco che 
incontra, mentre la seconda legge tutta la riga (sempre fino a 255 
caratteri).
[1] poiche` malloc() restituisce un puntatore void, che puo` essere 
assegnato a qualsiasi alro puntatore.
-- 
Enrico `Trippo' Porreca

 funzione di questo tipo per leggere una stringa da console ?
Si`, perche` scanf() non controlla quanti caratteri vengono inseriti. Un 
metodo sicuro lo trovi nell'altra risposta che ti ho dato.
-- 
Enrico `Trippo' Porreca

> 10. }
-- 
Enrico `Trippo' Porreca

 metodo sicuro lo trovi nell'altra risposta che ti ho dato.
E' completamente sbagliato...
malloc alloca un char e fa puntare c a tale char.
ora c contiene cio' che la malloc ha restituito, l'indirizzo di un char
allocato, e *c contiene il valore contenuto di tale locazione di memoria;
per ora questo valore contiene qualsiasi cosa.... e' inizializzato!
Poi la scanf legge una stringa e la mette dall'indirizzo puntato da c
compreso in poi....
Quindi, se io digito "Tommy", ora *c conterra 'T', ok, lo abbiamo
inizializzato;-) ma..... le 5 locazioni di memoria seguenti conterranno: 'o'
'm' 'm' 'y' '\0'....
Il problema e' che quelle ultime 5 locazioni potrebbero essere utilizzate da
altre variabili o ... su OS vecchi persino da altri processi!
In pratica il risultato di tale programma e' *imprevedibile*.
Qui ovviamente dovevi allocare una zona di memoria abbastanza grande da
contenere *tutta la stringa*..... tuttavia, come dice Enrico, il problema
*secondario* e' che con scanf non puoi dare il numero massimo di
caratteri... quindi se io alloco 32 e poi digito 40 faccio ugualmente un
pasticcio....
Il programma puo' sembrar funzionare, ma poi puo' succedere di tutto....
Per aiutarti a comprendere ho scritto questo al volo (modificando il tuo
codice) e l'ho compilato:
int main()
{
 char *c; char *d;
 c=(char *) malloc (sizeof(char));
 d=malloc (32);
 strncpy(d,"Tommy",6);
 printf("%s\n",d);
 scanf("%s",c);
 printf("\nHai digitato: %s\n",c);
 printf("%s\n",d);
 return 0;
}
da me produce un output tipo:
<OUTPUT>
Tommy
Ora_digitiamo_del_testo_a_caso
Hai digitato: Ora_digitiamo_del_testo_a_caso
l_testo_a_caso
</OUTPUT>
Come vedi l'ultima riga dovrebbe essere "Tommy", come la prima, ma in
realta' la la scanf scrive in c oltre la parte allocata (1o byte) e .....
arriva a sovrascrivere la stringa d che era stata *propriamente allocata* ed
inizializzata.
Ora in questo caso il bug e' facile da scoprire, ma immagina di avere un
ciclo e... vai a sovrascrivere la var di controllo e non esci mai dal
ciclo... chi ci va a pensare che il problema e' che scrivi oltre la parte
allocata?!
(in fine una nota: non tutti i compilatori potrebbero produrre l'output
sopra riportato)
La riga che dici essere inutilte, la 6, non e' inutile, e' solo praticamente
inutile allocare UN elemento e chiedere di digitare una intera stringa!!!!
Se rimuovi il malloc e' ancora peggio, vai a scrivere in una area *a
caso*...
Prima scrivevi su di una area di cui almeno il primo byte era stato
allocato... Ora nemmeno questo.... Il primo byte (e tutti gli altri)
potrebbero essere qualsiasi cosa ed in qualsiasi posto della memoria!
Corregerti sul cast non necessario e su sizeof(char) che e' sempre 1 etc...
mi sembra fuori luogo dato la gravita' degli altri problemi.
Spero aver risolto i tuoi dubbi.....
Tommy

 una funzione di questo tipo per leggere una stringa da console ?
La riga 6 cosi' com'e' non ha senso, visto che leggi una stringa devi
lasciare almeno lo spazio per il terminatore di stringa ('\0').
O leggi un singolo carattere con la getchar() e in tal caso di basta una
variabile di tipo "char" continui a leggere e scopri come fare
correttamente :)
A parte questo, la scanf non fa' il check contro i buffer overflow, quindi
non la devi usare.
Per leggere da stdin devi in ordine:
- allocare staticamente o dinamicamente (malloc, calloc) un buffer di 
  dimensione arbitraria, dipende da cosa devi leggere
- usare la fgets su stdin, il buffer appena creato e la dimensione del 
  buffer
<code>
char buffer[1024];
if (!fgets(buffer, 1024, stdin))
    handle_error();
</code>
-- 
Mox
"Trust the programmer."
"Don't prevent the programmer from doing what needs to be done."
"Keep the language small and simple."
"Provide only one way to do an operation."

 metodo sicuro lo trovi nell'altra risposta che ti ho dato.
E se mettessi:
      scanf("%100s",c);
per esempio?
Ma a questo punto potrei dichiarare c anche come array, visto che ne sto
fissando la dimensione massima.


 caratteri).
Insomma, vada per la seconda! :-)


 Spero aver risolto i tuoi dubbi.....
Si, grazie, ci ho messo una serata ma alla fine ho capito.


 anche senza dichiarare stdlib. Why ?
Perchè ci pensa il compilatore a includerla
Ciao Max
-- 
+-----------------------------------------------------------+
| Secondo alcuni autorevoli testi di tecnica Aeronautica,   |
| il calabrone non può volare, a causa della forma e del    |
| peso del proprio corpo in rapporto alla superficie alare. |
| Ma il calabrone non lo sa e perciò continua a volare.     |
|					     Igor Sikorsky  |
+-----------------------------------------------------------+

 fissando la dimensione massima.
Si', in questo modo funzionerebbe perche' il 100 indica la ampiezza 
massima della stringa letta da scanf.
Giorgio

 int ?
Il cast a void non mi sembra necessario. Invece non devi assumere che un 
puntatore debba essere un int o un long int ( e nemmeno un long long!).
Tanto per cominciare sarebbe meglio parlare di unsigned (int etc.) ma 
neanche questo e' completamente corretto perche' non e' quello che lo 
standard prevede (per dare ampia liberta' alle implementazioni). Di 
fatto un puntatore potrebbe non essere neanche un numero intero. Per 
questo motivo esiste un fonvertitore di formato apposito (%p).
Giorgio

ritornano tutti valori sequenziali. Com'e' possibile ?
Questo mi sembra il minimo. La stringa e' un'array di caratteri,
quindi occupa una zona di memoria continua, senza frammentazioni.
Sarebbe strano il contrario.
Ciao
 Max

 int ?
Nein, un puntatore e` un puntatore. Inoltre, il formato "%p" richiede 
necessariamente un void pointer. Perche`? Perche` il void pointer puo` 
contenere l'indirizzo di qualsiasi cosa. In questo modo basta un solo 
formato ("%p" appunto) per stampare qualsiasi tipo di puntatore (dato 
che non e` garantito che tutti i puntatori abbiano ad esempio la stessa 
dimensione).
-- 
Enrico `Trippo' Porreca

 anche senza dichiarare stdlib. Why ?
Perche` per l'ennesimo caso fortuito succede che un void * abbia la 
stessa dimensione di un int.
-- 
Enrico `Trippo' Porreca

 caratteri...
Ho provato piu' di una volta, ed ha funzionato.
E' per questo che non mi ritornavano i conti.


 Perchč ci pensa il compilatore a includerla
Anche senza che io gli specifichi in quale libreria cercare ?
Mi sembra strano.


 puntatore debba essere un int o un long int ( e nemmeno un long long!).
No no, scusa, mi sono espresso male. Con int o long int intendevo il valore
della locazione di memoria a cui punta il puntatore.


 Il cast a void non mi sembra necessario.
Dall'ultima bozza pubblica dello standard C99:
     7.19.6.1 The fprintf function
     Synopsis
   1 #include <stdio.h>
     int fprintf(FILE * restrict stream,
                 const char * restrict format, ...);
     Description
     [...]
   8 The conversion specifiers and their meanings are:
     [...]
   p The argument shall be a pointer to void. The value of the pointer
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^
     is converted to a sequence of printing characters, in an
     implementation-defined manner.
Naturalmente lo stesso vale per printf() in quanto:
     7.19.6.3 The printf function
     [...]
     Description
   2 The printf function is equivalent to fprintf with the argument
     stdout interposed before the arguments to printf.
E` da notare che qualsiasi puntatore puo` essere convertito in un void * 
senza perdita di informazione:
     6.3.2.3 Pointers
   1 A pointer to void may be converted to or from a pointer to any
     incomplete or object type. A pointer to any incomplete or object
     type may be converted to a pointer to void and back again; the
     result shall compare equal to the original pointer.
Infine e` da segnalare che il cast e` necessario, poiche` la conversione 
non e` effettuata automaticamente (dato che si tratta di uno dei 
parametri in numero variabile - quelli indicati nel prototipo con ... - 
nessuna conversione sui puntatori si applica).
-- 
Enrico `Trippo' Porreca

 Mi sembra strano.
Che SO stai utilizzando? Non credo linux ... 
Se stai usando linux,stai insultando l'intelligenza del gcc!!! :)
Eppure è così, almeno sotto linux!
Esempio:
int main(void)
{
	printf("Hello World\n");
}
Anche se non includi <stdio.h>
il gcc te lo compila lo stesso e se mandi in esecuzione
l'eseguibile vedrai che funziona correttamente.


 l'eseguibile vedrai che funziona correttamente.
Nonostante cio`, chiamare una funzione senza includere l'header relativo 
risulta essere un comportamento indefinito ai sensi dello standard.
Prova ad usare una funzione di <math.h> senza includere l'header e 
vedrai risultati stupefacenti (si`, anche col gcc):
#include <stdio.h>
int main(void)
{
     double x = log2(4.0);
     printf("il logaritmo in base 2 di 4.0 e` %f\n", x);
     return 0;
}
C:\WINDOWS\Desktop>gcc -o prova prova.c
C:\WINDOWS\Desktop>prova.exe
il logaritmo in base 2 di 4.0 e` 0.000000
*Molto* corretto no?
-- 
Enrico `Trippo' Porreca

 risulta essere un comportamento indefinito ai sensi dello standard.
Non sia mai neanche pensato che la mia risposta incitasse
a non rispettare lo standard, era solo per constatare i fatti.
:)


 l'eseguibile vedrai che funziona correttamente.
Beh, non mi sembra un comportamento molto corretto, visto che dovrebbe
richiedere l'inclusione della libreria.


 stessa dimensione di un int.
Ma malloc non fa parte della libreria stdlib ?
O è anche presente in stdio ?


 :)
Pero' il compilatore dovrebbe almeno avvisare di una cosa del genere.
Gli costa cosi' tanto ? :-)))


 Gli costa cosi' tanto ? :-)))
Gli hai detto di farlo?
     gcc -Wall -pedantic -ansi -o nomeeseguibile nomefile.c
-- 
Enrico `Trippo' Porreca

 O è anche presente in stdio ?
No.
-- 
Enrico `Trippo' Porreca

 richiedere l'inclusione della libreria.
Ti rispondo come ti ha risposto giustamente Enrico nel post poco sopra:
Gli hai detto di farlo? 
:)


     gcc -Wall -pedantic -ansi -o nomeeseguibile nomefile.c
Pero' attenzione perche' cosi' lo standard e' quello dell' 89. Per il 99 
ci vuole -std=c99 al posto di -ansi (per modi alternativi cfr man gcc).
Giorgio

 
Pensavo fosse nelle opzioni di defualt il controllo delle inclusioni.
Ok, ora è chiaro. Grazie.


per Max:
http://www.cicap.org/articoli/at101780.htm
leggilo, riguarda la tua signature
-- Lorenzo