Bắt Ctrl-C trong C


158

Làm thế nào để một người bắt Ctrl+ Ctrong C?


5
Không có thứ gọi là bắt tín hiệu trong C .... hoặc ít nhất là tôi nghĩ cho đến khi tôi đọc được tiêu chuẩn C99. Hóa ra là có xử lý tín hiệu được xác định trong C nhưng Ctrl-C không bắt buộc phải tạo ra bất kỳ tín hiệu cụ thể hoặc tín hiệu nào cả. Tùy thuộc vào nền tảng của bạn, điều này có thể là không thể.
JeremyP

1
Xử lý tín hiệu chủ yếu là phụ thuộc thực hiện. Trên các nền tảng * nix, hãy sử dụng <signal.h> và nếu bạn đang dùng OSX, bạn có thể tận dụng GCD để giúp mọi việc trở nên dễ dàng hơn ~.
Dylan Lukes

Câu trả lời:


205

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.


2
Hãy đề cập rằng chúng ta cần #include <signal.h> để điều này hoạt động!
kristianlm

13
tĩnh Nó phải được bool volatile keepRunning = true;an toàn 100%. Trình biên dịch là miễn phí để lưu trữ keepRunningtrong 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.
Julian Overmann

2
@DirkEddelbuettel Tôi đã sửa chữa, tôi nghĩ rằng những cải tiến của tôi sẽ phản ánh ý định ban đầu của bạn nhiều hơn, xin lỗi nếu điều đó không xảy ra. Dù sao, vấn đề lớn hơn là, vì câu trả lời của bạn cố gắng đủ chung chung và vì IMO nên nó cung cấp một đoạn mã cũng hoạt động theo các ngắt không đồng bộ: Tôi sẽ sử dụng sig_atomic_thoặc atomic_boolgõ ở đó. 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 :)
Peter Varo

2
Điều đó tốt hơn nhiều!
Dirk Eddelbuettel

2
@JohannesOvermann Không phải tôi muốn nitc mà nói một cách nghiêm túc, không có vấn đề gì nếu mã gọi bất kỳ hàm phi tuyến nào hay không, vì chỉ khi vượt qua hàng rào bộ nhớ, trình biên dịch không được phép dựa vào giá trị được lưu trong bộ nhớ cache. Khóa / Mở khóa một mutex sẽ là một rào cản bộ nhớ như vậy. Do biến là tĩnh và do đó không hiển thị bên ngoài tệp hiện tại, trình biên dịch sẽ an toàn khi giả định rằng một hàm không bao giờ có thể thay đổi giá trị của nó, trừ khi bạn chuyển tham chiếu đến biến đó cho hàm. Vì vậy, dễ bay hơi được khuyến khích ở đây trong mọi trường hợp.
Mecki

47

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
}

2
@Derrick Đồng ý, int mainlà một điều thích hợp, nhưng gcccá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ế.
icyrock.com

1
@ icyrock.com: Tất cả đều rất đúng (liên quan đến void main () trong C), nhưng khi đăng công khai thì có lẽ cũng nên tránh tranh luận hoàn toàn bằng cách sử dụng int main () vì nó làm sao lãng điểm chính của nó.
Clifford

21
Có một lỗ hổng lớn trong việc này. Bạn không thể sử dụng printf một cách an toàn trong nội dung của trình xử lý tín hiệu. Đó là một sự vi phạm về an toàn tín hiệu không đồng bộ. Điều này là do printf không được reentrant. Điều gì xảy ra nếu chương trình ở giữa sử dụng printf khi nhấn Ctrl-C và trình xử lý tín hiệu của bạn bắt đầu sử dụng nó cùng một lúc? Gợi ý: Nó có thể sẽ phá vỡ. write và fwrite giống nhau để sử dụng trong bối cảnh này.
Dylan Lukes

2
@ icyrock.com: Làm bất cứ điều gì phức tạp trong trình xử lý tín hiệu sẽ gây ra đau đầu. Đặc biệt là sử dụng hệ thống io.
Martin York

2
@stacker Cảm ơn - Tôi nghĩ cuối cùng nó cũng đáng. Nếu ai đó tình cờ tìm thấy mã này trong tương lai, tốt hơn là có nó càng nhiều càng tốt, bất kể chủ đề của câu hỏi.
icyrock.com

30

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 signalkhô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 sigactionthay 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
    }
}


14

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.


11

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. * /
}

8

@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 ví 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ếm 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_tlà 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 aborthoặc raisehà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ó statichoặ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...


Tôi không hiểu (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?
Luis Paulo


Tôi vừa tìm thấy câu trả lời đó và chuẩn bị dán liên kết đó vào đây! Cảm ơn :)
Luis Paulo

5

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.


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

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.


Không bao giờ gọi các thường trình I / O trong một bộ xử lý tín hiệu.
jwdonahue

2

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

2
Không bao giờ gọi I / O trong một bộ xử lý tín hiệu.
jwdonahue
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.