🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

Linux

Linuxは、Unixをベースにして開発されたオペレーティングシステムです。日本では「リナックス」と呼ばれています。 主にWebサーバやDNSサーバ、イントラネットなどのサーバ用OSとして利用されています。 上位500のスーパーコンピュータの90%以上はLinuxを使用しています。 携帯端末用のプラットフォームAndroidは、Linuxカーネル上に構築されています。

Q&A

解決済

5回答

2756閲覧

signal関数の意味が分かりません

ken21

総合スコア17

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

Linux

Linuxは、Unixをベースにして開発されたオペレーティングシステムです。日本では「リナックス」と呼ばれています。 主にWebサーバやDNSサーバ、イントラネットなどのサーバ用OSとして利用されています。 上位500のスーパーコンピュータの90%以上はLinuxを使用しています。 携帯端末用のプラットフォームAndroidは、Linuxカーネル上に構築されています。

0グッド

0クリップ

投稿2020/12/02 05:31

表題の通りです。
シグナルというものが別プロセスへの割り込みだということは理解できましたが
signal関数の定義式の意味がさっぱり分かりません。

定義式
void (*signal(int sig, void (*func)(int)))(int)

現状理解できていること
・一般的な関数ポインタについて
※ 型 (*ラベル名) (引数)が関数へのポインタ変数になり
関数のアドレスを格納するとラベル名(引数)で関数を呼び出せる。

関数ポインタについては上記の理解ですがsignal()の説明になると
「signal()は、引数がint型、返り値がvoid型の関数へのポインタを返す関数で、
引数として、int型と、int型を引数として受け取りvoid型を返り値として返す関数へのポインタを返す」
というような説明が多くどのような動きをしているか説明がなくイメージがわきません。

お手数ですがこれを理解するためにどなたか解説いただけないでしょうか。
もしくはこれを理解するための知識の何が足りなくどれを勉強すればよいか
教えていただけないでしょうか。

よろしくお願いいたします。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

yohhoy

2020/12/02 16:41

「signal()は、引数がint型、返り値がvoid型の関数へのポインタを返す関数で、引数として、int型と、int型を引数として受け取りvoid型を返り値として返す関数へのポインタを返す」は、単に宣言から読み取れる関数の型情報を日本語で書き下しただけです。本当に知りたいのは「signal関数はどのように動くか/何の役に立つか」ということでしょうか?
ken21

2020/12/03 04:56

そうですね。まずはそれを知ることが出来ればいいかと思っております。 それを知ったうえで上記の説明文を理解できればベストだと思っています。
guest

回答5

0

ベストアンサー

本当に知りたいのは「signal関数はどのように動くか/何の役に立つか」ということでしょうか?
そうですね。まずはそれを知ることが出来ればいいかと思っております。

C言語signal関数の理解には、前提知識としてOSが提供する「シグナル(Signal)」の仕組みを理解している必要があります。

詳細説明は上記で紹介したサイトに譲ります。signal関数の動作は下記2つで説明されます:

c

1void (*signal(int sig, void (*func)(int)))(int);
  • 指定したシグナル番号sigに対応するシグナルハンドラとして、指定した関数ポインタfuncを登録する。(振る舞いの説明)
  • 直近の古いシグナルハンドラ(関数ポインタ)を返す。(戻り値の説明)

シグナルハンドラとして引数で登録/戻り値となっている関数ポインタが、質問中にある「引数がint型、返り値がvoid型の関数へのポインタ(void(*)(int)型)」に対応します。

投稿2020/12/03 05:38

編集2020/12/03 05:41
yohhoy

総合スコア6191

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

ken21

2020/12/11 04:31

ご返事が遅れて申し訳ございません。 丁寧なご説明、大変勉強になりました。
guest

0

C

1typedef void (*sighandler_t)(int); 2sighandler_t signal(int signum, sighandler_t sighandler);

signal は関数で、引数にシグナル番号 signum とハンドラー指定すると、
そのシグナル番号のシグナルを受けた時に、そのハンドラーを呼び出します。
signal は、以前のハンドラーを返します。

C

1#include <stdio.h> // printf, fflush 2#include <signal.h> // signal, SIGINT 3#include <unistd.h> // sleep 4 5void handler1(int sig) { printf("\nhandler1: sig = %d\n", sig); } 6void handler2(int sig) { printf("\nhandler2: sig = %d\n", sig); } 7 8int main(void) 9{ 10 void (*f1)(int); 11 void (*f2)(int); 12 13 f1 = signal(SIGINT, handler1); 14 f2 = signal(SIGINT, handler2); 15 16 printf("handler1 = %p\n", handler1); 17 printf("handler2 = %p\n", handler2); 18 printf("f1 = %p\n", f1); 19 printf("f2 = %p\n", f2); 20 21 for (int i = 0; i < 30; i++) { 22 if (i == 10) signal(SIGINT, f2); 23 if (i == 20) signal(SIGINT, f1); 24 printf(" %d", i); 25 fflush(stdout); 26 sleep(1); 27 } 28}

実行例

text

1$ ./a.out 2handler1 = 0x7f98055431a9 3handler2 = 0x7f98055431d1 4f1 = (nil) 5f2 = 0x7f98055431a9 6 0 1 2 3 4^C 7handler2: sig = 2 8 5 6 7 8^C 9handler2: sig = 2 10 9 10 11 12 13^C 11handler1: sig = 2 12 14 15 16 17^C 13handler1: sig = 2 14 18 19 20 21 22 23 24 25 26^C 15$

投稿2020/12/02 17:11

kazuma-s

総合スコア8224

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

ken21

2020/12/03 04:46

ご回答ありがとうございます。 こちらを参考に実行してみてわからないところがありますので 教えてください。 ①f1に格納されている値はなぜnullなのでしょうか? handler1関数のアドレスが格納されると思いました。 ②for文内の if (i == 10) signal(SIGINT, f2); if (i == 20) signal(SIGINT, f1);の役割について。 実際に動作させると何も入力場合、単純に増加した数値が表示されているだけでした。 iが10,20になった時にsignalを起動させるためにあるのかと思いました。 ③入力した場合の動作 SIGINTがキーボードからの割りこみになのでプログラム実行中にCtrl+Cでどのような動作になるか確認しました。 結果として、handler1の場合も2の場合も出力され、法則がわかりませんでした。 ②の質問内容に影響するのだと思いますがどのような動作を想定されて作られたのでしょうか。 お手数おかけしますが教えていただけないでしょうか。
ken21

2020/12/03 04:53

すいません、①に関しては別の方が回答されていますね。 signalの仕様が以前のhandler関数のアドレスがかえるとなっているのでf1の場合はnullになったのですね。 とするとアドレスが正しくない状態で if (i == 10) signal(SIGINT, f2); if (i == 20) signal(SIGINT, f1); とするのは書き方として問題はないのでしょうか。
kazuma-s

2020/12/03 06:28 編集

typedef void (*sighandler_t)(int); #define SIG_ERR ((sighandler_t) -1) /* Error return. */ #define SIG_DFL ((sighandler_t) 0) /* Default action. */ #define SIG_IGN ((sighandler_t) 1) /* Ignore signal. */ シグナルハンドラーは関数のアドレスですが、特殊な値が define されています。 0 はデフォールトのアクションで、Ctrl-C に対してはプロセスを終了させます。 1 はシグナルの無視で、Ctrl-C に対して何も応答しません。 0 にセットするのは、ハンドラーをデフォールトに戻したということです。 最初の 10秒間はハンドラーが handler2 にセットされています。 Ctrl-C を押すたびに handler2: sig = 2 と表示されます。 次の 10秒間はハンドラーが handler1 にセットされています。 Ctrl-C を押すたびに handler1: sig = 2 と表示されます。 最後の 10秒間は、ハンドラーがデフォールトに戻されています。 Ctrl-C を押すと、プロセスが終了します。
ken21

2020/12/03 07:38

ご回答いただきありがたいのですが、私の知識では理解できませんでした。 まず、ここでおっしゃっているシグナルハンドラーとはsignal関数の戻り値のことを指していますでしょうか。
guest

0

signal関数は、シグナルを受けたときの処理を指定します。

C

1void foo(int s){ 2 printf("シグナル %d を受けました\n",s); 3} 4 5int main(){ 6 ・・・ 7 signal(2, foo); 8 ・・・ 9}

とすると、main実行中signal呼び出し以降に、シグナル2(つまりCtrl-Cを押した)を受けたときにfoo関数が呼ばれます。
この場合は、「プログラムを停止する」というシグナル2のデフォルト動作はしません。

投稿2020/12/02 06:33

otn

総合スコア85893

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

ken21

2020/12/02 06:49

なるほど、今までは簡単ですぐに終了するプログラムしか作成してこなかったですが実際のシステムでは常時稼働しているものがあるためこのような機能の登録が必要なのですね?
otn

2020/12/02 07:00

常時稼働のプログラムだけじゃ無いです。 複数プロセス間での通信(情報は送れないのでタイミングを合わせるくらい)とか。 alarmシステムコールで、指定時間後にシグナルを送ってもらって、処理を変えたり。 長時間かかる処理の途中経過を出したり。 (例えば、ddというコピーコマンドではシグナル10を受けると、その時点までにどれだけコピーをしたか表示します)
ken21

2020/12/02 07:47

ご丁寧にありがとうございます。 また、確認ですが上記のように signal(2, foo);とすると2番(SIGINT)のデフォルトの動作である「tetminate」は上書きされるという理解でよかったでしょうか。
otn

2020/12/02 08:14

はい。上書きです。というか、「差し替え」ですね。
angel_p_57

2020/12/02 16:18

分かり易いサンプルを作るのは難しい面があるのでやむを得ないかも知れませんが、シグナルハンドラ ( この場合の foo ) で printf を呼ぶのはご法度です。 シグナルハンドラでできる処理には結構厳しい制約があり、ちょっとしたことでも難易度が高くなります。かつ、今ではポピュラーなマルチスレッドプログラミングとの相性が最悪です。 マルチスレッド前提にはなりますが、sigwait系の関数で、単に「シグナルが来るのを待つ」( OSからハンドラに飛んでもらうのではなく ) という処理にした方が扱いが楽になります。 https://linuxjm.osdn.jp/html/LDP_man-pages/man2/sigtimedwait.2.html
otn

2020/12/02 16:26

ああ、そうですね。あくまでサンプルということで見てください。 簡便には、シグナルを処理する関数の中では、グローバル変数にフラグを立てる程度にして、 本体処理の、適当なタイミングでそのフラグをチェックして処理を変えるようにすると、 難しいことを考えずに済みます。
guest

0

signalは、シグナル番号(変数sig)に対する割り込みハンドラー(変数func)を登録する関数です。
以下をご参考ください。
C言語でシグナルをハンドルする

投稿2020/12/02 06:01

DreamTheater

総合スコア1095

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

void (*signal(なんたら, かんたら))(int);

これで、signal関数は、関数ポインタを返す関数って事がわかるでしょうか。
そのポインタの関数は、intを引数としたvoid型の関数ですね

んじゃ第2引数の方を。

void (*func)(int)

こいつも、intを引数に取るvoidを返す関数のポインタですね

投稿2020/12/02 05:56

y_waiwai

総合スコア88038

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

ken21

2020/12/02 06:33

関数ポインタということはわかりましたが結局signal()がどのように動いているのかがわかりません。 上の方は登録といっていますが、下記のような例だとどのような動きをしますでしょうか? void handler(int sig) { printf("No %d", sig) } int main(void) { signal(SIGINT, handler) }
y_waiwai

2020/12/02 06:37

SIGINTのシグナルが発行された場合に、何らかの処理をするようにその関数を登録する、という動作です。 このコードの場合は、handler関数を登録しています
ken21

2020/12/02 06:47

ということは、この場合だとmain関数実行中にSIGINT(キーボードからの割り込み)があったらmain関数の実行を一時的に中断してhandler関数を実行するということでしょうか? また、話が戻りますがvoid (*signal(なんたら, かんたら))(int);でなぜ関数を表しているかがわからないです。 簡略化するとvoid (*A()) (int)になるかと思いますが*A()が関数という意味なのでしょうか?
y_waiwai

2020/12/02 06:49

関数Aの戻り値が、関数ポインタ、という意味です。 わかりづらいですね。。 これはもう、こういうもんと覚えとかないとしようがないかも
y_waiwai

2020/12/02 06:50

まあ、この場合は(関数の登録が成功すれば)、handler関数のアドレスが返されるだけ、ってな動作となります。
ken21

2020/12/02 06:55

signal関数はわかりづらいですね、、、、いつか完全に理解できるように努めます。。。 アドレスが返されるというのはどうゆうことでしょうか? この場合だとsignal()からアドレスが返されたとしても受けとるものがない気がしますが、、、 上記の例は普通の使い方ではないのでしょうか?
y_waiwai

2020/12/02 07:35

この場合は戻り値は利用されないのでなにもしてませんね。 まあ、一般的には戻り値のアドレスを利用するってことはあんましなくて、せいぜい関数の実行失敗を判定するぐらいです。 ぐぐれば、サンプルコードや実行例なんかもでてくるんでそういうのをみることですか。 JAVAとかPythonみたいなイマドキの言語とちがい、CはCPUのハードウエアの実装を反映している言語となります。 ポインタとか、このsignal関数もそうですが、CPUの機能を実装するためのものですんで、そっち方面に行かないのであれば、こんなもんと流しておいてもいいかと思いますw
ken21

2020/12/02 07:50

ありがとうございました。 あいにくそっち方面なものであまり無視できないのです、、、 また、戻り値を利用しないとのことですが使い方としては signal()で登録をしたのちに割り込み(例えばキーボード入力やkillコマンド)があったら登録内容を実行するという理解でいいでしょうか。
dodox86

2020/12/02 08:10 編集

[2020/12/02 15:50]y_waiwaiさんのコメントより > まあ、この場合は(関数の登録が成功すれば)、handler関数のアドレスが返されるだけ、ってな動作となります。 もしかしたら誤解があるかもしれませんが、signalの返り値は、呼び出し時に登録したハンドラへのポインタではなく、その前に登録していたハンドラのポインタです。 https://linuxjm.osdn.jp/html/LDP_man-pages/man2/signal.2.html signalは少しばかり不確かな仕様の関数ですが、BSD系でもほぼ同等なはずです。 https://www.freebsd.org/cgi/man.cgi?sektion=3&query=signal 余り使い道はないですが、自前のハンドラをいったん登録し、後で登録前のハンドラに書き戻すような場合に使います。
y_waiwai

2020/12/02 08:55

ああ、デイジーチェインできるようになってんですね。 ご指摘ありがとうございます
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問