如果是命令行程序需要退出, CTRL+C是最直接的方法.

C语言如何处理CTRL+C

CTRL+C会向命令行进程发送中断信号, 在C语言的<signal.h>中的signal函数可以注册信号的处理函数.

signal函数的签名如下:

void (*signal(int sig, void (*func)(int)))(int);

比如, 我们要处理CTRL+C对应的SIGINT信号:

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

void sigHandle(int sig) {
    switch(sig) {
    case SIGINT:
        printf("sigHandle: %d, SIGINT\n", sig);
        break;
    default:
        printf("sigHandle: %d, OTHER\n", sig);
        break;
    }
    exit(1);
}

int main() {
    signal(SIGINT, sigHandle);
    for(;;) {}
    return 0;
}

编译并运行程序后会进入死循环, 按CTRL+C强制退出会看到以下的输出:

sigHandle: 2, SIGINT

当然, 直接从进程管理杀死程序就没办法收到信号的.

<signal.h>中除了signal函数, 还有一个raise函数用于生成信号:

int raise(int sig);

我们在sigHandle截获信号之后如果想重新恢复信号, 可以使用raise函数. 但是, 要注意不要导致无穷递归signal/raise调用.

Go语言如何处理CTRL+C

Go语言也有类似的函数signal.Notify(在os/signal包中), 可以过滤信号.

这是signal.Notify自带的例子:

// Set up channel on which to send signal notifications.
// We must use a buffered channel or risk missing the signal
// if we're not ready to receive when the signal is sent.
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, os.Kill)

// Block until a signal is received.
s := <-c
fmt.Println("Got signal:", s)

signal.Notify会将用户关注的信号转发到信道c, 信道c不能是阻塞的. 如果信道是缓冲不足的话, 可能会丢失信号. 如果我们不再次转发信号, 设置为1个缓冲大小就可以了.

signal.Notify从第二个参数起是可变参数的, 用于指定要过滤的信号. 如果不指定第二个参数, 则默认是过滤全部的信号.

信号的定义一般在syscall. syscall包是系统相关的, 不同的操作系统信号可能有差异. 不过syscall.SIGINTsyscall.SIGKILL各个系统是一致的, 分别对应os.Interruptos.Kill.

下面是Go语言版完整的例子:

package main

import (
    "fmt"
    "os"
    "os/signal"
)

func main() {
    c := make(chan os.Signal, 1)
    signal.Notify(c, os.Interrupt, os.Kill)

    s := <-c
    fmt.Println("Got signal:", s)
}

go run signal.go运行后会进入死循环, 按CTRL+C强制退出会看到以下的输出:

Got signal: interrupt

当然, 直接从进程管理杀死程序就没办法收到信号的.

如果要恢复信号, 调用s.Signal(). 如果要停止信号的过滤, 调用signal.Stop(c).