2012-03-08 6 views
6

Eventuali duplicati:
Is destructor called if SIGINT or SIGSTP issued?Come posso gestire il segnale di interruzione e chiamare il distruttore in C++?

mio codice come questo:

#include <iostream> 
#include <signal.h> 
#include <cstdlib> 

void handler(int) { 
    std::cout << "will exit..." << std::endl; 
    exit(0); 
} 

class A { 
public: 
    A() {std::cout << "constructor" << std::endl;} 
    ~A() {std::cout << "destructor" << std::endl;} 
}; 

int main(void) { 
    signal(SIGINT, &handler); 

    A a; 
    for (;;); 

    return 0; 
} 

Quando ho premuto Ctrl-C, è stampato:

constructor 
^Cwill exit... 

Non è stato stampato alcun "distruttore". Quindi, come posso uscire in modo pulito?

+0

Nota che la risposta "giusta" non è così "banale "come suggeriscono alcune risposte qui sotto. Ad esempio, dovresti considerare l'utilizzo di 'sigaction' intead di' signal'. Inoltre, il motivo principale per non usare 'exit()' in un gestore di segnali asincroni è che è semplicemente [non supportato] (http://pubs.opengroup.org/onlinepubs/009604599/functions/xsh_chap02_04.html#tag_02_04) . Allo stesso modo per usare 'iostreams' nel gestore del segnale (non ne sono sicuro al 100%). Quindi, usare un semplice 'bool' invece di' volatile std :: sig_atomic_t' potrebbe non essere affidabile o causare un comportamento indefinito. –

+0

http://ppst.me/5cUMeA ha scritto questo piccolo trucco per te, trovo molto più pulito dell'utilizzo di una variabile globale per tenere traccia del flusso di esecuzione. –

+1

@refp Il problema è che presenta un comportamento indefinito dappertutto. –

risposta

11

Con difficoltà. Già, il codice che hai scritto non ha un comportamento definito da ; non ti è permesso uscire in un flusso in un gestore di segnali; per questo, non sei autorizzato a chiamare exit neanche. (Sto basando mie asserzioni qui sullo standard Posix. In puro C++, tutti i sei permesso di fare è assegnare a una variabile di tipo sig_atomic_t.)

In un caso semplice come il tuo codice, si potrebbe fare qualcosa di simile:

sig_atomic_t stopFlag = 0; 

void 
handler(int) 
{ 
    stopFlag = 1; 
} 

int 
main() 
{ 
    signal(SIGINT, &handler); 
    A a; 
    while (stopFlag == 0) { 
    } 
    std::cout << "will exit..." << std::endl; 
    return 0; 
} 

a seconda dell'applicazione, si può essere in grado di fare qualcosa di simile, controllando il stopFlag in luoghi appropriati.Ma in generale, se provi a fare questo, ci saranno le condizioni della gara: controlli stopFlag prima del avviando una chiamata di sistema interrotta, quindi esegui la chiamata; il segnale arriva tra il controllo e la chiamata, si effettua la chiamata e non è interrotto. (Ho usato questa tecnica, ma in un'applicazione in cui la chiamata di sistema unico interrompibile è stata una presa di corrente letta con un brevissimo timeout.)

Tipicamente, almeno sotto Posix, si finirà per dover creare un filo di comando ; questo può quindi essere utilizzato per chiudere in modo pulito tutti gli altri thread . Fondamentalmente, si avvia impostando la maschera di segnale per bloccare tutti i segnali, quindi nel thread di gestione dei segnali, una volta avviato, impostare per accettare i segnali a cui si è interessati e chiamare sigwait(). Questo implica, tuttavia, che si eseguano tutte le normali azioni necessarie per uno shutdown pulito dei thread : il thread di gestione del segnale deve conoscere su tutti gli altri thread, chiamare pthread_cancel su di essi, ecc. E si è il compilatore deve generare il codice corretto per gestire pthread_cancel o è necessario sviluppare altri mezzi per garantire che tutti i thread siano stati notificati correttamente . (Si potrebbe sperare, oggi, che tutti i compilatori gestiscono pthread_cancel correttamente, ma non si sa mai,.. Così facendo ha significativa dei costi di esecuzione, e non è di solito necessario)

+0

+1: finalmente! :-) –

+0

Grazie in particolare per l'ultimo paragrafo. L'ho fatto per un po ', ed è bello vedere più prove che le altre persone sono d'accordo che è una buona cosa da fare. –

+0

Non ho capito le condizioni della gara. La domanda vuole solo usare l'interrupt per fermare il programma; il gestore di interrupt può impostare 'stopFlag' e nessuno lo cancella. Puoi testare la bandiera a tuo piacimento ovunque (ovviamente al di fuori di ogni chiamata di sistema). L'interruzione potrebbe verificarsi durante o appena prima una chiamata di sistema, ma il gestore imposta semplicemente il flag; la chiamata di sistema verrà completata e alla prossima occasione di test verrà trovato il flag impostato e l'arresto inizierà. Potresti perdere una frazione di μs, ma niente di serio. Il problema principale è garantire che vengano eseguiti test regolari. –

2

La memoria dovrebbe essere liberata comunque. ma se hai il codice da gestire, suppongo che dovrai tenere traccia di tutti i tuoi oggetti e poi distruggerli secondo necessità (ad esempio facendo in modo che il costruttore li aggiunga a std::set, mentre il distruttore li rimuove di nuovo). Tuttavia ciò non garantirebbe un ordine di distruzione appropriato (che potrebbe richiedere una soluzione più complessa).

Si potrebbe anche usare il gestore del segnale per impostare un flag che lascerà il loop infinito (o qualunque cosa si stia facendo nel loop principale) invece di terminare semplicemente usando exit().

+2

Per quanto riguarda la prima soluzione: non puoi accedere a un globale 'std :: set' in un gestore di segnale senza incorrere in comportamenti indefiniti. (Non si può fare molto di qualcosa in un gestore di segnale senza incorrere in comportamenti indefiniti. Il suo codice corrente ha un comportamento indefinito.) –

+0

Hm, sì, hai ragione. Quindi, in ogni caso, richiederebbe che venga impostato un flag solo per lasciare il ciclo e/o causare una gestione aggiuntiva. – Mario

+0

Tutto ciò che si può fare legalmente in un gestore di segnale è impostare un flag o annullare in modo efficace il programma, senza svuotare i buffer IO, ecc. ('_exit()' o 'abort()'). Quindi, se non vuoi abortire, devi impostare una bandiera. Oppure gestisci i segnali in un thread separato (almeno sotto Unix). –

2

è necessario uscire dal campo di applicazione della funzione principale di avere il lavoro distruttore:

#include <iostream> 
#include <signal.h> 
#include <cstdlib> 

bool stop = false; 
void handler(int) { 
    std::cout << "will exit..." << std::endl; 
    stop = true; 
} 

class A { 
public: 
    A() {std::cout << "constructor" << std::endl;} 
    ~A() {std::cout << "destructor" << std::endl;} 
}; 

int main(void) { 
    A a; 
    signal(SIGINT, &handler); 

    for (;!stop;); 

    return 0; 
} 
2

E 'perché il contesto del codice normale e il gestore del segnale è diverso. Se metti la variabile a in ambito globale (ad esempio, al di fuori di qualsiasi funzione) vedrai che il distruttore è chiamato correttamente.

Se si desidera gestire la pulizia da soli (invece di lasciare il run-time e OS gestirlo), si può avere un ciclo condizionale, qualcosa di simile:

bool keep_running = true; 

void handler(int) { 
    std::cout << "will exit..." << std::endl; 
    keep_running = false; 
} 

int main(void) { 
    signal(SIGINT, &handler); 

    A a; 
    while (keep_running); 

    return 0; 
} 
+1

Oppure potresti. Chiamare "exit" da un gestore di segnale è un comportamento indefinito, almeno secondo C90 e Posix, quindi qualsiasi cosa potrebbe (e in pratica accade). –

1

exit termina il processo quasi subito ; in particolare, gli oggetti con durata di archiviazione automatica non vengono distrutti. Gli stream sono anche scaricati e chiusi, ma non ti è permesso toccare i flussi dall'interno di un gestore di segnale. Quindi ...

Semplicemente non chiamare exit da un gestore di segnale; imposta un flag atomico per indicare invece che il ciclo termina.

#include <iostream> 
#include <signal.h> 
#include <cstdlib> 

sig_atomic_t exitRequested = 0; 

void handler(int) { 
    std::cout << "will exit..." << std::endl; 
    exitRequested = 1; 
} 

struct A { 
    A() { std::cout << "constructor" << std::endl; } 
    ~A() { std::cout << "destructor" << std::endl; } 
}; 

int main() { 
    signal(SIGINT, &handler); 

    A a; 
    for (; !exitRequested;); 
} 
+0

eccellente errore di battitura. –

+0

Nitpicker-Alert :-) Beh, tecnicamente 'exit' [does not] (http://www.cplusplus.com/reference/clibrary/cstdlib/exit/) esci immediatamente dal processo, a seconda della tua definizione di" immediato " ovviamente. Soprattutto per C++ esegue i distruttori per oggetti statici (mi dispiace non ho alcun riferimento allo standard per quello). Tutto ciò contribuisce al motivo per cui "exit" non è considerato async-signal-safe e solo "_exit()" è, cosa che non fa nulla di quanto detto prima. –

+1

@ Christian.K Non è pignola. Ci sono pochissime cose che puoi fare legalmente in un gestore di segnali, a causa della sua natura asincrona. Sotto Posix, ad esempio, puoi fare un 'scrivi' di sistema, ma non qualcosa con' FILE * 'o un iostream. E 'exit()' è vietato, perché farà cose con 'FILE *' e 'iostream' (svuota qualsiasi output, per esempio). –

Problemi correlati