在C中捕获Ctrl-C

Fel*_*dor 148 c signals

如何在C中捕获Ctrl+ C

Dir*_*tel 193

带信号处理程序.

这是一个简单的例子翻转bool用于main():

#include <signal.h>

static volatile int keepRunning = 1;

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

// ...

int main(void) {

   signal(SIGINT, intHandler);

   while (keepRunning) { 
      // ...
Run Code Online (Sandbox Code Playgroud)

2017年6月编辑:可能会引起关注,尤其是那些对编辑此答案抱怨无法满足的人.看,我年前写了这个答案.是的,语言标准会发生变化 如果你真的必须改善世界,请添加你的新答案,但保留原样.由于答案上有我的名字,我更喜欢它也包含我的话.谢谢.

  • static应该是`bool volatile keepRunning = true;`是100%安全的.编译器可以自由地将`keepRunning`缓存在寄存器中,而volatile会阻止这种情况.在实践中,当while循环调用至少一个非内联函数时,它很可能也可以在没有volatile关键字的情况下工作. (12认同)
  • “#include &lt;stdbool.h&gt;”怎么样?:) (3认同)
  • 让我们提一下,我们需要#include <signal.h>来实现这一点! (2认同)
  • @DirkEddelbuettel 我坚持纠正,我认为我的改进会更多地反映您的初衷,如果没有发生,我很抱歉。无论如何,更大的问题是,由于您的答案试图足够通用,并且因为 IMO 它应该提供一个也在异步中断下工作的片段:我会在那里使用 `sig_atomic_t` 或 `atomic_bool` 类型。我只是错过了那个。现在,既然我们正在谈论:您希望我回滚我的最新编辑吗?没有难受的感觉,从您的角度来看,这是完全可以理解的:) (2认同)
  • 那好多了! (2认同)
  • @JohannesOvermann 并不是说​​我想吹毛求疵,但严格来说,代码是否调用任何非内联函数都没有关系,因为只有在跨越内存屏障时,才允许编译器依赖缓存值。锁定/解锁互斥锁将是一个内存障碍。由于变量是静态的,因此在当前文件之外不可见,编译器可以安全地假设函数永远不会更改其值,除非您将该变量的引用传递给函数。因此,在任何情况下都强烈建议使用 volatile。 (2认同)

icy*_*com 46

点击这里:

注:显然,这是一个简单的例子解释只是如何建立一个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
}
Run Code Online (Sandbox Code Playgroud)

  • 这有一个巨大的缺陷.您无法在信号处理程序的内容中安全地使用printf.这违反了异步信号安全.这是因为printf不可重入.如果程序在按下Ctrl-C时正在使用printf,并且您的信号处理程序同时开始使用它会发生什么?提示:它可能会破裂.write和fwrite在此上下文中使用相同. (19认同)
  • @Derrick同意,`int main`是一个正确的东西,但是`gcc`和其他编译器自1990年以来将其编译成正确运行的程序.在这里解释得很好:http://www.eskimo.com/~scs/readings/voidmain.960823.html - 它基本上是一个"功能",我就这样认为. (2认同)
  • @icyrock.com:一切都非常正确(关于 C 中的 void main()),但是在公开发布时,最好使用 int main() 来完全避免辩论,以免分散注意力。 (2认同)
  • @ icyrock.com:在信号处理程序中做任何复杂的事情都会引起麻烦.特别是使用io系统. (2认同)
  • @stacker谢谢 - 我认为最终值得.如果有人在将来偶然发现这个代码,那么无论问题的主题如何,最好尽可能地使其正确. (2认同)

Fil*_* J. 29

关于UN*X平台的附录.

根据signal(2)GNU/Linux上的手册页,行为signal不像以下行为那样可移植sigaction:

signal()的行为在UNIX版本中各不相同,并且在不同版本的Linux上也有不同的历史变化.避免使用:改为使用sigaction(2).

在System 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
    }
}
Run Code Online (Sandbox Code Playgroud)

  • [需要`volatile sig_atomic_t`来定义明确](/sf/ask/2634180741/#comment62744362_37631288) (3认同)

MCC*_*CCS 15

@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;
}
Run Code Online (Sandbox Code Playgroud)

C11 标准:7.14§2标头<signal.h>声明了一个类型......sig_atomic_t它是一个对象的(可能是 volatile 限定的)整数类型,即使存在异步中断,也可以作为原子实体访问。

此外:

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

  • @LuisPaulo [对第二个问题是的。](/sf/ask/325336581/) (2认同)

Wal*_*ter 12

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

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);
Run Code Online (Sandbox Code Playgroud)

现在应该可以使用读取Ctrl+ C击键fgetc(stdin).请注意使用它,因为你不能正常使用Ctrl+ Z,Ctrl+ Q,Ctrl+ S等.


Pau*_*ham 11

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

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

处理您想要的信号,但要注意限制和陷阱:

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


Cli*_*ord 5

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