Làm thế nào để một người bắt Ctrl+ Ctrong C?
Làm thế nào để một người bắt Ctrl+ Ctrong C?
Câu trả lời:
Với một bộ xử lý tín hiệu.
Đây là một ví dụ đơn giản lật một bool
được sử dụng trong main()
:
#include <signal.h>
static volatile int keepRunning = 1;
void intHandler(int dummy) {
keepRunning = 0;
}
// ...
int main(void) {
signal(SIGINT, intHandler);
while (keepRunning) {
// ...
Chỉnh sửa vào tháng 6 năm 2017 : Người mà nó có thể quan tâm, đặc biệt là những người có sự thôi thúc vô độ để chỉnh sửa câu trả lời này. Hãy nhìn xem, tôi đã viết câu trả lời này bảy năm trước. Có, tiêu chuẩn ngôn ngữ thay đổi. Nếu bạn thực sự phải làm tốt hơn thế giới, xin vui lòng thêm câu trả lời mới của bạn nhưng hãy để lại câu trả lời của tôi. Vì câu trả lời có tên của tôi trên đó, tôi cũng muốn nó chứa từ của tôi. Cảm ơn bạn.
bool volatile keepRunning = true;
an toàn 100%. Trình biên dịch là miễn phí để lưu trữ keepRunning
trong một thanh ghi và dễ bay hơi sẽ ngăn chặn điều này. Trong thực tế, rất có thể nó cũng hoạt động mà không có từ khóa dễ bay hơi khi vòng lặp while gọi ít nhất một hàm không nội tuyến.
sig_atomic_t
hoặc atomic_bool
gõ ở đó. Tôi chỉ bỏ lỡ một. Bây giờ, vì chúng ta đang nói chuyện: bạn có muốn tôi quay lại bản chỉnh sửa mới nhất của mình không? Không có cảm giác khó khăn ở đó nó sẽ hoàn toàn dễ hiểu theo quan điểm của bạn :)
Kiểm tra tại đây:
Lưu ý: Rõ ràng, đây là một ví dụ đơn giản giải thích chỉ làm thế nào để thiết lập một CtrlChandler, nhưng như luôn có những quy tắc cần phải được tuân theo để không phá vỡ cái gì khác. Xin vui lòng đọc các ý kiến dưới đây.
Mã mẫu từ phía trên:
#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
là một điều thích hợp, nhưng gcc
các trình biên dịch khác biên dịch nó thành một chương trình đang chạy chính xác từ những năm 1990. Giải thích khá tốt ở đây: eskimo.com/~scs/readings/voidmain.960823.html - về cơ bản nó là một "tính năng", tôi coi nó như thế.
Phụ lục liên quan đến các nền tảng UN * X.
Theo signal(2)
trang man trên GNU / Linux, hành vi của signal
không thể mang theo như hành vi của sigaction
:
Hành vi của signal () khác nhau giữa các phiên bản UNIX và cũng đã thay đổi theo lịch sử trên các phiên bản Linux khác nhau. Tránh sử dụng của nó: sử dụng sigaction (2) thay vào đó.
Trên Hệ thống V, hệ thống không chặn phân phối thêm các trường hợp tín hiệu và phân phối tín hiệu sẽ đặt lại trình xử lý về mặc định. Trong BSD, ngữ nghĩa đã thay đổi.
Biến thể sau đây của câu trả lời trước của Dirk Eddelbuettel sử dụng sigaction
thay vì 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
}
}
Hoặc bạn có thể đặt thiết bị đầu cuối ở chế độ thô, như thế này:
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);
Bây giờ có thể đọc Ctrl+ Cgõ phím bằng cách sử dụng fgetc(stdin)
. Hãy cẩn thận khi sử dụng điều này bởi vì bạn không thể Ctrl+ Z, Ctrl+ Q, Ctrl+ S, v.v. như bình thường nữa.
Thiết lập bẫy (bạn có thể bẫy nhiều tín hiệu bằng một trình xử lý):
tín hiệu (SIGQUIT, my_handler); tín hiệu (SIGINT, my_handler);
Xử lý tín hiệu theo cách bạn muốn, nhưng lưu ý các hạn chế và vấn đề:
void my_handler (int sig) { / * Mã của bạn ở đây. * / }
@Peter Varo đã cập nhật câu trả lời của Dirk, nhưng Dirk đã từ chối thay đổi. Đây là câu trả lời mới của Peter:
Mặc dù đoạn trích trên là chính xác c89ví dụ, người ta nên sử dụng các loại hiện đại hơn và đảm bảo được cung cấp bởi các tiêu chuẩn sau này nếu có thể. Do đó, đây là một sự thay thế an toàn và hiện đại hơn cho những người đang tìm kiếmc99 và c11 tuân thủ thực hiện:
#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;
}
Tiêu chuẩn C11: 7.14 §2 Tiêu đề
<signal.h>
khai báo một loại ...sig_atomic_t
là loại số nguyên (có thể đủ điều kiện dễ bay hơi) của một đối tượng có thể được truy cập dưới dạng thực thể nguyên tử, ngay cả khi có sự gián đoạn không đồng bộ.
Hơn nữa:
Tiêu chuẩn C11: 7.14.1.1 §5 Nếu tín hiệu xảy ra ngoài kết quả của việc gọi hàm
abort
hoặcraise
hàm, hành vi không được xác định nếu trình xử lý tín hiệu tham chiếu đến bất kỳ đối tượng nào cóstatic
hoặc thời gian lưu trữ luồng không phải là đối tượng nguyên tử không khóa hơn bằng cách gán giá trị cho một đối tượng được khai báo làvolatile sig_atomic_t
...
(void)_;
... Mục đích của nó là gì? Có phải vì vậy trình biên dịch không cảnh báo về một biến không sử dụng?
Về câu trả lời hiện có, lưu ý rằng việc xử lý tín hiệu phụ thuộc vào nền tảng. Win32 chẳng hạn xử lý tín hiệu ít hơn nhiều so với hệ điều hành POSIX; xem tại đây . Trong khi SIGINT được khai báo trong signal.h trên Win32, hãy xem ghi chú trong tài liệu giải thích rằng nó sẽ không làm những gì bạn có thể mong đợi.
#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;
}
Hàm sig_handler kiểm tra xem giá trị của đối số được truyền có bằng SIGINT không, thì printf được thực thi.
Điều này chỉ cần in trước khi thoát.
#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);
}