CでCtrl-Cをキャッチ


158

CでCtrl+をどのようにキャッチCしますか?


5
Cでシグナルをキャッチするようなものはありません...少なくとも、C99標準を読むまではそう思っていました。Cで定義されている信号処理があることがわかりましたが、Ctrl-Cは特定の信号または信号を生成するように強制されていません。プラットフォームによっては、これが不可能な場合があります。
JeremyP 2010年

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、2009

13
static bool volatile keepRunning = true;100%安全である必要があります。コンパイラはkeepRunningレジスタに自由にキャッシュでき、揮発性はこれを防止します。実際には、whileループが少なくとも1つの非インライン関数を呼び出すときに、volatileキーワードがなくても機能する可能性があります。
ヨハネスオーバーマン2013年

2
@DirkEddelbuettel私は修正したと思います。私の改善点は元の意図をより反映するものだと思いました。とにかく、より大きな問題は、あなたの答えは、一般的な十分になろうとするので、つまり、およびIMOそれはまた、非同期割り込みの下で働いているスニペットを提供しなければならない理由:私は使用のどちらかだろうsig_atomic_tatomic_boolそこ種類。見逃しました。さて、話をしているので、最新の編集をロールバックしますか?そこにはあなたの視点から完全に理解できるような難しい感情はありません:)
Peter Varo

2
それははるかに良いです!
Dirk Eddelbuettel

2
@JohannesOvermann私がちょっと気にしたいわけではありませんが、厳密に言えば、コードが非インライン関数を呼び出すかどうかは関係ありません。メモリバリアを通過するときだけ、コンパイラはキャッシュされた値に依存することが許可されないためです。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同意int mainすることは適切なことですがgcc、他のコンパイラはこれを1990年代から正しく実行されているプログラムにコンパイルします。ここでかなりよく説明されています:eskimo.com/~scs/readings/voidmain.960823.html-それは基本的には「機能」であり、私はそのように捉えています。
icyrock.com 2010年

1
@ icyrock.com:(Cのvoid main()に関して)すべて非常に真実ですが、公に投稿するときは、メインポイントから注意をそらさないようにint main()を使用して議論を完全に回避することもおそらく同様です。
クリフォード

21
これには大きな欠陥があります。シグナルハンドラのコンテンツ内でprintfを安全に使用することはできません。これは非同期信号の安全性の違反です。これは、printfが再入可能ではないためです。Ctrl-Cが押されたときにプログラムがprintfを使用していて、シグナルハンドラがそれを同時に使用し始めた場合はどうなりますか?ヒント:壊れる可能性があります。このコンテキストで使用するwriteとfwriteは同じです。
Dylan Lukes

2
@ icyrock.com:シグナルハンドラーで複雑なことを行うと、頭痛の種になります。特にioシステムを使用しています。
マーティンヨーク

2
@stackerありがとう-最後にそれだけの価値があると思います。誰かが将来このコードに出くわした場合は、質問のトピックに関係なく、できるだけ正しくコードを記述しておくことをお勧めします。
icyrock.com 2010年

30

UN * Xプラットフォームに関する補遺。

signal(2)GNU / Linuxのmanページによると、の動作はの動作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

または、次のように端末を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+ ZCtrl+ QCtrl+ Sなどは通常のように使用できなくなるため、これを使用する場合は注意してください。


11

トラップを設定します(1つのハンドラーで複数のシグナルをトラップできます)。

シグナル(SIGQUIT、my_handler);
シグナル(SIGINT、my_handler);

信号は必要に応じて処理しますが、制限と注意点に注意してください。

void my_handler(int sig)
{
  / *ここにあなたのコード。* /
}

8

@ピーターヴァロはダークの回答を更新しましたが、ダークは変更を拒否しました。これがピーターの新しい答えです:

上記のスニペットは正しいですが たとえば、可能であれば、より新しいタイプと、後の標準によって提供される保証を使用する必要があります。したがって、ここでは、安全でモダンな代替品を求めている人のために そして 適合実装:

#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)_;...その目的は何ですか?コンパイラが未使用の変数について警告しないようにするためですか?
ルイスパウロ


私はその答えを見つけたばかりで、そのリンクをここに貼り付けようとしていました!ありがとう:)
ルイスパウロ

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 2018年

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 2018年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.