C에서 Ctrl-C를 잡아라


158

C에서 어떻게 Ctrl+ C를 잡을 까요?


5
C.에서 신호를 잡는 것과 같은 것은 없습니다. 적어도 C99 표준을 읽을 때까지 생각했습니다. C에 정의 된 신호 처리가 있지만 Ctrl-C는 특정 신호 또는 신호를 생성하도록 의무화되지 않습니다. 플랫폼에 따라 불가능할 수도 있습니다.
JeremyP

1
신호 처리는 대부분 구현에 따라 다릅니다. * nix 플랫폼에서는 <signal.h>를 사용하십시오. OSX를 사용하는 경우 GCD를 활용하여 작업을보다 쉽게 ​​수행 할 수 있습니다.
Dylan Lukes

답변:


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 년 6 월 편집 : 특히이 답변을 편집 할 수없는 욕구가있는 사람. 이 대답은 7 년 전에 썼습니다 . 예, 언어 표준이 변경되었습니다. 세상을 더 나아 져야한다면, 새로운 답변을 추가 하되 그대로 내버려 두십시오. 대답에는 내 이름이 있으므로 단어도 포함하는 것이 좋습니다. 감사합니다.


2
이것이 작동하려면 #include <signal.h>가 필요하다고 언급 해 봅시다!
kristianlm

13
static bool volatile keepRunning = true;100 % 안전 해야합니다 . 컴파일러는 keepRunning레지스터에 자유롭게 캐시 할 수 있으며 휘발성으로 인해이를 방지 할 수 있습니다. 실제로 while 루프가 하나 이상의 비 인라인 함수를 호출 할 때 volatile 키워드없이 작동 할 수도 있습니다.
Johannes Overmann

2
@ DirkEddelbuettel 나는 정정했다, 나는 내 개선 사항이 원래 의도를 더 반영 할 것이라고 생각했다. 어쨌든, 더 큰 문제는 귀하의 답변이 충분히 포괄적이기 때문에 IMO 때문에 비동기 인터럽트에서도 작동하는 스 니펫을 제공해야한다는 것입니다. 사용 sig_atomic_t하거나 입력하십시오 atomic_bool. 방금 그거보고 싶었어 이제 우리가 이야기하고 있기 때문에 : 최신 편집을 롤백하겠습니까? 거기에서 어려운 감정은 당신의 관점에서 완벽하게 이해할 수 있습니다 :)
Peter Varo

2
훨씬 낫다!
Dirk Eddelbuettel

2
@JohannesOvermann nitpick하고 싶지는 않지만 엄격하게 말하면 메모리 장벽을 넘을 때 컴파일러가 캐시 된 값에 의존 할 수없는 것처럼 코드가 인라인이 아닌 함수를 호출하는지 여부는 중요하지 않습니다. 뮤텍스 잠금 / 잠금 해제는 그러한 메모리 장벽입니다. 변수가 정적이므로 현재 파일 외부에 표시되지 않으므로 컴파일러는 해당 변수에 대한 참조를 함수에 전달하지 않으면 함수가 값을 변경할 수 없다고 가정하는 것이 안전합니다. 따라서 어떤 경우에도 휘발성이 좋습니다.
Mecki

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 Agree int main는 올바른 일이지만 gcc다른 컴파일러는 이것을 1990 년대부터 올바르게 실행되는 프로그램으로 컴파일합니다. 여기에 꽤 잘 설명되어 있습니다 : eskimo.com/~scs/readings/voidmain.960823.html- 기본적으로 "기능"입니다.
icyrock.com

1
@ icyrock.com : C의 void main ()에 관해서는 모두 사실이지만 공개적으로 게시 할 때 int main ()을 사용하여 논쟁을 피하는 것이 아마도 주요 지점에서 산만하지 않도록하는 것이 좋습니다.
Clifford

21
이것에는 큰 결함이 있습니다. 신호 처리기의 내용 내에서 printf를 안전하게 사용할 수 없습니다. 비동기 신호 안전을 위반합니다. printf가 재진입되지 않기 때문입니다. Ctrl-C를 눌렀을 때 프로그램이 printf를 사용하는 도중에 신호 처리기가 동시에 사용을 시작하면 어떻게됩니까? 힌트 : 깨질 것입니다. write와 fwrite는이 문맥에서 사용하는 것과 같습니다.
Dylan Lukes

2
@ icyrock.com : 신호 처리기에서 복잡한 작업을 수행하면 두통이 발생합니다. 특히 io 시스템을 사용합니다.
Martin York

2
@ stacker 감사합니다-나는 그것이 가치가 있다고 생각합니다. 미래에 누군가 가이 코드를 우연히 발견한다면 질문의 주제에 관계없이 가능한 한 올바르게 작성하는 것이 좋습니다.
icyrock.com

30

UN * X 플랫폼에 관한 부록.

signal(2)GNU / Linux 의 매뉴얼 페이지에 따르면 , signal동작은 sigaction다음 과 같이 이식성이 없습니다 .

signal ()의 동작은 UNIX 버전에 따라 다르며 역사적으로 Linux 버전에 따라 다릅니다. 사용을 피하십시오 : 대신 sigaction (2)를 사용하십시오.

System V에서 시스템은 신호의 추가 인스턴스 전달을 차단하지 않았으며 신호 전달은 핸들러를 기본 인스턴스로 재설정합니다. BSD에서 의미가 변경되었습니다.

Dirk Eddelbuettel의 이전 답변에 대한 다음 변형은 다음 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는 Dirk의 답변을 업데이트했지만 Dirk는 변경을 거부했습니다. Peter의 새로운 답변은 다음과 같습니다.

위의 코드 조각은 맞지만 예를 들어, 가능한 경우 이후 표준에서 제공하는보다 현대적인 유형과 보증을 사용해야합니다. 따라서 여기를 찾고있는 사람들을위한 더 안전하고 현대적인 대안이 있습니다. 적합한 구현 :

#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§5abort 또는 raise함수 호출의 결과가 아닌 다른 신호가 발생하는 경우 신호 처리기 static가 잠금없는 원자 객체가 아닌 다른 객체 또는 스레드 저장 기간을 참조하는 경우 동작이 정의되지 않습니다. 로 선언 된 객체에 값을 할당하는 것보다 volatile sig_atomic_t...


나는 이해하지 못한다 (void)_;... 그 목적은 무엇입니까? 컴파일러가 사용하지 않는 변수에 대해 경고하지 않습니까?
Luis Paulo


방금 그 대답을 찾았고 여기에 해당 링크를 붙여 넣으려고했습니다! 감사합니다 :)
Luis Paulo

5

기존 답변과 관련하여 신호 처리는 플랫폼에 따라 다릅니다. 예를 들어 Win32는 POSIX 운영 체제보다 훨씬 적은 신호를 처리합니다. 여기를 참조하십시오 . SIGINT가 Win32의 signals.h에 선언되어 있지만 설명서의 참고 사항을 참조하십시오.


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가 실행됩니다.


신호 처리기에서 I / O 루틴을 호출하지 마십시오.
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
신호 처리기에서 I / O를 호출하지 마십시오.
jwdonahue
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.