【C言語】シグナルの0除算のこと
先日、こちらの記事の答案を考えていて、シグナルに「0除算」があることを知りました。CPUの例外割り込みで「0除算」は見たことがあったけど、C言語のシグナルにあるとは驚きです。なので、早速試してみました。試してはみたのですが・・・。
シグナルが発生しない・・・。
となると、またまた「なんでなんで?」と思ってしまうわけで。
少々調べてみました。
結論から言うと、私の環境では「0除算」は例外にならない。シグナルも発生しない。計算結果は「0」で処理されるようです。
そんなCPUもあるのねぇ。
知らなかった。
コード
こちらが試してみたコードです。
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdbool.h>
#include <stdlib.h>
/*
struct sigaction {
void (*sa_handler)(int);
sigset_t sa_mask;
int sa_flags;
void (*sa_sigaction)(int, siginfo_t *, void *);
};
*/
void sahandler(int sig)
{
}
char* str_sig(int sig)
{
char* str = "";
switch (sig)
{
//case SIGABND: str = "SIGABND"; break;
case SIGABRT: str = "SIGABRT"; break;
case SIGALRM: str = "SIGALRM"; break;
case SIGBUS: str = "SIGBUS"; break;
case SIGFPE: str = "SIGFPE"; break;
case SIGHUP: str = "SIGHUP"; break;
case SIGILL: str = "SIGILL"; break;
case SIGINT: str = "SIGINT"; break;
case SIGKILL: str = "SIGKILL"; break;
case SIGPIPE: str = "SIGPIPE"; break;
case SIGPOLL: str = "SIGPOLL"; break;
case SIGPROF: str = "SIGPROF"; break;
case SIGQUIT: str = "SIGQUIT"; break;
case SIGSEGV: str = "SIGSEGV"; break;
case SIGSYS: str = "SIGSYS"; break;
case SIGTERM: str = "SIGTERM"; break;
case SIGTRAP: str = "SIGTRAP"; break;
case SIGURG: str = "SIGURG"; break;
case SIGUSR1: str = "SIGUSR1"; break;
case SIGUSR2: str = "SIGUSR2"; break;
case SIGVTALRM: str = "SIGVTALRM"; break;
case SIGXCPU: str = "SIGXCPU"; break;
case SIGXFSZ: str = "SIGXFSZ"; break;
case SIGCHLD: str = "SIGCHLD"; break;
//case SIGIO: str = "SIGIO"; break;
//case SIGIOERR: str = "SIGIOERR"; break;
case SIGWINCH: str = "SIGWINCH"; break;
case SIGSTOP: str = "SIGSTOP"; break;
case SIGTSTP: str = "SIGTSTP"; break;
//case SIGTSTP: str = "SIGTSTP"; break;
case SIGTTIN: str = "SIGTTIN"; break;
case SIGTTOU: str = "SIGTTOU"; break;
case SIGCONT: str = "SIGCONT"; break;
}
return str;
}
int ctrlc_cnt = 0;
void sigint(int sig, siginfo_t * siginfo, void * tmp)
{
ctrlc_cnt++;
printf("\n");
printf("sigint : sig=%d[%s] ctrlc_cnt=%d \n",
sig, str_sig(sig), ctrlc_cnt);
if (2 <= ctrlc_cnt)
{
exit(0);
}
}
void sigabrt(int sig, siginfo_t * siginfo, void * tmp)
{
printf("sigabrt : sig=%d[%s]\n", sig, str_sig(sig));
}
void sigusr1(int sig, siginfo_t * siginfo, void * tmp)
{
printf("sigusr1 : sig=%d[%s]\n", sig, str_sig(sig));
}
bool alarmed = false;
void sigalrm(int sig, siginfo_t * siginfo, void * tmp)
{
alarmed = true;
printf("\n");
printf("sigalrm : sig=%d[%s]\n", sig, str_sig(sig));
}
void sigfpe(int sig, siginfo_t * siginfo, void * tmp)
{
printf("sigfpe : sig=%d[%s]\n", sig, str_sig(sig));
}
int main()
{
struct sigaction sact = {0};
sigemptyset(&sact.sa_mask);
sact.sa_flags = 0;
sact.sa_handler = sahandler;
sact.sa_sigaction = sigint;
sigaction(SIGINT, &sact, NULL);
sact.sa_sigaction = sigabrt;
sigaction(SIGABRT, &sact, NULL);
sact.sa_sigaction = sigusr1;
sigaction(SIGUSR1, &sact, NULL);
sact.sa_sigaction = sigalrm;
sigaction(SIGALRM, &sact, NULL);
sact.sa_sigaction = sigfpe;
sigaction(SIGFPE, &sact, NULL);
while (1)
{
printf("=======================\n");
printf("0.endless loop\n");
printf("1.abort\n");
printf("2.raise(SIGUSR1)\n");
printf("3.alarm(3)\n");
printf("4.divide by 0\n");
printf("=======================\n");
printf("select number -> ");
int num = 0;
int n = scanf("%d", &num);
if (n != 1)
{
//continue;
}
if (num == 0)
{
ctrlc_cnt = 0;
while (1) {}
}
else if (num == 1)
{
abort();
}
else if (num == 2)
{
raise(SIGUSR1);
}
else if (num == 3)
{
alarm(3);
alarmed = false;
while (1)
{
if (alarmed == true)
{
break;
}
}
}
else if (num == 4)
{
int tmp = num/0;
printf("tmp = %d\n", tmp);
}
else
{
break;
}
}
return 0;
}
関係のないコードも多くあって申し訳ない。
次のコードが0除算になります。
int tmp = num/0;
実行結果
そして実行結果がこちら。
~/c/c25 $ sig_2
=======================
0.endless loop
1.abort
2.raise(SIGUSR1)
3.alarm(3)
4.divide by 0
=======================
select number -> 4
tmp = 0
=======================
0.endless loop
1.abort
2.raise(SIGUSR1)
3.alarm(3)
4.divide by 0
=======================
select number -> 99
~/c/c25 $
これを見ると、「printf("tmp = %d\n", tmp);」は実行されている。これは「num/0」(0除算)の直後のコードだ。一方で次のシグナルハンドラは実行されていない。
void sigfpe(int sig, siginfo_t * siginfo, void * tmp)
{
printf("sigfpe : sig=%d[%s]\n", sig, str_sig(sig));
}
まるで、0除算などなかったかのような動きだ。
0除算は本当にコード化されているのか。
そんな疑問も脳内をよぎる。
そこでアセンブラも出力してみた。
確かにコードは出力されている。
ldr w8, [sp, #44]
mov w9, wzr
sdiv w8, w8, w9
str w8, [sp, #36]
ldr w1, [sp, #36]
adrp x0, .L.str.44
add x0, x0, :lo12:.L.str.44
bl printf
ここの「sdiv w8, w8, w9」が0除算になります。
ついでなので、デバッグもしてみた。
その結果は・・・やはり実行されている。
(gdb) info r w8
w8 0x4 4
(gdb) info r w9
w9 0x0 0
(gdb) a
Dump of assembler code for function main:
0x0000005555557204 <+544>: ldr w8, [sp, #44]
0x0000005555557208 <+548>: mov w9, wzr
=> 0x000000555555720c <+552>: sdiv w8, w8, w9
0x0000005555557210 <+556>: str w8, [sp, #36]
0x0000005555557214 <+560>: ldr w1, [sp, #36]
0x0000005555557218 <+564>: adrp x0, 0x5555555000
0x000000555555721c <+568>: add x0, x0, #0x76b
0x0000005555557220 <+572>: bl 0x55555572a0 <printf@plt>
End of assembler dump.
(gdb) si
0x0000005555557210 173
int tmp = num/0;
(gdb) a
Dump of assembler code for function main:
0x0000005555557204 <+544>: ldr w8, [sp, #44]
0x0000005555557208 <+548>: mov w9, wzr
0x000000555555720c <+552>: sdiv w8, w8, w9
=> 0x0000005555557210 <+556>: str w8, [sp, #36]
0x0000005555557214 <+560>: ldr w1, [sp, #36]
0x0000005555557218 <+564>: adrp x0, 0x5555555000
0x000000555555721c <+568>: add x0, x0, #0x76b
0x0000005555557220 <+572>: bl 0x55555572a0 <printf@plt
End of assembler dump.
(gdb)
「=>」はプログラムカウンタの位置です。
最初は「sdiv w8, w8, w9」の位置にあって、このコードを実行する直前であることを意味します。
w8 = 4、w9 = 0ですので、0で除算しようとしています。
デバッグコマンド「si」はアセンブラを1ステップだけ実行するコマンドで、これにより「sdiv w8, w8, w9」が実行される。
そして、その結果は・・・。
プログラムカウンタは次のアセンブラに進み、0除算のハンドラである「sigfpe」には飛びません。
ネットも検索してみて、このあたりの話はあまり見つからないのだけど、それでも、ARM では0除算が例外にならないというような記事もありました。
少なくとも私の環境では、
0除算は例外にはならず、演算の結果は0
ということのようです。
だからと言って、除数が0でないことのチェックが外せるというわけでもないのだけれど。