PDA

View Full Version : [C] mandare in crash l'applicazione


sottovento
11-06-2006, 03:37
Ciao a tutti,
ci risiamo: il cliente, nella sua applicazione che mi ha chiesto di analizzare (non capisce perche' non funziona), fra le altre cose ha scritto:


char *p;

p = "una stringa qualsiasi"


poi utilizza la variabile p, con il valore cosi' assegnato, in vari punti del metodo.

Analogamente, ha scritto una funzione di questo genere:


char *funzione ()
{
switch(qualcosa)
{
case 1: return "una stringa";
case 2: return "altra stringa";
...
}
}


Il risultato e' che l'applicazione registra un crash ogni 2/3 settimane attribuibile a questo codice o al codice approssimativamente "vicino" a questo.

Ho parlato di questo, spiegando che questo codice e' errato Il cliente mi ha risposto che lo usa da anni e che, se non produco CONTROESEMPI che mostrino che questo codice e' errato, non cambiara' una virgola.
L'agilita' mentale del mio cliente, dunque, mi impone di cercare un esempio di utilizzo di questo codice che vada sempre in crash. (in teoria dovrebbe essere lui a mostrare la correttezza dell'applicazione e non io a fare il contrario).

E' praticamente una sfida titanica: nel 90% dei casi (direi nel 99.999% nei casi di piccole applicazioni) questo codice non dara' problemi, anche se errato.

Qualcuno ha idee/suggerimenti/feeling?
L'ambiente e' Windows, il compilatore Visual Studio.

Ringrazio anticipatamente chiunque per qualsiasi considerazione di qualsiasi tipo

High Flying
Sottovento

71104
11-06-2006, 05:17
scusa ma perché è errato...? :mbe:

71104
11-06-2006, 05:24
ipotizzando che sia errato a causa del cast implicito (da const char* a char*), prova a mostrargli questo:

char *asd() {
return "lol";
}
.
.
.
asd()[0] = 0;

bada bene, non l'ho manco provato, potrebbe addirittura non crashare... :p
ma se ricordo bene in realtà crasherà

sottovento
11-06-2006, 05:45
ipotizzando che sia errato a causa del cast implicito (da const char* a char*), prova a mostrargli questo:

char *asd() {
return "lol";
}
.
.
.
asd()[0] = 0;

bada bene, non l'ho manco provato, potrebbe addirittura non crashare... :p
ma se ricordo bene in realtà crasherà

Avevo gia' provato una soluzione simile: ovviamente va in crash appunto per il motivo che hai accennato. Su Windows si aprira' una finestra col bollone rosso che avvertira' che la memoria non poteva essere "written".

Il motivo per cui il codice errato e' che il C (quello ufficiale) dice che queste costanti non hanno scope. Subito dopo il loro utilizzo, la memoria da loro occupata potrebbe essere utilizzata per qualsiasi altra cosa.
Il problema e' che praticamente non succede mai.

Mi spiego:

char *p = "una stringa";

Questo e' legale: il compilatore riconosce che non e' un assegnamento bensi' una inizializzazione, quindi allochera' automaticamente la memoria necessaria.

char *p;
p = "una stringa";

In questo caso, p punta ad una locazione temporanea di memoria, la cui validita' non e' specificata da nessuna parte.
Su altri sistemi operativi con cui mi e' toccato lavorare (non proprio comuni), il compilatore assegnava davvero una locazione temporanea che poi rilasciava immediatamente, con il "sano" risultato di un crash ripetitivo dell'applicazione.

L'applicazione attuale, invece, sembra andare in crash raramente, per cui e' difficile stabilire il motivo. Ritengo che uno dei vari motivi sia questo pezzo di codice.
Ho passato il codice come il riso, controllando allocazioni/deallocazioni e validita' degli oggetti e puntatori, questo mi sembra il pezzo piu' debole.
Naturalmente, con dei crash cosi' rari (l'applicazione gira 24H), e' difficile fare analisi dettagliate, anche con un debugger.

Grazie per il suggerimento

High Flying
Sottovento

sottovento
11-06-2006, 05:49
Dimenticavo: grazie anche per il link sul trusted computing


High Flying
Sottovento

crick_pitomba
11-06-2006, 09:14
Ciao a tutti,
ci risiamo: il cliente, nella sua applicazione che mi ha chiesto di analizzare (non capisce perche' non funziona), fra le altre cose ha scritto:

...


char *funzione ()
{
switch(qualcosa)
{
case 1: return "una stringa";
case 2: return "altra stringa";
...
}
}


Il risultato e' che l'applicazione registra un crash ogni 2/3 settimane attribuibile a questo codice o al codice approssimativamente "vicino" a questo.
...



mi son ricordato del classico errore che si fa con i puntatori

int * somma(int a, int b){
int c=a+b;
return &c;
}


che ovviamente porta a risultati indesiderati.

pensavo che il problema che hai posto potesse essere ricollegato ad una cosa simile

quindi ho mandato in esecuzione il seguente frammento di codice per vedere cosa accadeva

char *p;
i=0;
while (i++<100){
p=funzione(i%3);
printf("%s, %i\n", p,p);
}


mi aspettavo che il puntatore cambiasse... invece è rimasto sempre lo stesso durante l'esecuzione.

sono andato a vedere nella documentazione del visual c++, in relazione alle stringhe letterali ed ho trovato

"Microsoft Specific

In some cases, identical string literals can be "pooled" to save space in the executable file. In string-literal pooling, the compiler causes all references to a particular string literal to point to the same location in memory, instead of having each reference point to a separate instance of the string literal. /GF enables string pooling.

END Microsoft Specific"

se non interpreto male, in pratica è come se il compilatore allocasse effettivamente della memoria per le stringhe letterali e dal mio test sembra anche indipendente dallo scope.

per provare ho modificato leggermente il codice


i=0;
while (i++<100){
p=funzione(i%3);
printf("%s, %i\n", p,p);

}


q="altra stringa";
printf("%s, %i\n", q,q);



incredibile ma vero:
p è definito nella funzione, q all'esterno... ma hanno lo stesso indirizzo :O


per come la vedo io, in pratica questo mette al riparo il cliente da errori di memoria non allocata perchè il compilatore crea un guscio di protezione



char *p;
p = "una stringa";

In questo caso, p punta ad una locazione temporanea di memoria, la cui validita' non e' specificata da nessuna parte.
Su altri sistemi operativi con cui mi e' toccato lavorare (non proprio comuni), il compilatore assegnava davvero una locazione temporanea che poi rilasciava immediatamente, con il "sano" risultato di un crash ripetitivo dell'applicazione.


in pratica abbiamo scoperto che con visual studio p punta ad una locazione di memoria ben definita... e la validità di questa locazione risulta anche nelle specifiche delle stringhe letterali di visual studio. come al solito ms fa le cose a modo suo.

è ragionevole pensare che quel valore resti invariato per l'intera esecuzione del programma.

cosa ne pensi?

thebol
11-06-2006, 09:40
incredibile ma vero:
p è definito nella funzione, q all'esterno... ma hanno lo stesso indirizzo :O


per come la vedo io, in pratica questo mette al riparo il cliente da errori di memoria non allocata perchè il compilatore crea un guscio di protezione



in pratica abbiamo scoperto che con visual studio p punta ad una locazione di memoria ben definita... e la validità di questa locazione risulta anche nelle specifiche delle stringhe letterali di visual studio. come al solito ms fa le cose a modo suo.

è ragionevole pensare che quel valore resti invariato per l'intera esecuzione del programma.

cosa ne pensi?

in realtà avevo già sentito di sta cosa tempo fa, e non relativa al visual studio. Era considerata una possibilie ottimizzazione che il compilatore poteva fare.

ps.è quando vedo questi post, che mi accorgo di quanto sia comodo java :asd:

andbin
11-06-2006, 10:32
char *p = "una stringa";

Questo e' legale: il compilatore riconosce che non e' un assegnamento bensi' una inizializzazione, quindi allochera' automaticamente la memoria necessaria.

char *p;
p = "una stringa";

In questo caso, p punta ad una locazione temporanea di memoria, la cui validita' non e' specificata da nessuna parte.Guarda che il risultato è lo stesso ...

Prendiamo per esempio:
char *p1 = "hello";
char *p2;

p2 = "hello";Sono praticamente la stessa cosa (come risultato intendo). Anzi, se il compilatore è "furbo" (e per queste cose credo praticamente tutti, VC++, gcc, ecc...) non solo entrambi i puntatori punteranno ad un stringa "hello" ma pure alla stessa identica stringa. In pratica il compilatore scopre che le due stringhe costanti sono uguali e ne inserisce 1 sola nel "constant pool".

Poi dove le stringhe costanti risiedono in memoria ... dipende dal compilatore e sopratutto dal S.O. Nei sistemi a 32 bit dove ci sono tutti i sistemi di protezione, se le stringhe sono allocate in un'area read-only ... non c'è santo che tenga, non puoi alterarne il contenuto.

Sui vecchi sistemi MS-DOS, dove non esisteva alcun sistema di protezione, se facevi char *p="ciao"; e poi p[0]='C'; non ottenevi alcun errore di protezione e il primo carattere veniva cambiato senza problemi.

71104
11-06-2006, 12:08
Nei sistemi a 32 bit dove ci sono tutti i sistemi di protezione, se le stringhe sono allocate in un'area read-only ... non c'è santo che tenga, non puoi alterarne il contenuto. be' non essere così assoluto, quale grande esperto di Win32 conoscerai sicuramente anche tu la funzione VirtualProtect, e magari anche VirtualProtectEx :D

71104
11-06-2006, 12:09
Il motivo per cui il codice errato e' che il C (quello ufficiale) dice che queste costanti non hanno scope. Subito dopo il loro utilizzo, la memoria da loro occupata potrebbe essere utilizzata per qualsiasi altra cosa.
Il problema e' che praticamente non succede mai. toglici "praticamente"; comunque guarda che se hai prodotto un codice che va in crash come quello che ti ho mostrato hai raggiunto il tuo scopo: hai dato al tizio una prova di crash causata da quel codice no?

PS: ma perché non fate il debug di quel programma quando crasha?

andbin
11-06-2006, 13:40
be' non essere così assolutoVolevo dire che a livello di linguaggio "C" non è possibile fare niente, perché è una questione del sistema operativo. Se poi il sistema operativo ti mette a disposizione delle funzioni per "alterare" le protezioni sulla memoria ... beh, è un'altra cosa. ;)

quale grande esperto di Win32Non ho ben capito in quale senso l'hai detto ma ... non sono né un super-esperto né un guru ... :(

conoscerai sicuramente anche tu la funzione VirtualProtect, e magari anche VirtualProtectEx :DSì, di nome le conosco, so cosa fanno, a grandi linee, ma non ho mai avuto necessità di usarle. E giusto per curiosità ho fatto una prova proprio adesso su Windows con VC++:

Codice che causa l'errore di protezione:
#define STRICT
#include <stdio.h>
#include <windows.h>

int main (void)
{
char *str = "hello";

printf ("Prima: %s\n", str);

str[0] = 'H';

printf ("Dopo: %s\n", str);

return 0;
}
Codice modificato che non causa l'errore di protezione:
#define STRICT
#include <stdio.h>
#include <windows.h>

int main (void)
{
BOOL bRet;
DWORD dwOldProtect;

char *str = "hello";

printf ("Prima: %s\n", str);

bRet = VirtualProtect (str, lstrlen (str)+1, PAGE_READWRITE, &dwOldProtect);

str[0] = 'H';

printf ("Dopo: %s\n", str);

return 0;
}
Ok??

sottovento
11-06-2006, 14:58
Beh, a dirla tutta, ho fatto qualche prova:

char *p = "ciao";

Questa e' una inizializzazione (cosi' la chiamano K&R) ed anche su VS l'istruzione

p[0] = 'C'

e' valida, senza alcuna modifica. Non sono esperto di questi sistemi, ho fatto un piccolo progetto (mantenendo tutte le opzioni di default) ed ha girato senza alcun problema. In effetti, non vedo alcuna violazione dello standard C in questo.

Ho fatto anche l'altra prova:


char *p;
p = "ciao";
il tentativo di modificare con

p[0] = 'C'

porta al bollone rosso del crash (la memoria non poteva essere written).

Come avete detto (grazie soprattutto ad andbin e crick_pitomba) sembra che davvero Visual Studio produca un codice NON STANDARD (e' bene precisarlo, visto che abbiamo anche la pretesa di far girare il codice su altre piattaforme) in modo da allocare queste stringhe nella heap.
Questo, teoricamente, per un motivo di ottimizzazione del codice. Per risparmiare spazio, insomma, ed e' per questo motivo che non si puo' cambiare detta stringa (visto che l'istruzione non e' legale nello standard C, la definisco come voglio. Non fa una piega).

A questo punto, confidente delle varie analisi che ho fatto in questi giorni (vale a dire, il resto del software sembra a posto), mi viene una domanda:
Cosa succede quando la macchina e' sotto stress?
In altri termini: ho un grosso carico di lavoro, memoria tutta occupata e le cpu che lavorano come matte. Che fine fa quest'area?
E poi, visto che non e' standard, quanto persiste? Tutta la durata dell'applicazione (beh, bella ottimizzazione! Fa proprio ridere!), la durata di un modulo? (cos'e' un modulo)? La vita di un oggetto? Se questa stringa e' usata in un oggetto richiamato periodicamente, come si comporta? Quanto dura?

Come dicevo, il crash avviene ogni 2 o 3 settimane e sembra sia imputabile a questo codice o qualcosa che avviene nello stesso task.

Grazie a tutti per il preziosissimo aiuto!!!

High Flying
Sottovento

repne scasb
11-06-2006, 15:49
Si tratta di un comportamento del compilatore oltremodo "subdolo", dovuto ad un eccessiva ottimizzazione del medesimo. Alcuni compilatori assumono che una costante stringa, indipendentemente dal punto del codice dove si trova sia valutata come un array di char "globale". Si analizzi la seguente funzione:


char * funzione()

{
return "Stringa";
}


Il corrispettivo in assembly per x86/32 sara':



.... <<-- Codice specifico dipendente dal sistema operativo

push ebx
push ecx
push edx
push esi
push edi
push ebp
mov ebp,esp
sub esp,4h
mov [ebp-4h],00408004 <<-- valore arbitrario
mov eax,[ebp-4h]
mov esp,ebp
pop ebp
pop edi
pop esi
pop edx
pop ecx
pop ebx
ret


Cio' che e' importante notare e' il valore '00408004'. Ossia, si tratta di un offset assoluto che e' invariante rispetto al codice eseguibile (il compilatore, in generale, non e' in grado di generare codice automodificante); se la stringa "Stringa" fosse aleatoria, il corrispondente codice assembly x86/32 del compilatore sarebbe stato:



.... <<-- Codice specifico dipendente dal sistema operativo

push ebx
push ecx
push edx
push esi
push edi
push ebp
mov ebp,esp
sub esp,4h
mov eax,[_const_001]
mov [ebp-4h],eax
mov eax,[ebp-4h]
mov esp,ebp
pop ebp
pop edi
pop esi
pop edx
pop ecx
pop ebx
ret


Ossia l'indirizzo di memoria che contiene il punto iniziale della stringa "Stringa" non e' un offset inmodificabile, ma e' contenuto in una locazione di memoria essa stessa variabile (_const_001). Al contrario, l'uso di un offset nel codice assembly dimostra che in effetti la stringa "Stringa" permane in memoria immultabile per l'intero codice. La stringa "Stringa" si trova e permane all'indirizzo 00408004.

Dove puo' scaturire allora l'errore? L'errore nasce da un'ulteriore ottimizzazione di alcuni "balordi" compilatori, che applicano il seguente principio: costanti stringa "uguali" occupano medesime porzioni di memoria. Ad esempio:


#include <stdio.h>

char * funzione(void);

void main(void)

{
char *p="Stringa";
char *q;

p[0]='A';

q=funzione();

printf("%s",q);
}

char * funzione()

{
return "Stringa";
}


Su alcuni "balordi" compilatori il risultato sara' la stanpa di: Atringa. Credo che l'errore possa essere questa...chiamiamola ottimizzazione ("Duplicate string merged").

crick_pitomba
11-06-2006, 17:08
Si tratta di un comportamento del compilatore oltremodo "subdolo", dovuto ad un eccessiva ottimizzazione del medesimo. Alcuni compilatori assumono che una costante stringa, indipendentemente dal punto del codice dove si trova sia valutata come un array di char "globale". Si analizzi la seguente funzione:


considerazioni interessanti... e ho voluto provare

sulla mia versione, con un progetto applicazione console win32 con opzioni di default, sia


char *q;
p[0]='A';
q=funzione();
printf("%s",q);


che

char *p = "ciao";
p[0] = 'C';


mi danno un errore sul tentativo di modificare la stringa. A questo punto solo un manuale di c può risolvere l'arcano.

ho ripreso in mano il mio vecchio manuale di c e ho scoperto l'arcano: evidentemente troppo java mi aveva fatto arruginire il C-ervello :P

secondo il manuale (kelley/pohl),dichiarare


char *p="prova";


significa creare un puntatore alla stringa costante "prova". Quindi immodificabile per definizione e qualsiasi cosa provo a fare su quella stringa, "correttamente", il programma va in crash.

se voglio un array modificabile di caratteri devo usare la sintassi


char p[]="prova";


ovviamente in questo caso non posso fare


char p[];
p="prova";


ed è il compilatore a fermarmi

Alla fine della storia il comportamento del compilatore risulta essere perfettamente coerente con la teoria...

Tornando al problema originale che avevi posto. per quanto detto sopra sia

char *p= "pippo";


che


char *p;
p="pippo";


sono valide e da quanto detto finora la cosa è più che lecita: sto parlando di un puntatore a caratteri (e non di stringhe) che può essere modificato a piacimento. è la stessa cosa che fare



int i;
i=10;
i=20;


non è sbagliato, ma è sconsigliato farlo perchè potrebbe portare ad errori logici nel programma lasciare variabili non inizializzate...

Potrei avere bisogno di questo codice:

char *p; //p è un puntatore non inizializzato
p="pippo"; //p viene fatto puntare alla stringa "pippo"
//...
p="paperino"; //p viene fatto puntare alla stringa "paperino"


e quindi quel tipo di assegnamento deve essere valido.

lavorando con la notazione
char p[]
non ho tutta questa flessibilità.

Questo per quanto riguarda le dichiarazioni e definizioni di puntatori a caratteri, diversamente questo tipo di codice:


char *f(){
return "pippo";
}

void main (){
char* p=f();
}


funziona per pura fortuna... tecnicamente quelle stringhe non dovrebbero esistere fuori dal loro scope e quello che le tiene in vita è una particolare implementazione del compilatore.

scusate l'eccessiva lunghezza :P

71104
11-06-2006, 17:59
Non ho ben capito in quale senso l'hai detto ma ... non sono né un super-esperto né un guru ... :( comunque dicevo sul serio ^^
via non fare il modesto, hai dimostrato spesso una esperienza quantomeno non comune :)

71104
11-06-2006, 18:10
char * funzione()

{
return "Stringa";
}


Il corrispettivo in assembly per x86/32 sara':



.... <<-- Codice specifico dipendente dal sistema operativo

push ebx
push ecx
push edx
push esi
push edi
push ebp
mov ebp,esp
sub esp,4h
mov [ebp-4h],00408004 <<-- valore arbitrario
mov eax,[ebp-4h]
mov esp,ebp
pop ebp
pop edi
pop esi
pop edx
pop ecx
pop ebx
ret
veramente il mio compilatore (Microsoft, e l'Intel si comporta analoga) non salva i registri tra chiamate; inoltre il mov esp,ebp subito prima del ripristino del vecchio frame pointer se ho fatto bene i conti è inutile (infatti il mio compilatore non lo genera)

Cio' che e' importante notare e' il valore '00408004'. Ossia, si tratta di un offset assoluto che e' invariante rispetto al codice eseguibile (il compilatore, in generale, non e' in grado di generare codice automodificante); in quel punto metterà sicuramente un fixup, sfruttando così il "supporto del sistema operativo per codice automodificante" :D
(la rilocazione insomma :))

[...] Ossia l'indirizzo di memoria che contiene il punto iniziale della stringa "Stringa" non e' un offset inmodificabile, ma e' contenuto in una locazione di memoria essa stessa variabile (_const_001). Al contrario, l'uso di un offset nel codice assembly dimostra che in effetti la stringa "Stringa" permane in memoria immultabile per l'intero codice. La stringa "Stringa" si trova e permane all'indirizzo 00408004. a meno di rilocazione del modulo naturalmente

Dove puo' scaturire allora l'errore? L'errore nasce da un'ulteriore ottimizzazione di alcuni "balordi" compilatori, che applicano il seguente principio: costanti stringa "uguali" occupano medesime porzioni di memoria. Ad esempio:


#include <stdio.h>

char * funzione(void);

void main(void)

{
char *p="Stringa";
char *q;

p[0]='A';

q=funzione();

printf("%s",q);
}

char * funzione()

{
return "Stringa";
}


Su alcuni "balordi" compilatori il risultato sara' la stanpa di: Atringa. Credo che l'errore possa essere questa...chiamiamola ottimizzazione ("Duplicate string merged"). un programma simile a runtime non può stampare quell'output su nessun compilatore a cui sia lecito attribuire questo nome: se il compilatore effettua quell'ottimizzazione vuol dire che la sezione contenente la stringa costante deve essere accessibile in sola lettura, quindi con la maggior parte dei compilatori esistenti il programma dovrebbe semplicemente crashare all'istruzione p[0]='A';

andbin
11-06-2006, 20:24
comunque dicevo sul serio ^^Grazie. :O



Comunque volevo precisare una cosa sul codice che ho postato (il primo, quello senza VirtualProtect). L'ho compilato in modalità "Debug" e mi ha dato appunto l'errore di protezione. Ho provato solo ora a compilarlo in modalità "Release". :doh: In effetti questo codice:
char *str = "hello";
str[0] = 'H';funziona senza problemi in modalità "Release"! :eek:
Adesso, perché questo .... non lo so di preciso. Ho provato a guardare le differenze tra Debug e Release nelle proprietà del progetto per capire quale (se c'era) opzione che potesse influire su questo diverso comportamento ma non ho trovato nulla.

Il gcc su Linux invece è più coerente ... l'assegnazione crasha in qualunque condizione. :p

71104
12-06-2006, 20:29
Comunque volevo precisare una cosa sul codice che ho postato (il primo, quello senza VirtualProtect). L'ho compilato in modalità "Debug" e mi ha dato appunto l'errore di protezione. Ho provato solo ora a compilarlo in modalità "Release". :doh: In effetti questo codice:
char *str = "hello";
str[0] = 'H';funziona senza problemi in modalità "Release"! :eek:
Adesso, perché questo .... non lo so di preciso. la variabile str era globale, ho indovinato? ^^
(hmm, no, non sembra che io abbia azzeccato visto che probabilmente le due righe di codice riportate erano consecutive, però io provo non si sa mai :p)

in caso avessi toppato: hai usato variabili globali in quel programma?

andbin
12-06-2006, 21:01
la variabile str era globale, ho indovinato? ^^
(hmm, no, non sembra che io abbia azzeccato visto che probabilmente le due righe di codice riportate erano consecutive, però io provo non si sa mai :p)

in caso avessi toppato: hai usato variabili globali in quel programma?No ... ho usato proprio il primo codice che avevo postato. Tutto era nel main ...
Comunque guarda ... io non starei a cercare l'ago nel pagliaio ... il VC++ sappiamo tutti com'è fatto ... ;) Eppoi se andassi sempre a cercare le cose più strane .... non la finirei più.

L'altro giorno ho visto che il gcc ha l'opzione -fwritable-strings (che non ho mai usato). Indovinate fa??? Ma poi il man dice anche ... Writing into string constants is a very bad idea; "constants" should be constant.
Quindi perché complicarsi la vita cercando di scrivere su stringhe literal?? :)

71104
13-06-2006, 00:34
Comunque guarda ... io non starei a cercare l'ago nel pagliaio ... il VC++ sappiamo tutti com'è fatto ... ;) manco per il cavolo invece... :O a parte che semmai dovresti parlare del compilatore, non dell'IDE, ma comunque entrambi sono tra i migliori esistenti nelle loro rispettive categorie, specialmente l'IDE.

ti spiego il ragionamento che ho fatto io circa le variabili globali, e come mai la stringa diventi magicamente da read-only a read-write cambiando le opzioni del compilatore: in modalità Release l'IDE setta automaticamente le opzioni (comunque modificabili a piacere) in modo tale che il programma sia ottimizzato al massimo, anche dal punto di vista della dimensione dell'eseguibile finale nonché della sua immagine caricata in memoria; di conseguenza quando il compilatore vede che hai usato una intera sezione .rdata (la cui dimensione ammonta come minimo a 4 kb) per soli 6 byte ("hello"), si chiede come mai sprecare 4090 bytes (se non sul disco quantomeno in RAM) piuttosto che mettere quella stringa assieme a tante altre variabili globali in una sezione .data che nota bene, non è read-only; in questo modo sfrutti i bytes di frammentazione interna della sezione .data (sempre a meno di non sforare il page bound ovviamente) piuttosto che creare un'altra frammentazione interna di 4090 bytes.

l'ipotesi della variabile globale mi era venuta in mente dal fatto che il ragionamento vale solo se effettivamente esiste una sezione .data, cioè se il programma di fatto usa variabili globali; ma ho toppato a indovinare perché anche se non ne hai usate tu, le usi comunque per forza senza saperlo visto che linki le librerie di runtime. che ci vuoi fare, ormai a causa di vari lavori che sto facendo sono abituato a ridefinire nei miei eseguibili il vero entry point e a rimuovere il runtime del compilatore tutte le volte :D


Eppoi se andassi sempre a cercare le cose più strane .... non la finirei più. scherzi?? è proprio con quelle cose che la mia cultura ha subito gli allargamenti più significativi

sottovento
13-06-2006, 08:48
Riassumendo (ci provo):
- L'istruzione scritta dal cliente:
char *p;
p = "ciao"
... usa p da qualche parte...

Non e' standard in C. Fino a qui siamo tutti d'accordo.

Non essendo standard, ogni implementazione puo' fare quello che vuole: andare in crash, allocare memoria read-only, o writeable, ...

- Sembra dunque che con VS tutto debba andar bene, anche se non si sa lo scope di questa istruzione (non mi sembra sia emerso dai discorsi).
Si parlava di ottimizzazioni, ma era uscito che queste stringhe potrebbero restare in memoria per tutta la durata dell'applicazione.
However, se le stringhe sono tante ed utilizzate localmente, su un programma che gira 24 ore al giorno per i prossimi 20 anni non sembrerebbe un'ottimizzazione.

- Rimango sempre con due crash mensili, dovuti a questa parte (non necessariamente questo codice). Secondo voi e' opportuno modificare questo codice in modo da renderlo piu' sicuro oppure secondo voi e' gia' abbastanza sicuro? Le modifiche, come potete immaginare, dovrebbero essere abbastanza semplici.

High Flying
Sottovento

crick_pitomba
13-06-2006, 09:50
Riassumendo (ci provo):
- L'istruzione scritta dal cliente:
char *p;
p = "ciao"
... usa p da qualche parte...

Non e' standard in C. Fino a qui siamo tutti d'accordo.



scusa se insisto, ma secondo me quella dichiarazione non è errata ed è standard c.

Cito dal manuale ANSI C
"si considerino le due dichiarazioni

char *p="abcde"; e char s[]="abcde";

con la prima dichiarazione il compilatore riserva spazio in memoria per p e per la stringa "abcde", inizializzando p con l'indirizzo di base della costante stringa. si può dunque pensare che p punti alla stringa.

la seconda dichiarazione è equivalente a quanto segue
char s[]={'a','b','c','d','e'}
visto che le parentesi quadre sono vuote, il compilatore riserva 6 byte di memoria per l'array s.
"

nel primo caso abbiamo un puntatore ad un'area di memoria ben definita che contiene la stringa, il compilatore alloca separatamente la memoria per p, per la stringa e poi inserisce in p il valore corretto.
ovviamente queste operazioni possono essere fatte senza problemi in due fasi diverse
char *p;
p="abcde"

nel secondo caso abbiamo un array di caratteri con tutti i problemi di allocazione di memoria.

Quindi, quando hai scritto

char *p = "una stringa";

Questo e' legale: il compilatore riconosce che non e' un assegnamento bensi' una inizializzazione, quindi allochera' automaticamente la memoria necessaria.

char *p;
p = "una stringa";

In questo caso, p punta ad una locazione temporanea di memoria, la cui validita' non e' specificata da nessuna parte.


p non punta ad una locazione temporanea di memoria ignota bensì a quell'area di memoria ben precisa che il compilatore ha riservato alla stringa costante (come risulta dal manuale ANSI C e, coerentemente, dalla documentazione del compilatore). ovviamente quest'area di memoria campa per tutta la durata del programma.

In definitiva
le dimensioni della stringa sono note e fissate
le dimensioni del puntatore sono note e fissate
quindi non ci sono errori.

l'errore sarebbe (e anche grave)


char p[];
p= "una stringa";


in questo caso il casino ci sarebbe in quanto il compilatore non ha modo di assegnare la memoria e le tue considerazioni sarebbero corrette.

quindi



- Rimango sempre con due crash mensili, dovuti a questa parte (non necessariamente questo codice). Secondo voi e' opportuno modificare questo codice in modo da renderlo piu' sicuro oppure secondo voi e' gia' abbastanza sicuro? Le modifiche, come potete immaginare, dovrebbero essere abbastanza semplici.

High Flying


ora se il tuo cliente ha scritto
char *p;
p="pippo";
perfettamente conscio che sta facendo riferimento a una stringa costante da non modificare, non ci sono santi... sicuramente non ha sbagliato.
l'errore è solo stilistico(non è consigliabile dichiarare una variabile senza inizializzarla)

ma se ha scritto
char *p;
p="pippo";

con l'intenzione di scrivere
char p[]="pippo";
e di usare p come più gli aggrada, allora ha fatto un grave errore, il codice non è sicuro e certamente è quella la causa del problema.


cmq se dici che l'ha fatto spesso e il crash capita così raramente mi sembra strano...
lo so che hai già controllato questo aspetto, ma se capita costantemente dopo 2/3 settimane è più plausibile che ci sia qualcosa che riempia l'heap con costanza ma lentamente (esempio una lista che si riempie e si svuota non cancellando correttamente tutti gli elementi). A quel puntoe tenta di allocare memoria, non fa il controllo sul risultato dell'allocazione e poi tenta di scrivere lo stesso. Non è che usa qualche libreria esterna di cui non puoi visualizzare il codice?

prova a compilare una versione del codice riducendo le dimensioni dello heap per vedere se il crash si verifica prima.

sottovento
14-06-2006, 07:47
Ciao,
non voglio aprire una discussione infinita.
Mi permetto solo di dire, (non voglio ovviamente insistere) che:

char *p;
p="abcde"

Non e' per niente uguale a

char *p = "abcde"

Anche se Visual Studio ti alloca la memoria necessaria e la tiene allocata per tutta la durata dell'applicazione, la seconda istruzione non e' standard ANSI, almeno per quanto riguarda i miei manuali.

Infatti, su altri sistemi (per esempio, VMEexe, pSos+, OS/9, e tanti altri) l'istruzione causa immediatamente il crash della macchina.

La seconda istruzione viene detta "inizializzazione": creo e do un valore.
La prima e' un semplice assignamento. Che poi mamma Microsoft intervenga e' un altro paio di maniche.

La prima istruzione e' standard quanto la seguente:

a = 1;
a++ += a++;

Se la esegui su diversi sistemi con diversi compilatori, otterrai valori diversi. E' standard ANSI?

Anche per quanto riguarda i vettori:
char vec[] = "alsdkfjh"
e' detta ancora inizializzazione,ed e' valida.

char []vect;
vect = "dlkjh";
non verra' nemmeno accettata in fase si compilazione. E' ben diverso.

Cmq grazie per i suggerimenti. Ovviamente continuero' a cercare per risolvere questo maledetto problema. Oltretutto qui non c'e' altro da fare, al massimo posso guardare una tempesta di sabbia....


High Flying
Sottovento

trallallero
14-06-2006, 08:09
a++ += a++;

??? il tuo compilatore accetta un
a++ += ... :confused:

hai digitatto male immagino :mbe:
"a++" non e' un lvalue come fai ad assegnare un valore ?

se invece intendevi
a += a++ ci sono delle regole ANSI che stabiliscono le precedenze.

crick_pitomba
14-06-2006, 11:37
Ciao,
Anche se Visual Studio ti alloca la memoria necessaria e la tiene allocata per tutta la durata dell'applicazione, la seconda istruzione non e' standard ANSI, almeno per quanto riguarda i miei manuali.


non ho capito una cosa: ma sul tuo manuale è riferito esplicitamente che

char* p;
p="abcde";
non è standard?
oppure c'è scritto esplicitamente che per "abcde" l'area di memoria allocata è indefinita?
oppure c'è scritto che in quel caso non viene allocata la memoria per p?

cmq per correttezza, quando si cita qualcosa, si cita anche la fonte, non solo quello che c'è scritto.

la dichiarazione
char *p;
non ha niente a che vedere con l'allocazione di memoria per quello a cui punta p. il compilatore deve allocare la memoria solo e soltanto per un puntatore a carattere di cui sa perfettamente la dimensione; poi quello che ci voglio mettere dentro sono problemi miei.

è la stessa cosa di fare

int a=0;
int b=1;
int *c; //mica qui devo per forza inizializzare la variabile?

in seguito posso tranquillamente fare

c=&a;
c=&b;

tuttavia quel c potrebbe puntare anche ad un array di interi, se la cosa mi aggrada

analogamente, potrei fare

char a='a';
char b='b';
char *c;

c=&a;
c=&b;

tuttavia quel c potrebbe puntare anche ad un array di caratteri, se la cosa mi aggrada.
l'importante è che quando faccio

c= qualcosa

quel qualcosa sia correttamente allocato. Passiamo al discorso dell'allocazione di memoria.

Per "una stringa", l'allocazione c'è ed è prevista dallo standard ansi (e non è un regalo particolare del compilatore visual studio) infatti ti ho citato il manuale in cui è spiegata questa cosa.

ora, tu potresti pensare che alla fine queste sono mie opinioni... ma
oltre il mio manuale, che spiega le cose in questo modo, nel paragrafo 5.5 della seconda edizione del K&R versione inglese(quindi già siamo a 2 manuali ANSI C che la vedono allo stesso modo:k&R e Kelley/pohl) c'è questo esempio


" If pmessage is declared as

char *pmessage;
then the statement

pmessage = "now is the time";
assigns to pmessage a pointer to the character array. This is not a string copy; only pointers
are involved. C does not provide any operators for processing an entire string of characters as
a unit. "

che è proprio la coppia di istruzioni incriminate.

Per quanto riguarda l'allocazione della memoria, continua con

"There is an important difference between these definitions:

char amessage[] = "now is the time"; /* an array */
char *pmessage = "now is the time"; /* a pointer */

amessage is an array, just big enough to hold the sequence of characters and '\0' that initializes it. Individual characters within the array may be changed but amessage will always refer to the same storage. On the other hand, pmessage is a pointer, initialized to point to a string constant; the pointer may subsequently be modified to point elsewhere, but the result is undefined if you try to modify the string contents.


ovviamente, essendo l'allocazione è statica quindi quando il compilatore processa l'istruzione

p="prova";

non è che alloca la memoria per "prova" e poi assegna il contenuto a p al momento (non avrebbe senso questo overhead), ma assegna a p il puntatore all'area di memoria che ha riservato inizialmente per l'array di carattere "prova" nella sua sezione dati.

Ti ricordo che in c scrivere "prova" significa definire a volo un array con {'p','r','o','v','a','\0'} al suo interno, come specificato nel paragrafo 1.9 del K&R. quindi per ogni costante stringa viene definito un array. essendo una costante questa non sparisce nel nulla dopo che l'ho usata (il compilatore mica può sapere quante volte l'utilizzerò nel corso del programma).

Inoltre, secondo il K&R, il comportamento di modifica di una costante stringa tramite puntatori è "undefined" quindi abbiamo risolto anche l'arcano sul perchè diverse versioni di compilatori reagiscono in modo diverso al tentativo di modifica della stringa costante: ogni compilatore è libero di fare quello che vuole visto che lo standard non specifica il risultato dell'operazione.

ovviamente, se trovi l'istruzione in cui usa sto puntatore per scrivere in una stringa costante... probabilmente hai trovato l'errore


Oltretutto qui non c'e' altro da fare, al massimo posso guardare una tempesta di sabbia....


non so se volevi essere sarcastico, ma non mi sembra un bel modo di ringraziare chi cerca di farti notare un tuo errore concettuale. alla luce di quello che si legge sui manuali il tuo cliente non sembra avere fatto un errore.

ovviamente puoi citarmi il tuo manuale di riferimento e la sezione dove viene citata questa violazione dello standard, giusto per completare la panoramica sulla questione.