PDA

View Full Version : Su linux funziona, su Windows no [C]


vicko98
20-08-2018, 00:25
Salve,

mi sto preparando per un esame universitario basato sul linguaggio C.
Ho svolto una traccia di qualche esame fa in cui veniva chiesto di creare una lista con una sottolista annidata, inserendo prima i dati nella lista principale in maniera ordinata, mentre nella sottolista è richiesto un inserimento in coda.

il testo è questo:
Un servizio di assistenza clienti utilizza un archivio informatico in cui gli interventi di assistenza
vengono organizzati in funzione del nome del quartiere in cui si deve effettuare l'intervento. In
particolare, la struttura dati utilizzata include una lista che contiene le seguenti informazioni:

Nome Quartiere
Coda degli interventi

Dove il campo Nome Quartiere specifica il nome di uno dei quartieri in cui è divisa la città in cui
opera il servizio di assistenza.
La lista è realizzata con puntatori ed è ordinata per Nome di Quartiere.
Per ogni elemento della lista, oltre al campo Nome Quartiere, è memorizzata la coda degli
interventi. Tale coda contiene tutti gli interventi da eseguire in ciascun quartiere, memorizzati in
ordine temporale di arrivo. Ciascun elemento della coda degli interventi contiene i dati del
cliente che ha richiesto l’intervento di assistenza, e cioè:

Cognome
Nome
Via
Telefono
Tipo di Intervento Richiesto
Descrizione dell'intervento richiesto

La coda degli interventi è realizzata con puntatori.
Il tipo di intervento è codificato con un opportuno codice definito tramite enum
Codificare in Linguaggio C:

 le strutture dati utilizzate;
 una procedura di inserimento di un nuovo quartiere (non presente in archivio);
 una procedura di inserimento di una richiesta di intervento. Se il quartiere non esiste, viene
inserito in archivio;
 una procedura che indichi il numero di interventi per ciascun quartiere;
 una procedura che riceve in ingresso il nome di un quartiere e che in uscita fornisce tutti i dati
del primo intervento presente in coda per quel quartiere. Tale richiesta viene quindi eliminata
dalla coda;
 Il main che richiama opportunamente le procedure precedenti.

Durata della prova: 2 ore


il codice è questo:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
enum tipo {inst,exe,test};
struct intervento
{
char cognome[20];
char nome[20];
char via[30];
char telefono[15];
enum tipo t;
char info[30];
struct intervento *next;
};
struct quartiere
{
char nomehq[20];
struct intervento *act;
struct quartiere *next;
};
void inshq(struct quartiere** list)
{
struct quartiere *p,*g,*t=*list,*scorri=*list;
int status=0;
p=(struct quartiere*)malloc(sizeof(struct quartiere));
p->next=NULL;
//p->act=(struct intervento*)malloc(sizeof(struct intervento));
//p->act->next=NULL;
printf("Inserisci un nome di un quartiere: ");
scanf("%s",p->nomehq);
while(scorri!=NULL)
{
if(strcmp(p->nomehq,scorri->nomehq)==0)
{
status=1;
printf("Quartiere giC esistente! Inserire un altro nome \n");
return;
}
else status=0;
scorri=scorri->next;
}
if(status==0)
{
if(*list==NULL)
{
*list=p;
}
else
{
for(g=*list;g!=NULL&&strcmp(g->nomehq,p->nomehq)<0;t=g,g=g->next);
if(t==g)
{
p->next=*list;
*list=p;
}
else
{
t->next=p;
p->next=g;
}
}
}
}

void inscoda(struct quartiere **list)
{
char namehq[20];
int status=0;
struct quartiere *scorri=*list;
printf("Inserire nome quartiere: ");
scanf("%s",namehq);
while(scorri!=NULL)
{
if(strcmp(scorri->nomehq,namehq)==0)
{
printf("Quartiere trovato \n");
status=1;
struct intervento *p;
p=(struct intervento*)malloc(sizeof(struct intervento));
p->next=NULL;
printf("Inserisci Cognome: ");
scanf("%s",p->cognome);
printf("Inserisci Nome: ");
scanf("%s",p->nome);
printf("Inserisci Via: ");
scanf("%s",p->via);
printf("Inserisci Telefono: ");
scanf("%s",p->telefono);
printf("Inserisci Tipo intervento [0=install|1=Esecuzione|2=Test] : ");
scanf("%d",&(p->t));
while(getchar()!='\n');
printf("Inserisci descrizione intervento: ");
fgets(p->info,31,stdin);
if(scorri->act==NULL)
{
scorri->act=p;
return;
}
else
{
struct intervento *scorri1=scorri->act;
//while(scorri1->next!=NULL)scorri1=scorri1->next;
scorri1->next=p;
}
}
else status=0;
scorri=scorri->next;
}
if(status==0)inshq(list);

}


void stampa(struct quartiere **list)
{
struct quartiere *scorri=*list;
while(scorri!=NULL)
{
printf("Nome quartiere: %s \n",scorri->nomehq);
struct intervento *scorri1=(*list)->act;
while(scorri1!=NULL)
{
printf("Cognome:%s \n",scorri1->cognome);
scorri1=scorri1->next;
}
scorri=scorri->next;
}
}
void cont(struct quartiere **list,char nhq[30])
{
struct quartiere *scorri=*list;
int i=0,stat=0;
while(scorri!=NULL)
{
if(strcmp(nhq,scorri->nomehq)==0)
{
struct intervento* scorri1=scorri->act;
while(scorri1!=NULL)
{
i++;
scorri1=scorri1->next;
}
printf("Numero interventi quartiere: %s \n Interventi n. %d",scorri->nomehq,i);
return;
}
scorri=scorri->next;
}
printf("non sono stati trovati quartieri con quel nome! \n");
}
void cerca(struct quartiere **list,char nhq[20])
{
struct quartiere *scorri=*list;
while(scorri!=NULL)
{
if(strcmp(scorri->nomehq,nhq)==0)
{
struct intervento *p;
p=scorri->act;
scorri->act=scorri->act->next;
printf("Cognome del primo in coda: %s \n",p->cognome);
printf("Nome del primo in coda: %s \n",p->nome);
free(p);
}
scorri=scorri->next;
}
}
int main()
{
struct quartiere *lista;
lista=NULL;
int i=0;
inshq(&lista);
while(i<1){
inscoda(&lista);
i++;
}
cont(&lista,"tremestieri");
cerca(&lista,"tremestieri");
//stampa(&lista);
}


Il problema sorge sul sistema operativo utilizzato, se eseguito con il compilatore di linux(gcc) parte senza dare troppi problemi.
Mentre se eseguo lo stesso codice su windows, questo al termine dell'inserimento dei primi dati nella sottolista, fa return e termina il programma :




Process returned -1073741819 (0xC0000005) execution time : 21.037 s
Press any key to continue.



Ora, volendo..il docente utilizza esclusivamente linux, quindi ironicamente non sarebbe un problema :D, ma a me da fastidio lasciarlo così, vorrei capire quindi cosa fa infuriare windows con il proprio compilatore, qualcuno sa dirmi perchè?

Grazie in anticipo.

sottovento
20-08-2018, 16:45
Nel main() hai scritto:


struct quartiere *lista;
lista=NULL;
int i=0;
inshq(&lista);


vai quindi a dereferenziare lista, che e' un puntatore a NULL. Il risultato non e' predicibile. Probabilmente il compilatore su linux cerca di dare senso a quell'operazione, mentre quello di Windows ti stoppa subito.

sottovento
20-08-2018, 17:20
Ma sei sicuro? A me questo sembra corretto, perche' alla fine nella funzione inshq non viene passato il puntatore ma la sua reference.
Ops, hai ragione. Sono stato troppo precipitoso.
Cmq se posso permettermi un consiglio: @vicko98, aggiungi sempre il controllo che la malloc() sia andata a buon fine. In caso contrario avrai dei crash senza spiegazione alcuna nel caso la malloc() fallisca

sottovento
20-08-2018, 17:44
Ok, vediamo se prendo un'altra cantonata. @Bellaz89 correggimi se sbaglio ancora.

Nella
void inshq(struct quartiere** list)

si va a fare

p=(struct quartiere*)malloc(sizeof(struct quartiere));
p->next=NULL;

Poi si scandisce la lista per inserire p nel caso il quartiere inserito non sia gia' presente.
Prima nota: nel caso il quartiere sia gia' presente ci sara' un memory leak!!!

La cosa interessante e' che il puntatore p->act non e' inizializzato, quindi punta a casaccio.
L'inserimento in inshq() andra' correttamente, mentre l'esecuzione fallira' quindi con un errore in inscoda():


if(scorri->act==NULL)
{
scorri->act=p;
return;
}
else
{
struct intervento *scorri1=scorri->act;
//while(scorri1->next!=NULL)scorri1=scorri1->next;
// SOTTOVENTO - qui
scorri1->next=p;
}


Probabilmente vicko98 ha utilizzato Visual C++ per eseguire questo codice. In modalita' debug, Visual C++ inizializza i dangling pointer a 0xcdcdcdcd in modo che il debugger possa trovare il pattern e andare in crash immediatamente. Questo spiegherebbe la differenza di funzionamento sui due sistemi in oggetto.

vicko98
21-08-2018, 20:39
Rieccomi,

Scusate se non ho risposto precedentemente, alla fine ho risolto il problema.



//p->act=(struct intervento*)malloc(sizeof(struct intervento));
//p->act->next=NULL;


Era necessario definire che p->act fosse null, perchè la sottolista deve essere dichiarata vuota quando si crea un nuovo nodo, dunque ho risolto facendo :


p->act=NULL;


(p->act->next=NULL effettivamente non aveva più senso)

Ho trovato la soluzione, ma non ho capito il motivo per cui linux riconosce comunque il puntatore, utilizzandone lo spazio offerto con la malloc..mentre windows riconosce qualche errore e termina li il programma, chissà...

Grazie per il vostro aiuto :)

sottovento
21-08-2018, 22:43
Ho trovato la soluzione, ma non ho capito il motivo per cui linux riconosce comunque il puntatore, utilizzandone lo spazio offerto con la malloc..mentre windows riconosce qualche errore e termina li il programma, chissà...

Grazie per il vostro aiuto :)

Come ti dicevo, e' perche' non avendolo inizializzato avevi un dangling pointer.
In questo senso, Visual Studio e' piu' furbo: quando compili in modalita' debug, inizializza i dangling pointer a 0xcdcdcdcd e questo gli permette di trovare il problema subito e di visualizzartelo con un crash.
Il debugger di linux era decisamente meno furbo e per tua sfortuna il dangling pointer puntava ad una locazione valida. Quindi c'era l'errore anche su linux ma siccome puntava ad una locazione valida (anche se a caso) il software sembrava comunque funzionare

fano
22-08-2018, 15:18
+1 per Windows insomma.

Stresso anch'io questa cosa comunque: testa sempre i valori di ritorno delle funzioni! Su Linux malloc() in realtà non fallisce mai anche se chiedi più della memoria che il sistema realmente può darti (è un "bug" nascosto sotto lo strano nome di "optimistic memory allocation strategy"), altri OS fatti meglio tornano NULL in quei casi, quindi occhio!

WarDuck
22-08-2018, 18:09
+1 per Windows insomma.

Al massimo per il compilatore/linker :).

Evidentemente VS in modalità debug sostituisce le routine di allocazione per l'heap con routine che inizializzano ad un valore noto.

Dovrebbe essere possibile fare la stessa cosa sotto Linux definendo la variabile d'ambiente: MALLOC_PERTURB_ ad un valore intero. Il suo negato viene usato per riempire la memoria allocata proprio per beccare situazioni strane.

http://man7.org/linux/man-pages/man3/mallopt.3.html


Stresso anch'io questa cosa comunque: testa sempre i valori di ritorno delle funzioni! Su Linux malloc() in realtà non fallisce mai anche se chiedi più della memoria che il sistema realmente può darti (è un "bug" nascosto sotto lo strano nome di "optimistic memory allocation strategy"), altri OS fatti meglio tornano NULL in quei casi, quindi occhio!

Premesso che bisogna controllare il valore di ritorno di malloc() a prescindere, la tua considerazione non c'entra niente con l'errore che ha riscontrato l'utente.

Nel suo caso infatti trattasi di memoria allocata correttamente ma non completamente inizializzata, in questo caso c'entra più che altro l'implementazione della malloc() della libreria C, che adotta un pool di memoria da riutilizzare per velocizzare l'allocazione. In questi casi è molto probabile che nella serie di malloc() free() malloc() ti venga riassegnata la stessa memoria.

Quando il sistema operativo (Linux) ti assegna una nuova pagina di memoria infatti questa viene sempre azzerata :).

PS: sotto Linux con un compilatore abbastanza recente (gcc) se passi i flag "-Wall -Wextra" è molto probabile che ti dia dei warning sul fatto che utilizzi una variabile non inizializzata.

In ogni caso sempre sotto Linux è possibile utilizzare valgrind, che è una cosa che consiglio sempre quando ci sono problemi di questo tipo.

Cmq quello che bisogna stressare è compilare soprattutto con i warning abilitati: ho visto persone, anche esperte non farlo e per me è da bocciatura :asd:.

melko
22-08-2018, 20:35
Al massimo per il compilatore/linker :).
PS: sotto Linux con un compilatore abbastanza recente (gcc) se passi i flag "-Wall -Wextra" è molto probabile che ti dia dei warning sul fatto che utilizzi una variabile non inizializzata.

In ogni caso sempre sotto Linux è possibile utilizzare valgrind, che è una cosa che consiglio sempre quando ci sono problemi di questo tipo.

Cmq quello che bisogna stressare è compilare soprattutto con i warning abilitati: ho visto persone, anche esperte non farlo e per me è da bocciatura :asd:.

concordo,
in questo caso però anche con -Wall -Wextra sia gcc che clang non danno warning sulla mancata inizializzazione,
una passata di valgrind invece lo evidenzia subito

fano
23-08-2018, 13:55
Anch'io un tempo abilitavo sempre -Wall -Wextra, ma ho notato un "difetto" (va beh oltre all'ovvio che "all" non abilita realmente tutto, ma ci sia "extra" pure) a volte segnala come warning complete sciocchezze e così ti perdi magari quelli "buoni" che - secondo me - in un linguaggio di programmazione serio sarebbero errori belli e buoni.

Ve ne racconto uno che è capitato ad un programmatore junior con monitor di recupero con pure "cluster" bruciati, dopo un merge di 2 branch CVS terrificante fatto alla "linux" (tutto a mano con 2 GVIM affiancati :muro: ) tutto compilava e poi andava allegramente in core!
Venivano generati tipo 150 warning quindi venivano tranquillamente ignorati
peccato che dopo circa 6 ore che riguardavamo il diff vediamo una cosa orrida: una sottrazione tra 2 stringhe! Uno dei "-" del diff si era magicamente spostato e "giaceva" su uno dei famosi cluster bruciati.

GCC lo warning lo aveva pure dato, che quella sottrazione tra stringhe non lo convinceva mica, ma poi aveva compilato lo stesso.

Io avevo anche provato a scegliere con cura quali -W abilitare, ma alla fine con ogni versione di GCC cambiano significato... questo è uno dei tanti enormi problemi del C.