จับ Ctrl-C ใน C


158

หนึ่งจับCtrl+ Cใน 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 : ผู้ที่อาจกังวลโดยเฉพาะผู้ที่มีความต้องการที่ไม่รู้จักพอที่จะแก้ไขคำตอบนี้ ดูฉันเขียนคำตอบนี้เจ็ดปีที่ผ่านมา ใช่มาตรฐานภาษามีการเปลี่ยนแปลง หากคุณจริงๆต้องดีกว่าโลกโปรดเพิ่มคำตอบใหม่ของคุณแต่ปล่อยเหมืองที่เป็นอยู่ เนื่องจากคำตอบมีชื่อของฉันอยู่ฉันต้องการให้มันมีคำของฉันด้วย ขอบคุณ.


2
เราพูดถึงว่าเราต้อง #include <signal.h> เพื่อให้ใช้งานได้!
kristianlm

13
คงที่มันควรจะbool volatile keepRunning = true;ปลอดภัย 100% คอมไพเลอร์มีอิสระในการแคชkeepRunningในรีจิสเตอร์และความผันผวนจะป้องกันสิ่งนี้ ในทางปฏิบัติมันอาจเป็นไปได้ที่จะทำงานโดยไม่มีคำสำคัญระเหยเมื่อห่วงขณะที่เรียกอย่างน้อยหนึ่งฟังก์ชั่นที่ไม่ใช่แบบอินไลน์
Johannes Overmann

2
@DirkEddelbuettel ฉันยืนแก้ไขฉันคิดว่าการปรับปรุงของฉันจะสะท้อนให้เห็นถึงความตั้งใจเดิมของคุณมากขึ้นขออภัยถ้ามันไม่ได้เกิดขึ้น อย่างไรก็ตามปัญหาที่ใหญ่กว่าก็คือเนื่องจากคำตอบของคุณพยายามที่จะเป็นแบบทั่วไปมากพอและเนื่องจาก IMO ควรให้ข้อมูลโค้ดที่ทำงานภายใต้การขัดจังหวะแบบอะซิงโครนัส: ฉันอาจใช้sig_atomic_tหรือatomic_boolพิมพ์ที่นั่น ฉันเพิ่งพลาดไป ตอนนี้เนื่องจากเรากำลังพูดถึง: คุณต้องการให้ฉันย้อนกลับการแก้ไขล่าสุดของฉันหรือไม่ ไม่มีความรู้สึกยากมีก็จะเป็นที่เข้าใจได้อย่างสมบูรณ์แบบจากมุมมองของคุณ :)
ปีเตอร์ Varo

2
มันดีกว่ามาก!
Dirk Eddelbuettel

2
@JohannesOvermann ไม่ว่าฉันต้องการ nitpick แต่พูดอย่างเคร่งครัดมันไม่สำคัญว่ารหัสจะเรียกใช้ฟังก์ชันที่ไม่ใช่แบบอินไลน์หรือไม่เช่นเดียวกับเมื่อข้ามสิ่งกีดขวางหน่วยความจำคอมไพเลอร์ไม่ได้รับอนุญาตให้ใช้ค่าแคช การล็อค / ปลดล็อก mutex จะเป็นอุปสรรคต่อหน่วยความจำ เนื่องจากตัวแปรนั้นเป็นแบบสแตติกและไม่สามารถมองเห็นได้นอกไฟล์ปัจจุบันคอมไพเลอร์จึงมีความปลอดภัยที่จะสมมติว่าฟังก์ชั่นไม่สามารถเปลี่ยนค่าได้เว้นแต่คุณจะส่งการอ้างอิงไปยังตัวแปรนั้นไปยังฟังก์ชัน ดังนั้นความผันผวนจึงแนะนำอย่างยิ่งที่นี่ในทุกกรณี
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: ทั้งหมดจริงมาก (เกี่ยวกับโมฆะ main () ใน C) แต่เมื่อโพสต์แบบสาธารณะมันก็อาจจะเป็นเช่นกันเพื่อหลีกเลี่ยงการอภิปรายทั้งหมดโดยใช้ int main () เกรงว่ามันจะหันเหความสนใจจากจุดหลัก
Clifford

21
มีข้อบกพร่องใหญ่หลวงในเรื่องนี้ คุณไม่สามารถใช้ printf ภายในเนื้อหาของตัวจัดการสัญญาณได้อย่างปลอดภัย เป็นการละเมิดความปลอดภัยสัญญาณแบบอะซิงโครนัส นี่เป็นเพราะ printf ไม่ได้ทำการส่งซ้ำ จะเกิดอะไรขึ้นหากโปรแกรมอยู่ระหว่างการใช้งาน printf เมื่อกด Ctrl-C และตัวจัดการสัญญาณของคุณเริ่มใช้งานพร้อมกัน? คำแนะนำ: มันอาจจะแตก การเขียนและการเขียนเป็นแบบเดียวกันกับที่ใช้ในบริบทนี้
Dylan Lukes

2
@ icyrock.com: การทำอะไรที่ซับซ้อนในเครื่องจัดการสัญญาณจะทำให้ปวดหัว โดยเฉพาะอย่างยิ่งการใช้ระบบ io
Martin York

2
@stacker ขอบคุณ - ฉันคิดว่ามันคุ้มค่าในตอนท้าย หากใครบางคนสะดุดรหัสนี้ในอนาคตดีกว่าที่จะทำให้ถูกต้องมากที่สุดโดยไม่คำนึงถึงหัวข้อของคำถาม
icyrock.com

30

ภาคผนวกเกี่ยวกับแพลตฟอร์ม UN * X

ตามsignal(2)หน้า man บน GNU / Linux พฤติกรรมของsignalไม่สามารถพกพาได้เหมือนกับพฤติกรรมของsigaction:

พฤติกรรมของสัญญาณ () แตกต่างกันไปตามรุ่นของ 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

หรือคุณสามารถวางเทอร์มินัลในโหมด raw เช่นนี้

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

จัดการกับสัญญาณที่คุณต้องการ แต่ระวังข้อ จำกัด และ gotchas:

ถือเป็นโมฆะ my_handler (int sig)
{
  / * รหัสของคุณที่นี่ * /
}

8

@Peter Varo ได้อัปเดตคำตอบของ Dirk แล้ว แต่ Dirk ปฏิเสธการเปลี่ยนแปลง นี่คือคำตอบใหม่ของปีเตอร์:

แม้ว่าตัวอย่างข้างต้นนั้นถูกต้อง ตัวอย่างหนึ่งควรใช้ประเภทที่ทันสมัยกว่าและการรับประกันที่จัดทำโดยมาตรฐานในภายหลังหากเป็นไปได้ ดังนั้นที่นี่เป็นทางเลือกที่ปลอดภัยและทันสมัยสำหรับผู้ที่กำลังมองหา และ การปฏิบัติตาม:

#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 ถูกประกาศใน 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


อย่าเรียกรูทีน 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.