应使用 sigaction() 而非 signal() 捕获 sigint,因其行为可预测、支持 sa_restart 和信号掩码控制;处理函数中仅允许操作 sig_atomic_t 变量,避免调用非异步信号安全函数。

直接用 signal() 注册处理函数能捕获 SIGINT,但不推荐在生产环境这么干——它不可靠、不跨平台、且容易引发未定义行为。
为什么 signal() 不够用
Linux 下 signal() 是 BSD 风格的简化封装,行为受系统实现影响大。比如它可能自动重置为 SIG_DFL(即再次收到 SIGINT 会直接终止),也可能在某些系统调用被中断后不恢复,导致 sleep() 或 read() 突然返回 EINTR 而不重试。
-
signal()不保证信号处理期间屏蔽同类型信号,可能重入(虽然SIGINT通常不会快速连发,但逻辑上不安全) - 它不提供对信号掩码(
sa_mask)、是否重启被中断系统调用(SA_RESTART)等关键控制 - POSIX 标准明确指出:
signal()的语义在不同系统上不一致,应优先使用sigaction()
用 sigaction() 安全捕获 SIGINT
sigaction() 是 POSIX 标准接口,行为可预测,支持细粒度控制。核心是填一个 struct sigaction 并传给系统。
- 设置
sa_handler指向你的处理函数,别用sa_sigaction(那是带额外参数的高级用法,普通场景不需要) - 把
sa_flags设为SA_RESTART,这样被SIGINT中断的sleep()、read()等会自动重试,避免逻辑跳变 -
sa_mask可以留空(sigemptyset(&sa.sa_mask)),除非你明确需要在处理期间屏蔽其他信号 - 务必在注册前用
sigemptyset()初始化sa_mask,否则未初始化内存可能造成随机屏蔽
#include <csignal>
#include <iostream>
#include <unistd.h>
volatile sig_atomic_t keep_running = 1;
void sigint_handler(int) {
keep_running = 0;
}
int main() {
struct sigaction sa;
sa.sa_handler = sigint_handler;
sa.sa_flags = SA_RESTART;
sigemptyset(&sa.sa_mask);
if (sigaction(SIGINT, &sa, nullptr) == -1) {
std::cerr
<h3>处理函数里只能干“安全”的事</h3>
<p>信号处理函数运行在异步上下文,不能调用大多数标准库函数——比如 <code>std::cout</code>、<code>malloc</code>、<code>printf</code> 都不是异步信号安全的,可能破坏堆或流状态。</p>
<ul>
<li>只操作 <code>sig_atomic_t</code> 类型的全局变量(如上面的 <code>keep_running</code>),这是唯一保证原子读写的类型</li>
<li>避免任何 I/O、内存分配、锁操作、浮点运算</li>
<li>不要调用 <code>exit()</code> 或 <code>abort()</code> ——它们本身不安全;改用主循环轮询标志位后退出</li>
<li>如果真要记录日志,用 <code>write()</code> 系统调用(它属于异步信号安全函数族)写到 <code>STDERR_FILENO</code>
</li>
</ul>
<h3>多线程程序要额外注意信号屏蔽</h3>
<p>在多线程环境下,<code>SIGINT</code> 默认会发送给整个进程的任意一个未屏蔽该信号的线程,无法保证由哪个线程执行处理函数。</p>
<ul>
<li>主线程注册 <code>sigaction()</code> 后,应调用 <code>pthread_sigmask()</code> 把 <code>SIGINT</code> 屏蔽掉,防止其他线程意外收到</li>
<li>如果希望统一由某个工作线程处理,可让该线程调用 <code>sigwait()</code> 主动等待,其他线程保持屏蔽</li>
<li>不要在多个线程里分别调用 <code>sigaction()</code> 注册同一信号——行为未定义</li>
</ul>
<p>最常被忽略的一点:信号处理函数不是“回调”,它是抢占式插入的异步执行体。哪怕你只写了一行赋值,也要确认变量类型是 <code>sig_atomic_t</code>,且主逻辑真的在轮询它——否则 Ctrl+C 按下去,程序看似响应了,其实只是运气好没崩而已。</p></unistd.h></iostream></csignal>C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











