Pegar Ctrl-C em C


158

Como se pega Ctrl+ Cem C?


5
Não existe sinal de captura em C .... ou pelo menos assim eu pensei até ler o padrão C99. Acontece que há manipulação de sinal definida em C, mas Ctrl-C não é obrigado a produzir nenhum sinal específico ou sinal. Dependendo da sua plataforma, isso pode ser impossível.
JeremyP

1
A manipulação de sinais depende principalmente da implementação. Nas plataformas * nix, use <signal.h> e, se você estiver no OSX, poderá aproveitar o GCD para tornar as coisas ainda mais fáceis ~.
Dylan Lukes

Respostas:


205

Com um manipulador de sinal.

Aqui está um exemplo simples lançando um boolusado em main():

#include <signal.h>

static volatile int keepRunning = 1;

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

// ...

int main(void) {

   signal(SIGINT, intHandler);

   while (keepRunning) { 
      // ...

Editar em junho de 2017 : a quem possa interessar, principalmente aqueles com um desejo insaciável de editar esta resposta. Olha, eu escrevi esta resposta há sete anos. Sim, os padrões de idioma mudam. Se você realmente deve melhorar o mundo, adicione sua nova resposta, mas deixe a minha como está. Como a resposta tem meu nome, eu prefiro que contenha minhas palavras também. Obrigado.


2
Vamos mencionar que precisamos # incluir <signal.h> para que isso funcione!
precisa saber é o seguinte

13
estático Deve bool volatile keepRunning = true;ser 100% seguro. O compilador é livre para armazenar keepRunningem cache em um registro e o volátil evitará isso. Na prática, é provável que também funcione sem a palavra-chave volátil quando o loop while chama pelo menos uma função não-inline.
Johannes Overmann

2
@DirkEddelbuettel Estou corrigido, pensei que minhas melhorias refletiriam mais suas intenções originais, desculpe se isso não aconteceu. Enfim, o maior problema é que, já que sua resposta tenta ser genérica o suficiente, e como a IMO deve fornecer um trecho que também esteja funcionando sob interrupções assíncronas: eu usaria sig_atomic_tou atomic_booldigitaria lá. Eu apenas perdi essa. Agora, já que estamos conversando: você gostaria que eu revelasse minha edição mais recente? Sem ressentimentos não seria perfeitamente compreensível a partir de seu ponto de vista :)
Peter Varo

2
Isso é muito melhor!
Dirk Eddelbuettel 19/05

2
@JohannesOvermann Não que eu queira escolher, mas estritamente falando, não importa se o código chama funções não-in-line ou não, pois somente ao atravessar uma barreira de memória, o compilador não pode confiar em um valor em cache. Bloquear / desbloquear um mutex seria uma barreira de memória. Como a variável é estática e, portanto, não é visível fora do arquivo atual, o compilador pode assumir que uma função nunca pode alterar seu valor, a menos que você passe uma referência a essa variável para a função. Tão volátil é fortemente recomendado aqui em qualquer caso.
Mecki

47

Verifique aqui:

Nota: Obviamente, este é um exemplo simples que explica exatamente como configurar um CtrlCmanipulador, mas como sempre, existem regras que precisam ser obedecidas para não quebrar outra coisa. Por favor, leia os comentários abaixo.

O código de exemplo acima:

#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 Concord, int mainé uma coisa apropriada, mas gcce outros compiladores compilam isso para programas em execução correta desde os anos 90. Explicou muito bem aqui: eskimo.com/~scs/readings/voidmain.960823.html - é basicamente um "recurso", eu entendo assim.
Icyrock.com

1
@ icyrock.com: Tudo muito verdadeiro (em relação ao void main () em C), mas, ao postar publicamente, é provável que evite o debate usando int main () para não desviar o foco do ponto principal.
Clifford

21
Há uma enorme falha nisso. Você não pode usar printf com segurança no conteúdo de um manipulador de sinal. É uma violação da segurança do sinal assíncrono. Isso ocorre porque printf não é reentrante. O que acontece se o programa estava no meio do uso de printf quando Ctrl-C foi pressionado e o manipulador de sinais começa a usá-lo ao mesmo tempo? Dica: Provavelmente irá quebrar. write e fwrite são iguais para usar neste contexto.
Dylan Lukes

2
@ icyrock.com: Fazer algo complicado em um manipulador de sinais causará dores de cabeça. Especialmente usando o sistema io.
Martin York

2
@stacker Obrigado - acho que vale a pena no final. Se alguém se deparar com esse código no futuro, é melhor acertar o máximo possível, independentemente do tópico da pergunta.
Icyrock.com

30

Adendo referente às plataformas UN * X.

De acordo com a signal(2)página do manual no GNU / Linux, o comportamento de signalnão é tão portátil quanto o comportamento de sigaction:

O comportamento do sinal () varia entre as versões UNIX e também variou historicamente entre diferentes versões do Linux. Evite seu uso: use sigaction (2).

No Sistema V, o sistema não bloqueou a entrega de outras instâncias do sinal e a entrega de um sinal redefiniria o manipulador para o padrão. No BSD, a semântica mudou.

A seguinte variação da resposta anterior de Dirk Eddelbuettel usa em sigactionvez de 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

Ou você pode colocar o terminal no modo bruto, assim:

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);

Agora deve ser possível ler Ctrl+ Cpressionamentos de teclas usando fgetc(stdin). Cuidado com isso, porque você não pode Ctrl+Z , Ctrl+ Q, Ctrl+ S, etc.


11

Configure uma interceptação (você pode interceptar vários sinais com um manipulador):

sinal (SIGQUIT, my_handler);
sinal (SIGINT, my_handler);

Manipule o sinal da maneira que desejar, mas esteja ciente das limitações e dicas:

void my_handler (int sig)
{
  / * Seu código aqui. * /
}

8

@ Peter Varo atualizou a resposta de Dirk, mas Dirk rejeitou a alteração. Aqui está a nova resposta de Peter:

Embora o snippet acima seja um correto Por exemplo, deve-se usar os tipos e garantias mais modernos fornecidos pelos padrões posteriores, se possível. Portanto, aqui está uma alternativa mais segura e moderna para aqueles que buscam o e implementação conforme:

#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;
}

Padrão C11: 7.14§2 O cabeçalho <signal.h>declara um tipo ... sig_atomic_tque é o tipo inteiro (possivelmente qualificado para um volátil) de um objeto que pode ser acessado como uma entidade atômica, mesmo na presença de interrupções assíncronas.

Além disso:

C11 Standard: 7.14.1.1§5 Se o sinal ocorrer como resultado da chamada doabortraise função ou , o comportamento será indefinido se o manipulador de sinal se referir a qualquer objeto com staticou duração de armazenamento de encadeamento que não seja um objeto atômico livre de bloqueios do que atribuir um valor a um objeto declarado como volatile sig_atomic_t...


Eu não entendo o (void)_;... Qual é o seu propósito? É assim que o compilador não avisa sobre uma variável não utilizada?
Luis Paulo


Acabei de encontrar essa resposta e estava prestes a colar esse link aqui! Obrigado :)
Luis Paulo

5

Em relação às respostas existentes, observe que o tratamento do sinal depende da plataforma. O Win32, por exemplo, lida com muito menos sinais que os sistemas operacionais POSIX; veja aqui . Enquanto o SIGINT é declarado em signs.h no Win32, consulte a nota na documentação que explica que ele não fará o que você poderia esperar.


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;
}

A função sig_handler verifica se o valor do argumento passado é igual ao SIGINT, então o printf é executado.


Nunca chame rotinas de E / S em um manipulador de sinal.
precisa saber é o seguinte

2

Isso é impresso antes da saída.

#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
Nunca chame E / S em um manipulador de sinal.
precisa saber é o seguinte
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.