PDA

View Full Version : [C] Problema nel ritorno di una stringa


Y3PP4
03-08-2009, 16:01
Giorno a tutti, stò scrivendo un piccolo parser e tra i vari passi da seguire c'è quello di rimuovere i commenti e gli spazi bianchi (in modo da poter effettuare token senza problemi dei blank spaces).

Semplificando la funzione incriminata ho ricostruito il programma con la singola funzione e un main che la richiama.
L'algoritmo è molto semplice: tramite una switch inserita in una for che cicla i caratteri della riga elimina tabulazioni, blank spaces, e commenti.

Per verificare il funzionamento dell'algoritmo in sè ho modificato il programma facendogli direttamente mostrare i caratteri della stringa che non siano i suddetti da eliminare e tutto viene mostrato correttamente.
escaping2.c (codice funzionante ma non è ciò che mi serve - esegue solo l'algoritmo)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static void EscapeString( char *source )
{
unsigned int i;
unsigned int isCommentLine = 0; /*on 1 the line will be discarded - is a comment line*/
//char *bufString;
for( i = 0; i < strlen(source); i++ )
{
switch( source[i] )
{
case '\t':
case ' ' :
break;
case '#' :
isCommentLine = 1;
break;
default :
printf("%c", source[i]);
break;
}
if(isCommentLine) /* != 0"*/
{
return;
}
}
return;
} /*EscapeString*/

int main()
{
char *string = "this is a string #and this is a comment that will not show.";
EscapeString(string);
return 0;
}

Ma se invece voglio raccogliere in una stringa i caratteri non riesco proprio.
Ottengo sempre un segmentation fault e gdb si blocca sulla chiamata alla funzione, quindi è quella che non lavora a dovere.
Ora mostro lo stesso algoritmo che dovrebbe ritornare la stringa:
blank.c (segmentation fault)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define CRLF
static char *EscapeString( char *source )
{
unsigned int i;
unsigned int isCommentLine = 0; /*on 1 the line will be discarded - is a comment line*/
char *bufString;
for( i = 0; i < strlen(source); i++ )
{
switch( source[i] )
{
case '\t':
case ' ' :
break;
case '#' :
isCommentLine = 1;
break;
default :
bufString += source[i];
break;
}
if(isCommentLine) /* != 0"*/
{
return bufString;
}
}
return bufString;
} /*EscapeString*/

int main()
{
char *String = "this is a string #and this is a comment that will not show.";
char *escapedString = EscapeString(String);
printf("%s" CRLF, escapedString);
return 0;
}

Premetto che una volta risolto questo problema la funzione userà questo prototipo:
static char *EscapeString( char *dest, char *source );
in modo da accettare come input anche la stringa in cui conservare la stringa elaborata ed evitare di scrivere questo poco elegante: char *escapedString = EscapeString(string); ma il problema è lo stesso.

Ringrazio chiunque saprà dirmi cosa sbaglio... è da sabato che ci sto lavorando sopra e ho spulciato tutto google ma non ne sono venuto a capo.
Ps. Ho provato con strcat e strcpy lavorando sui puntatori, ma stesso risultato.

DanieleC88
03-08-2009, 16:14
Quel
bufString += source[i];
purtroppo non esiste in C. Devi usare l'allocazione dinamica della stringa (ergo: malloc() e realloc()) e concatenare con strcat(), o aggiungere un carattere alla volta.

Y3PP4
03-08-2009, 16:24
Quel
bufString += source[i];
purtroppo non esiste in C. Devi usare l'allocazione dinamica della stringa (ergo: malloc() e realloc()) e concatenare con strcat(), o aggiungere un carattere alla volta.

Lol imperdonabile errore.
Riguardo all'utilizzo di malloc() avevo già provato a effettuare un bel
char *bufString = malloc(sizeof(source));
calcolando una lunghezza massima pari alla stringa di input (dato che si và a sottrarre non ad addizionare altri caratteri)
e poi usare strcat(bufString, source[i]) nel blocco default.
Ma ovviamente strcat si aspetta una puntatore costante di tipo char (const char *src).
Eseguo quindi il cast come segue:
strcat(bufString, (char *)source[i]);
Ma il compilatore mi segnala un errore di tipo: warning: cast to pointer from integer of different size.
E indovina un po' ?
Segmentation fault. Sò qual'è il problema ma non son riuscito a risolverlo e tra i tanti test avevo testato quel +=.

Mille grazie per l'attenzione!
Ciao e buona giornata.

DanieleC88
03-08-2009, 16:54
Aspetta aspetta, qui stai facendo un po' di confusione e ti capisco, i puntatori fanno girare la testa a molti.

Allora, per calcolare la lunghezza in caratteri di una stringa non usare sizeof(p), dove p è il puntatore alla stringa. La grandezza esatta la trovi con:
size_t uSize = (strlen(source) + 1) * sizeof(char);
Ovvero, un'area di memoria grande tanto quanto la grandezza di un singolo char (dato dal sizeof()), moltiplicata per la lunghezza della stringa originaria (data da strlen()) più uno (per il terminatore di stringa). Se usi semplicemente sizeof(source) otterrai sempre l'equivalente di sizeof(char *), perché quello è il tipo di source. Una cosa simile funzionerebbe soltanto su un'array di char.

Poi, strcat() si occupa di concatenare due stringhe, ma tu la chiami su una stringa e su un carattere (rispettivamente, char* e char), il che non è la stessa cosa. Per copiare solo un carattere nella stringa, ti consiglierei lavorare su di un indice e salvare il carattere che vuoi man mano che ti serve, abbozzo un po':
char *sNewString = /* allocazione */;
unsigned uIndex = 0;

while (/* scansiona la stringa originale */)
{
if (/* il carattere c è quello giusto */)
{
sNewString[uIndex++] = c;
}
}
Insomma, puoi accodare così ogni nuovo carattere nella stringa.

ciao ;)

Y3PP4
03-08-2009, 18:35
Aspetta aspetta, qui stai facendo un po' di confusione e ti capisco, i puntatori fanno girare la testa a molti.

Allora, per calcolare la lunghezza in caratteri di una stringa non usare sizeof(p), dove p è il puntatore alla stringa. La grandezza esatta la trovi con:
size_t uSize = (strlen(source) + 1) * sizeof(char);
Ovvero, un'area di memoria grande tanto quanto la grandezza di un singolo char (dato dal sizeof()), moltiplicata per la lunghezza della stringa originaria (data da strlen()) più uno (per il terminatore di stringa).

Hai perfettamente ragione, ho commesso un'errore, ma adesso che me lo hai chiarito spero di non commetterlo in seguito.

Se usi semplicemente sizeof(source) otterrai sempre l'equivalente di sizeof(char *), perché quello è il tipo di source. Una cosa simile funzionerebbe soltanto su un'array di char.
E qui si ho fatto un'errore coi puntatori, nel senso che io pensavo che essendo un char *name; un puntatore a una locazione di memoria che contiene una sequenza di char (come del resto gli array), una cosa simile avrebbe funzionato anche sul puntatore (che, forse erroneamente, pensavo si riferisse a un'array di char).

Poi, strcat() si occupa di concatenare due stringhe, ma tu la chiami su una stringa e su un carattere (rispettivamente, char* e char), il che non è la stessa cosa. Per copiare solo un carattere nella stringa, ti consiglierei lavorare su di un indice e salvare il carattere che vuoi man mano che ti serve, abbozzo un po':
char *sNewString = /* allocazione */;
unsigned uIndex = 0;

while (/* scansiona la stringa originale */)
{
if (/* il carattere c è quello giusto */)
{
sNewString[uIndex++] = c;
}
}
Insomma, puoi accodare così ogni nuovo carattere nella stringa.

ciao ;)
Qui non mi è molto chiaro. Strcat() effettua una concatenazione. Prende due stringe in input ok, ma se una stringa fosse di un solo char dovrebbe funzionare lo stesso no? Quindi effettuando un cast all'ultimo secondo su un tipo char mi aspettavo funzionasse, ma evidentemente benchè l'abbia castata a char * non la riconosce come stringa ma come singolo carattere. Ed ora capisco anche a cosa si riferiva quell'errore che diceva casting to pointer from integer of different size.

Beh adesso provo il metodo che mi hai consigliato per lavorare su un singolo char alla volta.

Mille grazie e buona serata!

DanieleC88
03-08-2009, 18:53
Il tuo dubbio ulla strcat() viene dal fatto che in C non esiste un "tipo stringa", ma una stringa è semplicemente definita da un'area di memoria contenente dei caratteri e terminata da un carattere speciale (uno '\0'). Quindi un carattere singolo NON è una stringa: una stringa C contenente un solo carattere è in realtà una sequenza di due caratteri (il carattere più il terminatore).

Le funzioni standard del C che lavorano sulle stringhe presuppongono che ciò che ricevono in input sia un puntatore ad una stringa terminata da '\0'. Se non viene mai incontrato il terminatore, puoi sforare e leggere memoria inesistente o a cui non dovresti avere accesso. Il semplice cast da carattere a puntatore di caratteri significa che otterrai un puntatore con indirizzo pari al valore del carattere (quindi tra 0 e 255 inclusi), e non la memoria che ti aspetteresti.

Y3PP4
03-08-2009, 19:24
Bene, più o meno mi è chiaro. Nel senso che ho capito quello che hai detto, ma dato che l'unica differenza (come hai specificato, entrambe terminano con \0) è la sequenza di caratteri, non capisco perchè le funzioni che accettano le stringhe non l'accettano. Infondo sarebbe come dichiarare:
char *ciao = "a";
char ciao = "a";
char ciao[4] = "a";
tutte e tre sono viste come a\0 no?
Capisco il fatto del puntatore e che punta a una memoria di un singolo char, ma proprio a livello di memoria non capisco cosa cambia. char *ciao = "a" viene riconosciuta dalle funzione che accettano la stringa e char ciao = "a" no. Se non si basasse sul riconoscimento dei tipi, potrebbe accettarle senza problemi mi pare. Capisco che è cosi e boh (soprattutto poichè appunto si basa sul riconoscimento dei tipi) ed è un linguaggio molto vecchio (e quindi non una cosa fatta male) ma mi rimane sto chiodo fisso solo per pura curiosità. Il resto l'ho perfettamente capito :).

DanieleC88
03-08-2009, 19:30
Infondo sarebbe come dichiarare:
char *ciao = "a";
char ciao = "a";
char ciao[4] = "a";
tutte e tre sono viste come a\0 no?
Il primo e il terzo sì, il secondo no. Perché "a" diventa effettivamente "a\0", quindi una stringa definita in memoria e composta da due caratteri. Ora, per quel che ti ho spiegato prima, il compilatore assegna a ciò che sta a sinistra dell'assegnazione il puntatore alla stringa, ma nel secondo caso il tipo della variabile è char, ovverosia un valore intero di 1 byte (e non di 4 byte come sono i puntatori nelle architetture x86 a 32 bit), perciò si rifiuta di farlo senza segnalarti (giustamente) un errore. Non volevo scendere così "in basso" per spiegarti l'errore. :D

ciao ;)

Y3PP4
03-08-2009, 19:41
Oh, finalmente mi è chiaro.
Mi spiace averti portato via così tanto tempo, ma a me non piacciono i dogmi e vorrei capire le varie cose, sia per curiosità sia perchè entrando nell'ottica che sta alla base di un ragionamento non solo riesco ad applicarlo meglio, ma riesco a ricordarlo con facilità e non si dimentica nel tempo.
Un po' come la matematica: se un esecizio ti viene spiegato con tanto di perchè delle operazioni lo ricordi meglio e più facilmente di quello in cui ti danno la formula e basta.

:D

Mille grazie e buona serata.

DanieleC88
03-08-2009, 19:45
Figurati, fai strabenissimo a "scavare" per capire bene le cose. Non mi hai rubato tempo, altrimenti non ti avrei risposto... :p