在 C 中捕获 Ctrl-C

如何在C中捕获Ctrl+C ?

243043 次浏览

用信号处理器。

下面是一个简单的例子,翻转在main()中使用的bool:

#include <signal.h>


static volatile int keepRunning = 1;


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


// ...


int main(void) {


signal(SIGINT, intHandler);


while (keepRunning) {
// ...

2017年6月编辑:给可能关心的人,特别是那些有贪得无厌的冲动编辑这个答案的人。看,我多年前就写下了这个答案。是的,语言标准在变化。如果你真的想让世界变得更好,请添加你的新答案,但保留我的。既然答案上有我的名字,我希望它也包含我的话。谢谢你!

检查:

  • http://www.csl.mtu.edu/cs4411.ck/www/NOTES/signal/install.html < a href = " http://www.csl.mtu.edu/cs4411.ck/www/NOTES/signal/install.html " > < / >

注意:显然,这是一个简单的例子,解释了只是如何设置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
}

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

signal (SIGQUIT, my_handler);
signal (SIGINT, my_handler);

不管你想怎么处理信号,但要注意局限性和陷阱:

void my_handler (int sig)
{
/* Your code here. */
}

或者你可以将终端设置为原始模式,就像这样:

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

现在应该可以使用fgetc(stdin)读取Ctrl+C键击。不过要注意使用这个方法,因为你不能像正常情况下那样Ctrl+ZCtrl+Ctrl+年代等。

对于现有的答案,请注意信号处理是依赖于平台的。例如,Win32处理的信号比POSIX操作系统少得多;在这里看到的。虽然SIGINT是在Win32的signals.h中声明的,但请参阅文档中的说明,说明它不会执行您可能期望的操作。

这只是在退出前打印。

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

关于UN*X平台的补充。

根据GNU/Linux上的signal(2)手册页,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
}
}
#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。

@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是对象的整数类型(可能是volatile-qualified),可以作为原子实体访问,即使是在异步中断存在的情况下。

此外:

C11标准:7.14.1.1§5如果信号不是调用abortraise函数的结果,如果信号处理程序引用任何具有static或线程存储持续时间且不是无锁原子对象的对象,则行为未定义,除非将值赋给声明为volatile sig_atomic_t的对象…