C'de Ctrl-C'yi yakalayın


158

C'de Ctrl+ nasıl yakalanır C?


5
C .... sinyal yakalamak gibi bir şey yoktur ya da en azından C99 standardını okuyana kadar düşündüm. C'de tanımlanmış sinyal işleme olduğu ortaya çıkıyor, ancak Ctrl-C herhangi bir spesifik sinyal veya sinyal üretmek için zorunlu değil. Platformunuza bağlı olarak bu imkansız olabilir.
JeremyP

1
Sinyal işleme çoğunlukla uygulamaya bağlıdır. * Nix platformlarında <signal.h> kullanın ve OSX kullanıyorsanız işleri daha da kolaylaştırmak için GCD'den yararlanabilirsiniz ~.
Dylan Lukes

Yanıtlar:


206

Bir sinyal işleyici ile.

İşte saygısız basit bir örnek boololarak kullanılan main():

#include <signal.h>

static volatile int keepRunning = 1;

void intHandler(int dummy) {
    keepRunning = 0;
}

// ...

int main(void) {

   signal(SIGINT, intHandler);

   while (keepRunning) { 
      // ...

Haziran 2017'de düzenleyin: Özellikle bu yanıtı düzenleme konusunda doyumsuz bir dürtüye sahip olanlar için endişe duyabilecekleri. Bak, bu cevabı yedi yıl önce yazdım . Evet, dil standartları değişiyor. Dünyayı gerçekten daha iyi hale getirmeniz gerekiyorsa, lütfen yeni cevabınızı ekleyin , ancak benimki olduğu gibi bırakın. Cevabın üzerinde benim adım olduğu için, sözlerimi de içermesini tercih ederim. Teşekkür ederim.


2
Bunun çalışması için # signal.h> ifadesini eklememiz gerektiğinden söz edelim!
kristianlm

13
statik bool volatile keepRunning = true;% 100 güvenli olmalıdır. Derleyici keepRunningbir kayıtta önbelleğe almakta serbesttir ve uçucu bunu önleyecektir. Uygulamada, while döngüsü en az bir satır içi olmayan işlevi çağırdığında, büyük olasılıkla değişken anahtar kelime olmadan da çalışabilir.
Johannes Overmann

2
@DirkEddelbuettel Düzeltildim, geliştirmelerimin orijinal niyetlerinizi daha fazla yansıtacağını düşündüm, eğer olmadıysa özür dilerim. Her neyse, daha büyük sorun, cevabınız yeterince genel olmaya çalıştığından ve IMO nedeniyle, eşzamansız kesimler altında da çalışan bir snippet sağlaması gerektiğidir: Orada ya sig_atomic_tda atomic_booltürleri kullanırım . Sadece özledim. Şimdi konuştuğumuzdan beri: son düzenlememi geri almamı ister misiniz? Zor duygular yok, bakış açınızdan mükemmel bir şekilde anlaşılabilir :)
Peter Varo

2
Bu çok daha iyi!
Dirk Eddelbuettel

2
@JohannesOvermann Nitpick yapmak istediğimden değil ama kesinlikle konuşursak, kodun herhangi bir satır içi olmayan işlevi çağırıp çağırmaması önemli değil, sadece bir bellek bariyerini geçerken derleyicinin önbelleğe alınmış bir değere güvenmesine izin verilmiyor. Muteksin kilitlenmesi / kilidinin açılması böyle bir bellek engeli olacaktır. Değişken statik olduğundan ve bu nedenle geçerli dosyanın dışında görünmediğinden, o değişkene bir başvuru göndermediğiniz sürece, derleyici bir işlevin değerini değiştiremeyeceğini varsayabilir. Bu yüzden burada her durumda uçucu olması şiddetle tavsiye edilir.
Mecki

47

Burayı kontrol et:

Not: Açıkçası, bu sadece bir CtrlCişleyicinin nasıl kurulacağını açıklayan basit bir örnektir , ancak her zaman olduğu gibi başka bir şeyi kırmamak için uyulması gereken kurallar vardır. Lütfen aşağıdaki yorumları okuyun.

Yukarıdaki örnek kod:

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

void     INThandler(int);

int  main(void)
{
     signal(SIGINT, INThandler);
     while (1)
          pause();
     return 0;
}

void  INThandler(int sig)
{
     char  c;

     signal(sig, SIG_IGN);
     printf("OUCH, did you hit Ctrl-C?\n"
            "Do you really want to quit? [y/n] ");
     c = getchar();
     if (c == 'y' || c == 'Y')
          exit(0);
     else
          signal(SIGINT, INThandler);
     getchar(); // Get new line character
}

2
@ Derrick Kabul, int mainuygun bir şeydir, ancak gccdiğer derleyiciler bunu 1990'lardan beri doğru çalışan programlara derler. Burada oldukça iyi açıklanmıştır: eskimo.com/~scs/readings/voidmain.960823.html - temelde bir "özellik", bunu böyle alıyorum.
icyrock.com

2
@ icyrock.com: Hepsi çok doğru (C'deki void main () ile ilgili olarak), ancak herkese açık bir şekilde yayın gönderirken, int main () kullanarak tartışmayı tamamen ana noktadan uzaklaştırmamak için tamamen önlemek de olasıdır.
Clifford

21
Bunda büyük bir kusur var. Bir sinyal işleyicinin içeriğinde printf'i güvenle kullanamazsınız. Bu eşzamansız sinyal güvenliğinin ihlalidir. Bunun nedeni printf'in yeniden girilmemesidir. Ctrl-C tuşuna basıldığında program printf kullanmanın ortasındaysa ve sinyal işleyiciniz programı aynı anda kullanmaya başlarsa ne olur? İpucu: Muhtemelen kırılacaktır. write ve fwrite bu bağlamda aynıdır.
Dylan Lukes

2
@ icyrock.com: Bir sinyal işleyicide karmaşık bir şey yapmak baş ağrısına neden olacaktır. Özellikle io sistemini kullanarak.
Martin York

2
@stacker Teşekkürler - Sanırım sonunda buna değer. Birisi gelecekte bu kod boyunca tökezliyorsa, sorunun konusuna bakılmaksızın mümkün olduğunca doğru olması daha iyidir.
icyrock.com

30

UN * X platformlarına ilişkin Zeyilname.

signal(2)GNU / Linux'taki man sayfasına göre , davranışı aşağıdakilerin davranışı signalkadar taşınabilir değildir sigaction:

Signal () davranışı UNIX sürümleri arasında değişiklik gösterir ve aynı zamanda tarihsel olarak farklı Linux sürümleri arasında da değişiklik gösterir. Kullanımından kaçının: bunun yerine sigaction (2) kullanın.

Sistem V'de, sistem sinyalin başka örneklerinin verilmesini engellemedi ve bir sinyalin verilmesi işleyiciyi varsayılana sıfırlar. BSD'de anlambilim değişti.

Dirk Eddelbuettel'in önceki cevabının aşağıdaki varyasyonu sigactionyerine şunları kullanıyor signal:

#include <signal.h>
#include <stdlib.h>

static bool keepRunning = true;

void intHandler(int) {
    keepRunning = false;
}

int main(int argc, char *argv[]) {
    struct sigaction act;
    act.sa_handler = intHandler;
    sigaction(SIGINT, &act, NULL);

    while (keepRunning) {
        // main loop
    }
}


14

Veya terminali şu şekilde ham moda koyabilirsiniz:

struct termios term;

term.c_iflag |= IGNBRK;
term.c_iflag &= ~(INLCR | ICRNL | IXON | IXOFF);
term.c_lflag &= ~(ICANON | ECHO | ECHOK | ECHOE | ECHONL | ISIG | IEXTEN);
term.c_cc[VMIN] = 1;
term.c_cc[VTIME] = 0;
tcsetattr(fileno(stdin), TCSANOW, &term);

Şimdi Ctrl+ Ctuş vuruşlarını kullanarak okumak mümkün olmalıdır fgetc(stdin). Normalde artık Ctrl+ Z, Ctrl+ Q, Ctrl+ Svb.


11

Bir tuzak kurun (bir işleyici ile birden fazla sinyal yakalayabilirsiniz):

sinyali (SIGQUIT, my_handler);
sinyali (SIGINT, my_handler);

Sinyali istediğiniz gibi kullanın, ancak sınırlamalara ve sorunlara dikkat edin:

void my_handler (int sig)
{
  / * Kodunuz buraya. * /
}

8

@Peter Varo Dirk'ün cevabını güncelledi, ancak Dirk değişikliği reddetti. İşte Peter'ın yeni cevabı:

Yukarıdaki pasaj doğru olmasına rağmen örneğin, mümkünse daha sonraki standartlar tarafından sağlanan daha modern tipler ve garantiler kullanılmalıdır. Bu nedenle, iş arayanlar için daha güvenli ve modern bir alternatif. ve uygun uygulama:

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

static volatile sig_atomic_t keep_running = 1;

static void sig_handler(int _)
{
    (void)_;
    keep_running = 0;
}

int main(void)
{
    signal(SIGINT, sig_handler);

    while (keep_running)
        puts("Still running...");

    puts("Stopped by signal `SIGINT'");
    return EXIT_SUCCESS;
}

C11 Standart: 7.14§2 başlık <signal.h>tipi ... ilan sig_atomic_tasenkron kesme mevcudiyetinde, bir atom bir varlık olarak erişilebilir bir nesnenin (muhtemelen uçucu nitelikli) tamsayı türüdür.

Ayrıca:

C11 Standardı: 7.14.1.1§5 Sinyal, abortveya raisefonksiyonunun çağrılmasından başka bir şey ortaya çıkarsa, sinyal işleyici statickilitsiz atomik nesne olmayan herhangi bir nesneye veya iş parçacığı saklama süresine atıfta bulunursa, davranış tanımsızdır. olarak bildirilen bir nesneye bir değer atayarak volatile sig_atomic_t...


Anlamıyorum (void)_;... Amacı ne? Derleyici kullanılmayan bir değişken hakkında uyarmaz mı?
Luis Paulo


Bu cevabı yeni buldum ve bu linki buraya yapıştırmak üzereydim! Teşekkürler :)
Luis Paulo

5

Mevcut cevaplarla ilgili olarak, sinyal işlemenin platforma bağlı olduğunu unutmayın. Örneğin Win32, POSIX işletim sistemlerinden çok daha az sinyal işliyor; buraya bakın . SIGINT Win32 üzerinde signal.h başlık dosyasında bildirilmiş olsa da, belgelerde beklediğiniz şeyi yapmayacağını açıklayan nota bakın.


3
#include<stdio.h>
#include<signal.h>
#include<unistd.h>

void sig_handler(int signo)
{
  if (signo == SIGINT)
    printf("received SIGINT\n");
}

int main(void)
{
  if (signal(SIGINT, sig_handler) == SIG_ERR)
  printf("\ncan't catch SIGINT\n");
  // A long long wait so that we can easily issue a signal to this process
  while(1) 
    sleep(1);
  return 0;
}

Sig_handler işlevi, iletilen bağımsız değişkenin değerinin SIGINT değerine eşit olup olmadığını denetler, ardından printf yürütülür.


Hiçbir zaman sinyal işleyicisinde G / Ç rutinlerini aramayın.
jwdonahue

2

Bu sadece çıkıştan önce yazdırın.

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

void sigint_handler(int);

int  main(void)
{
    signal(SIGINT, sigint_handler);

     while (1){
         pause();   
     }         
    return 0;
}

 void sigint_handler(int sig)
{
    /*do something*/
    printf("killing process %d\n",getpid());
    exit(0);
}

2
Asla bir sinyal tutucusundaki G / Ç'yi aramayın.
jwdonahue
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.