見出し画像

【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でないことのチェックが外せるというわけでもないのだけれど。

この記事が気に入ったらサポートをしてみませんか?