หนึ่งจับCtrl+ Cใน C อย่างไร
หนึ่งจับCtrl+ Cใน C อย่างไร
คำตอบ:
ด้วยตัวจัดการสัญญาณ
นี่คือตัวอย่างง่ายๆที่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 : ผู้ที่อาจกังวลโดยเฉพาะผู้ที่มีความต้องการที่ไม่รู้จักพอที่จะแก้ไขคำตอบนี้ ดูฉันเขียนคำตอบนี้เจ็ดปีที่ผ่านมา ใช่มาตรฐานภาษามีการเปลี่ยนแปลง หากคุณจริงๆต้องดีกว่าโลกโปรดเพิ่มคำตอบใหม่ของคุณแต่ปล่อยเหมืองที่เป็นอยู่ เนื่องจากคำตอบมีชื่อของฉันอยู่ฉันต้องการให้มันมีคำของฉันด้วย ขอบคุณ.
bool volatile keepRunning = true;
ปลอดภัย 100% คอมไพเลอร์มีอิสระในการแคชkeepRunning
ในรีจิสเตอร์และความผันผวนจะป้องกันสิ่งนี้ ในทางปฏิบัติมันอาจเป็นไปได้ที่จะทำงานโดยไม่มีคำสำคัญระเหยเมื่อห่วงขณะที่เรียกอย่างน้อยหนึ่งฟังก์ชั่นที่ไม่ใช่แบบอินไลน์
sig_atomic_t
หรือatomic_bool
พิมพ์ที่นั่น ฉันเพิ่งพลาดไป ตอนนี้เนื่องจากเรากำลังพูดถึง: คุณต้องการให้ฉันย้อนกลับการแก้ไขล่าสุดของฉันหรือไม่ ไม่มีความรู้สึกยากมีก็จะเป็นที่เข้าใจได้อย่างสมบูรณ์แบบจากมุมมองของคุณ :)
ตรวจสอบที่นี่:
หมายเหตุ:แน่นอนนี้เป็นตัวอย่างง่ายๆอธิบายเพียงวิธีการตั้งค่าการ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
}
int main
เป็นสิ่งที่เหมาะสม แต่gcc
และคอมไพเลอร์อื่น ๆ รวบรวมสิ่งนี้ไปยังโปรแกรมที่ทำงานอย่างถูกต้องตั้งแต่ปี 1990 อธิบายได้ค่อนข้างดีที่นี่: eskimo.com/~scs/readings/voidmain.960823.html - มันเป็น "ฟีเจอร์" โดยทั่วไปฉันชอบมันมาก
ภาคผนวกเกี่ยวกับแพลตฟอร์ม 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
}
}
หรือคุณสามารถวางเทอร์มินัลในโหมด 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ฯลฯ เช่นปกติได้อีก
ตั้งค่ากับดัก (คุณสามารถดักจับสัญญาณหลายสัญญาณด้วยตัวจัดการเดียว):
สัญญาณ (SIGQUIT, my_handler); สัญญาณ (SIGINT, my_handler);
จัดการกับสัญญาณที่คุณต้องการ แต่ระวังข้อ จำกัด และ gotchas:
ถือเป็นโมฆะ my_handler (int sig) { / * รหัสของคุณที่นี่ * / }
@Peter Varo ได้อัปเดตคำตอบของ Dirk แล้ว แต่ Dirk ปฏิเสธการเปลี่ยนแปลง นี่คือคำตอบใหม่ของปีเตอร์:
แม้ว่าตัวอย่างข้างต้นนั้นถูกต้อง c89ตัวอย่างหนึ่งควรใช้ประเภทที่ทันสมัยกว่าและการรับประกันที่จัดทำโดยมาตรฐานในภายหลังหากเป็นไปได้ ดังนั้นที่นี่เป็นทางเลือกที่ปลอดภัยและทันสมัยสำหรับผู้ที่กำลังมองหาc99 และ c11 การปฏิบัติตาม:
#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)_;
... จุดประสงค์คืออะไร คอมไพเลอร์ไม่เตือนเกี่ยวกับตัวแปรที่ไม่ได้ใช้หรือไม่?
#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
นี่แค่พิมพ์ก่อนออก
#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);
}