在C中捕获Ctrl-C


158

如何在C中抓住Ctrl+ C


5
根本没有C捕获信号之类的东西……或者至少在我读完C99标准之前我一直以为。事实证明,在C中定义了信号处理,但是Ctrl-C并未强制产生任何特定信号或完全没有信号。根据您的平台,这可能是不可能的。
JeremyP

1
信号处理主要取决于实现。在* nix平台上,使用<signal.h>,如果您使用的是OSX,则可以利用GCD使事情变得更容易〜。
迪伦·卢克斯

Answers:


205

带有信号处理程序。

这是一个简单的示例,bool用于翻转中的main()

#include <signal.h>

static volatile int keepRunning = 1;

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

// ...

int main(void) {

   signal(SIGINT, intHandler);

   while (keepRunning) { 
      // ...

编辑于2017年6月:可能关注的对象,尤其是那些渴望编辑此答案的人。看,我年前写了这个答案。是的,语言标准在变化。如果您真的必须改善世界,请添加您的新答案,但不要更改。因为答案上面有我的名字,所以我也希望它也包含我的话。谢谢。


2
让我们提到我们需要#include <signal.h>才能起作用!
kristianlm 2011年

13
静态应该是bool volatile keepRunning = true;100%安全的。编译器可以自由缓存keepRunning在寄存器中,而volatile将阻止这种情况。实际上,当while循环调用至少一个非内联函数时,很可能在没有volatile关键字的情况下也可以工作。
Johannes Overmann

2
@DirkEddelbuettel我已纠正,我认为我的改进会更多地反映出您的初衷,对不起,如果没有发生。无论如何,更大的问题是,由于您的答案试图足够通用,并且因为IMO应该提供一个在异步中断下也能正常工作的代码段,所以我会在其中使用sig_atomic_tatomic_bool键入。我只是想念那个。现在,由于我们正在谈论:您是否希望我回滚我的最新编辑?从您的角度来看,没有难过的感觉是完全可以理解的:)
彼得·瓦罗

2
那好多了!
Dirk Eddelbuettel

2
@JohannesOvermann并不是我想挑剔,但严格来说,代码是否调用任何非内联函数都没有关系,因为只有在越过内存障碍时,编译器才不允许依赖缓存的值。锁定/解锁互斥锁将成为这种存储障碍。由于变量是静态的,因此在当前文件外部不可见,因此编译器可以放心地假设一个函数永远无法更改其值,除非您将对该变量的引用传递给该函数。因此,强烈建议在任何情况下都使用易失性。
麦基'18

47

在这里检查:

注意:显然,这是一个简单的示例,说明如何设置CtrlC处理程序,但是与往常一样,为了不破坏其他内容,需要遵守一些规则。请阅读下面的评论。

上面的示例代码:

#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 Agree int main是正确的做法,但gcc自1990年代以来,其他编译器将此编译为可正确运行的程序。在这里很好的解释了:eskimo.com/~scs/readings/voidmain.960823.html-它基本上是一个“功能”,我这样认为。
icyrock.com 2010年

1
@ icyrock.com:非常正确(关于C语言中的void main()),但是在公开发布时,最好也使用int main()避免争论,以免分散注意力。
克利福德,2010年

21
这有一个巨大的缺陷。您不能在信号处理程序的内容中安全地使用printf。这违反了异步信号安全性。这是因为printf不是可重入的。如果在按Ctrl-C的同时程序处于使用printf的中间,并且信号处理程序同时开始使用它,会发生什么情况?提示:可能会破裂。write和fwrite在此上下文中使用相同。
迪伦·卢克斯

2
@ icyrock.com:在信号处理程序中做任何复杂的事情都会引起头痛。特别是使用io系统。
马丁·约克

2
@stacker谢谢-我认为这是值得的。如果将来有人偶然发现此代码,则不管问题的主题如何,最好使其尽可能正确。
icyrock.com 2010年

30

有关UN * X平台的附录。

根据signal(2)GNU / Linux上的手册页,的行为signal不如的行为可移植sigaction

signal()的行为在UNIX版本之间有所不同,并且在历史上在Linux的不同版本中也有所不同。避免使用它:改为使用sigaction(2)。

在系统V上,系统未阻止其他信号实例的传递,并且信号的传递会将处理程序重置为默认实例。在BSD中,语义发生了变化。

以下是Dirk Eddelbuettel先前回答的变体,sigaction而不是使用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

或者,您可以将终端置于原始模式,如下所示:

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);

现在应该可以使用读取Ctrl+ C击键fgetc(stdin)。不过要小心使用它,因为您不能再像往常一样Ctrl+ ZCtrl+ QCtrl+ S等等。


11

设置一个陷阱(您可以使用一个处理程序来捕获多个信号):

信号(SIGQUIT,my_handler);
信号(SIGINT,my_handler);

随意处理信号,但要注意限制和陷阱:

无效的my_handler(int sig)
{
  / *您的代码在这里。* /
}

8

@Peter Varo更新了Dirk的答案,但Dirk拒绝了更改。这是彼得的新答案:

尽管以上代码段是正确的 例如,如果可能的话,应该使用更高标准的类型和更高版本所提供的保证。因此,对于那些寻求 符合要求的实现:

#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标准:7.14§2标头<signal.h>声明一个类型... sig_atomic_t,它是对象的(可能是易失性限定)整数类型,即使存在异步中断,该对象也可以作为原子实体进行访问。

此外:

C11标准:7.14.1.1§5如果信号不是通过调用abortraise函数产生的,则信号处理程序引用的对象具有非static线程锁对象或线程存储持续时间不是对象时,则行为未定义而不是通过为声明为volatile sig_atomic_t... 的对象赋值


我不理解(void)_;...的目的是什么?这样编译器就不会警告未使用的变量吗?
Luis Paulo


我刚刚找到了这个答案,并打算将这个链接粘贴到这里!谢谢:)
路易斯·保罗

5

关于现有答案,请注意信号处理取决于平台。例如,Win32处理的信号要比POSIX操作系统少得多。看这里。尽管在Win32上的signal.h中声明了SIGINT,但请参阅文档中的说明,该说明将说明它不会达到您的期望。


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检查所传递的参数的值是否等于SIGINT,然后执行printf。


切勿在信号处理程序中调用I / O例程。
jwdonahue

2

这只是在退出前打印。

#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
切勿在信号处理程序中调用I / O。
jwdonahue
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.