Поймать Ctrl-C в C

158

Как можно поймать Ctrl+ Cв C?

Feldor
источник
5
Нет такой вещи, как перехват сигналов в C .... или, по крайней мере, так я думал, пока не прочитал стандарт C99. Оказывается, в C определена обработка сигналов, но Ctrl-C не обязан создавать какой-либо конкретный сигнал или сигнал вообще. В зависимости от вашей платформы это может быть невозможно.
JeremyP
1
Обработка сигналов в основном зависит от реализации. На платформах * nix используйте <signal.h>, и, если вы работаете в OSX, вы можете воспользоваться GCD, чтобы сделать вещи еще проще ~.
Дилан Люк

Ответы:

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 года : кому это может касаться, особенно тем, у кого есть ненасытное желание отредактировать этот ответ. Смотри, я написал этот ответ семь лет назад. Да, языковые стандарты меняются. Если вы действительно должны улучшить мир, пожалуйста, добавьте свой новый ответ, но оставьте мой как есть. Поскольку в ответе указано мое имя, я бы предпочел, чтобы в нем также содержались мои слова. Спасибо.

Дирк Эддельбюттель
источник
2
Давайте отметим, что нам нужно #include <signal.h>, чтобы это работало!
kristianlm
13
статический Это должно быть bool volatile keepRunning = true;на 100% безопасно. Компилятор может свободно кэшировать данные keepRunningв регистре, и volatile предотвращает это. На практике это, скорее всего, также может работать без ключевого слова volatile, когда цикл while вызывает хотя бы одну не встроенную функцию.
Йоханнес Оверманн
2
@DirkEddelbuettel Я исправлен, я думал, что мои улучшения будут отражать ваши первоначальные намерения больше, извините, если этого не произошло. В любом случае, большая проблема заключается в том, что, поскольку ваш ответ пытается быть достаточно универсальным, а IMO должен предоставить фрагмент, который также работает при асинхронных прерываниях: я бы либо использовал, sig_atomic_tлибо atomic_boolнапечатал там. Я просто пропустил это. Теперь, поскольку мы говорим: хотите, чтобы я откатил мою последнюю правку? Никаких обид там нет, это было бы совершенно понятно с вашей точки зрения :)
Питер Варо
2
Это намного лучше!
Дирк Эддельбюттель
2
@JohannesOvermann Не то чтобы я хотел придираться, но, строго говоря, не имеет значения, вызывает ли код какие-либо не встроенные функции или нет, поскольку только при пересечении барьера памяти компилятору не разрешается полагаться на кэшированное значение. Блокировка / разблокировка мьютекса была бы таким барьером памяти. Поскольку переменная является статической и, следовательно, не видна за пределами текущего файла, компилятор может предположить, что функция никогда не сможет изменить свое значение, если вы не передадите ссылку на эту переменную в функцию. Настоятельно рекомендуется в любом случае здесь изменчиво.
Меки
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
}
icyrock.com
источник
2
@ Деррик Согласитесь, int mainэто правильная вещь, но gccи другие компиляторы компилируют это в правильно работающие программы с 1990-х годов. Объяснено довольно хорошо здесь: eskimo.com/~scs/readings/voidmain.960823.html - это в основном «фича», я так понимаю.
icyrock.com
1
@ icyrock.com: Все очень верно (в отношении void main () в C), но при публичном размещении, вероятно, также следует избегать дискуссий вообще, используя int main (), чтобы не отвлекать внимание от основного.
Клиффорд
21
В этом есть огромный недостаток. Вы не можете безопасно использовать printf в содержимом обработчика сигналов. Это нарушение безопасности асинхронного сигнала. Это потому, что printf не реентерабелен. Что произойдет, если программа была в процессе использования printf при нажатии Ctrl-C, и ваш обработчик сигналов начинает использовать ее одновременно? Подсказка: скорее всего, сломается. write и fwrite одинаковы для использования в этом контексте.
Дилан Люк
2
@ icyrock.com: Выполнение чего-либо сложного в обработчике сигналов может вызвать головную боль. Особенно с использованием системы IO.
Мартин Йорк,
2
@ Stacker Спасибо - я думаю, это того стоит. Если кто-то случайно наткнется на этот код в будущем, лучше сделать его максимально правильным, независимо от темы вопроса.
icyrock.com
30

Приложение относительно платформ UN * X.

Согласно signal(2)справочной странице по GNU / Linux, поведение signalне так переносимо, как sigaction:

Поведение signal () варьируется в разных версиях UNIX, а также исторически различалось в разных версиях Linux. Избегайте его использования: используйте вместо этого sigaction (2).

В системе V система не блокировала доставку дальнейших экземпляров сигнала, и доставка сигнала сбрасывала обработчик к стандартному. В BSD семантика изменилась.

Следующий вариант предыдущего ответа Дирка Эддельбюттеля использует 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+ Z, Ctrl+ Q, Ctrl+ S, и т. Д. Как обычно больше.

Вальтер
источник
11

Установите ловушку (вы можете перехватить несколько сигналов одним обработчиком):

сигнал (SIGQUIT, my_handler);
сигнал (SIGINT, my_handler);

Обрабатывайте сигнал как хотите, но помните об ограничениях и ошибках:

void my_handler (int sig)
{
  / * Ваш код здесь. * /
}
Пол Бекингем
источник
8

@Peter Varo обновил ответ Дирка, но Дирк отклонил изменение. Вот новый ответ Питера:

Хотя приведенный выше фрагмент является правильным Например, следует использовать более современные типы и гарантии, предоставляемые более поздними стандартами, если это возможно. Поэтому здесь есть более безопасная и современная альтернатива для тех, кто ищет и соответствующая реализация:

#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 Если сигнал возникает не в результате вызова функции abortили raise, поведение не определено, если обработчик сигнала ссылается на какой-либо объект с staticдлительностью хранения или потока, который не является атомарным объектом без блокировки, другим чем путем присвоения значения объекту, объявленному как volatile sig_atomic_t...

MCCCS
источник
Я не понимаю (void)_;... Какова его цель? Это так, что компилятор не предупреждает о неиспользуемой переменной?
Луис Паулу
Я только что нашел этот ответ и собирался вставить эту ссылку здесь! Спасибо :)
Луис Пауло
5

Что касается существующих ответов, обратите внимание, что обработка сигналов зависит от платформы. Например, Win32 обрабатывает гораздо меньше сигналов, чем операционные системы POSIX; смотрите здесь . Хотя SIGINT объявлен в signal.h на Win32, см. Примечание в документации, объясняющее, что он не будет делать то, что вы ожидаете.

Клиффорд
источник
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.

Любовь бисария
источник
Никогда не вызывайте процедуры ввода / вывода в обработчике сигналов.
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);
}
alemol
источник
2
Никогда не вызывайте ввод / вывод в обработчике сигнала.
Jwdonahue