0

ここでの目標は、SIGINT をキャッチして、小さなソケット サーバーのサーバー ソケットを閉じることでした。コードをきれいに保つために、ネストされた関数を使用しようとしました。しかし...

Ctrl-C (SIGINT ですよね?) を実行すると、Illegal instruction: 4. この投稿を読んだ後、1​​0.8 を使用-mmacosx-version-min=10.8しているため、コンパイル フラグに追加しようとしました。Ctrl-C を実行すると同じエラーが発生します。

ここで 2 つの質問があります: なぜ「Illegal instruction 4」が表示されるのですか?グローバル変数を使用せずにサーバー ソケットを閉じるにはどうすればよいですか?

私のソフトウェア:

Mac OSX 10.8.4
GCC 4.2.1

コンパイル方法は次のとおりです。

gcc -fnested-functions main.c

コードは次のとおりです。

#include <sys/socket.h>
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>

void register_sigint_handler(int *serverSocket)
{
    void sigint_handler(int signal) {
        printf("Shutting down...\n");
        printf("Server socket was %d\n", *serverSocket);
        close(*serverSocket);
        exit(0);
    }
    signal(SIGINT, &sigint_handler);
}

int main(void) {
    int serverSocket = 0, guestSocket = 0;

    register_sigint_handler(&serverSocket);

    serverSocket = socket(PF_INET, SOCK_STREAM, 0);

    while (1) {}

    close(serverSocket);
    return 0;
}
4

2 に答える 2

6

具体的に何が起こるかは言えませんが、gcc のドキュメントには次のような一般化があります。

含まれている関数が終了した後で、ネストされた関数をそのアドレスを介して呼び出そうとすると、すべての地獄が解き放たれます。

関数ポインタを signal() に渡すと、まさにそれが行われ、含まれている関数が終了した後にローカル関数を呼び出します。したがって、ネストされた関数ポインターを signal() に渡すべきではありません

おそらく、フラグを設定するハンドラーの通常の関数を使用する必要があります。

static volatile int do_exit;
void sigint_handler(int sig)
{
   do_exit = 1;
}

サーバーでは、通常、select や poll などのメイン ループの周りに何らかのメイン ループがあり、空の while ループは次のようになります。

while (!do_exit) { 
  pause(); 
}

(プロセスが終了すると、ソケットはオペレーティング システムによって自動的に閉じられることに注意してください。)

于 2013-08-27T21:48:22.407 に答える
5

じゃあ、やらないで

GCC の nested-functions-in-C 拡張機能は、真のクロージャを提供しません。のアドレスを取得するとsigint_handler、「トランポリン」(小さな自己変更コード) がスタックに書き込まれます。register_sigint_handler出るとすぐに、トランポリンは破壊されます。その後トランポリンを呼び出そうとすると (シグナルをディスパッチするためにカーネルによって)、未定義の動作が発生します。

シグナルハンドラは、定義上プロセスグローバルです。したがって、 シグナル ハンドラでグローバル変数を使用しないようにすることは、原則として正しくありません。このコードを2 つのサーバー ソケットに対応させるにはどうすればよいか想像してみてください。SIGINT登録できるハンドラーは 1 つだけなので、どうにかして両方のソケットを閉じる必要があります。

プロセスが終了すると、開いているファイル記述子はすべて自動的に閉じられます。 したがって、最初に手で閉じる必要はありません。さらに、で正常に終了することは規約違反です^C このプログラムが監視プロセスによって駆動されている場合、そのプロセスはwaitpidSIGINT. これら 2 つのことをまとめると、このシグナル ハンドラはまったく必要ありません

(終了時にアクティブなソケットに何かをする必要がある場合、それは当てはまりません。たとえば、終了時にすべてのアクティブなクライアント接続に何かを書き込みたい場合は、シグナル ハンドラーが必要になります。しかし、その時点で、シグナル ハンドラーにメイン イベント ループを警告させ、そこで作業を行います。)

libevent(すべての低レベルのグーを自分で行うのではなく、この種のことを使用することを検討してください。)

于 2013-08-27T22:01:43.547 に答える