Cコンパイラ　想定外の最適化とvolatileの役割

## 初めに
running変数にvolatileをつけた場合とつけない場合で比較する

### 比較するC言語ソース
<details>
<summary>volatile無し</summary>

```test1.s
#include <stdio.h>
#include <signal.h>

int running = 1;  // ★ここを変更して比較

// Ctrl+Cハンドラ
void handler(int sig) {
    running = 0;
    printf("Handler called!\n");
}

int main() {
    signal(SIGINT, handler);  // 修正箇所
    while (running); // 最適化で無限ループに

    printf("Exited gracefully\n");
    return 0;
}
```
</details>

<details>
<summary>volatile有り</summary>

```test2.s
#include <stdio.h>
#include <signal.h>

volatile int running = 1;  // ★ここを変更して比較

// Ctrl+Cハンドラ
void handler(int sig) {
    running = 0;
    printf("Handler called!\n");
}

int main() {
    signal(SIGINT, handler);  // 修正箇所
    while (running); // 最適化で無限ループに

    printf("Exited gracefully\n");
    return 0;
}
```
</details>

### アセンブリ
```
gcc -S -O2 -masm=intel test1.c -o test1.s
gcc -S -O2 -masm=intel test2.c -o test2.s
```

<details>
<summary>volatile無し</summary>

```test1.s
	.file	"test1.c"
	.intel_syntax noprefix
	.section .rdata,"dr"
LC0:
	.ascii "Handler called!\0"
	.text
	.p2align 4,,15
	.globl	_handler
	.def	_handler;	.scl	2;	.type	32;	.endef
_handler:
LFB12:
	.cfi_startproc
	mov	DWORD PTR _running, 0
	mov	DWORD PTR [esp+4], OFFSET FLAT:LC0
	jmp	_puts
	.cfi_endproc
LFE12:
	.def	___main;	.scl	2;	.type	32;	.endef
	.section .rdata,"dr"
LC1:
	.ascii "Exited gracefully\0"
	.section	.text.startup,"x"
	.p2align 4,,15
	.globl	_main
	.def	_main;	.scl	2;	.type	32;	.endef
_main:
LFB13:
	.cfi_startproc
	push	ebp
	.cfi_def_cfa_offset 8
	.cfi_offset 5, -8
	mov	ebp, esp
	.cfi_def_cfa_register 5
	and	esp, -16
	sub	esp, 16
	call	___main
	mov	DWORD PTR [esp+4], OFFSET FLAT:_handler
	mov	DWORD PTR [esp], 2
	call	_signal
	mov	eax, DWORD PTR _running
	.p2align 4,,10
L3:
	test	eax, eax
	jne	L3
	mov	DWORD PTR [esp], OFFSET FLAT:LC1
	call	_puts
	xor	eax, eax
	leave
	.cfi_restore 5
	.cfi_def_cfa 4, 4
	ret
	.cfi_endproc
LFE13:
	.globl	_running
	.data
	.align 4
_running:
	.long	1
	.ident	"GCC: (MinGW.org GCC-6.3.0-1) 6.3.0"
	.def	_puts;	.scl	2;	.type	32;	.endef
	.def	_signal;	.scl	2;	.type	32;	.endef
```
</details>

<details>
<summary>volatile有り</summary>

```test2.s
	.file	"test2.c"
	.intel_syntax noprefix
	.section .rdata,"dr"
LC0:
	.ascii "Handler called!\0"
	.text
	.p2align 4,,15
	.globl	_handler
	.def	_handler;	.scl	2;	.type	32;	.endef
_handler:
LFB12:
	.cfi_startproc
	mov	DWORD PTR _running, 0
	mov	DWORD PTR [esp+4], OFFSET FLAT:LC0
	jmp	_puts
	.cfi_endproc
LFE12:
	.def	___main;	.scl	2;	.type	32;	.endef
	.section .rdata,"dr"
LC1:
	.ascii "Exited gracefully\0"
	.section	.text.startup,"x"
	.p2align 4,,15
	.globl	_main
	.def	_main;	.scl	2;	.type	32;	.endef
_main:
LFB13:
	.cfi_startproc
	push	ebp
	.cfi_def_cfa_offset 8
	.cfi_offset 5, -8
	mov	ebp, esp
	.cfi_def_cfa_register 5
	and	esp, -16
	sub	esp, 16
	call	___main
	mov	DWORD PTR [esp+4], OFFSET FLAT:_handler
	mov	DWORD PTR [esp], 2
	call	_signal
	.p2align 4,,10
L3:
	mov	eax, DWORD PTR _running
	test	eax, eax
	jne	L3
	mov	DWORD PTR [esp], OFFSET FLAT:LC1
	call	_puts
	xor	eax, eax
	leave
	.cfi_restore 5
	.cfi_def_cfa 4, 4
	ret
	.cfi_endproc
LFE13:
	.globl	_running
	.data
	.align 4
_running:
	.long	1
	.ident	"GCC: (MinGW.org GCC-6.3.0-1) 6.3.0"
	.def	_puts;	.scl	2;	.type	32;	.endef
	.def	_signal;	.scl	2;	.type	32;	.endef
```
</details>


## 比較

### 動作
Ctrl＋cを押した後の画面

#### volatile無し
無限ループを抜けない
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4054974/ca8ad75a-9b8d-4127-8d3c-78717e023003.png)

#### volatile有り
無限ループを抜ける
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4054974/a2101529-db61-4d81-9ea7-07704df0e7fd.png)

### 分析
#### アセンブリ差分の比較
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4054974/0c081042-d997-44b1-9c36-c9a7e743afc1.png)

#### volatile無し
一度eaxに入れた1を使い廻しているため、`running`に0が入っても無限ループから抜けることができない
```無限ループ.s
	mov	eax, DWORD PTR _running
	.p2align 4,,10
L3:
	test	eax, eax
	jne	L3
```

#### volatile有り
毎回メモリからrunningの値を読み込んでいる。
```無限ループ.s
	.p2align 4,,10
L3:
	mov	eax, DWORD PTR _running
	test	eax, eax
	jne	L3
```
※test	eax, eaxは`eax`が0かどうかを調べている
　`eax`が0の場合はゼロフラグ`ZF​​`が1となる。
 その後`jne	L3`にて`ZF`が0でない場合はループを続ける。

メモリの読み込み回数が最適化で減らされ不具合が起きてしまった。(敢えて起こした)

## 動作環境
windows7 Version6.1.7601
GCC-6.3.0-1
（帰省中につき一時的に昔のパソコンを使用している。windows7が懐かしい。。。）