Ловіть Ctrl-C у C


158

Як один лов Ctrl+ Cу С?


5
Немає такого поняття, як ловити сигнали в С .... або принаймні так я думав, поки не прочитав стандарт 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в реєстрі, і непостійне запобіжить цьому. На практиці це, швидше за все, може працювати і без мінливого ключового слова, коли цикл while викликає хоча б одну не вбудовану функцію.
Йоганнес Оверманн

2
@DirkEddelbuettel Я виправлений, я вважав, що мої вдосконалення більше будуть відображати ваші первісні наміри, вибачте, якщо цього не сталося. У будь-якому разі, більша проблема полягає в тому, що ваша відповідь намагається бути достатньо загальною, і тому, що IMO, він повинен надати фрагмент, який також працює при асинхронних перериваннях: я б там використовував sig_atomic_tабо atomic_boolвводив. Я просто пропустив цю. Тепер, оскільки ми говоримо: чи хотіли б я відкатати останню редакцію? Ніяких важких почуттів там це було б цілком зрозуміло з вашої точки зору :)
Пітер Варо

2
Це набагато краще!
Дірк Еддельбюттель

2
@JohannesOvermann Не те, що я хочу робити nitpick, але строго кажучи, не має значення, чи викликає код будь-які не вбудовані функції чи ні, так як лише при перетині бар'єру пам'яті компілятору заборонено покладатися на кешоване значення. Блокування / розблокування мютексу було б таким бар'єром пам’яті. Оскільки змінна є статичною і тому не видно поза поточним файлом, компілятор може припустити, що функція ніколи не може змінити своє значення, якщо ви не передасте посилання на цю змінну. Тому в будь-якому випадку тут настійно рекомендується нестабільність.
Мецькі

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
}

2
@ Derrick Погодьтеся, int mainце належна річ, але gccінші компілятори складають це в правильно запущені програми з 1990-х. Пояснив тут досить добре: eskimo.com/~scs/readings/voidmain.960823.html - це в основному "функція", я вважаю це так.
icyrock.com

1
@ icyrock.com: Все дуже вірно (щодо void main () в C), але при публікації публічно, ймовірно, так само добре уникнути дебатів, використовуючи int main (), щоб не відволікти їх від головної точки.
Кліффорд

21
У цьому величезна вада. Ви не можете безпечно використовувати printf у вмісті обробника сигналу. Це порушення безпеки асинхронного сигналу. Це тому, що printf не є ретентом. Що станеться, якщо програма натиснула Ctrl-C в середині використання printf, і ваш обробник сигналу почне одночасно використовувати його? Підказка: це, швидше за все, зламається. write і fwrite - це те ж саме, що і в цьому контексті.
Ділан Лукес

2
@ icyrock.com: Якщо щось, що складно, в обробці сигналів, це призведе до головного болю. Особливо з використанням системи io.
Мартін Йорк

2
@stacker Спасибі - я думаю, що це варто того в кінці. Якщо хтось натрапляє на цей код у майбутньому, краще мати його максимально правильно, незалежно від теми питання.
icyrock.com

30

Додаток щодо платформ UN * X.

Згідно з signal(2)довідковою сторінкою в GNU / Linux, поведінка signalне настільки портативна, як поведінка sigaction:

Поведінка сигналу () варіюється в різних версіях 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);

Обробляйте сигнал як завгодно, але будьте в курсі обмежень та отриманих даних:

недійсний 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...


Я не розумію (void)_;... Яке його призначення? Це так, що компілятор не попереджає про невикористану змінну?
Луїс Пауло


Я щойно знайшов цю відповідь і збирався вставити це посилання тут! Дякую :)
Луїс Пауло

5

Щодо існуючих відповідей, зверніть увагу, що обробка сигналу залежить від платформи. Наприклад, Win32 обробляє набагато менше сигналів, ніж операційні системи POSIX; дивіться тут . Поки SIGINT оголошується в сигналі.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);
}

2
Ніколи не дзвоніть вводу / виводу в обробник сигналів.
jwdonahue
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.