演示在信号处理过程中不使用volatile关键字时编译器的优化效果?

Demonstrate compiler's optimization effects when volatile keyword is not used during signal handling?

在下面的代码中,我没有让变量quitvolatile sig_atomic_t。我将其保留为普通 int.

#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>

#define UNUSED(x) (void) (x)

int quit;

void sigusr1_handler(int sig)
{
    UNUSED(sig);
    write(1, "handler\n", 8);
    quit = 1;
}

int main()
{
    struct sigaction sa;

    sa.sa_handler = sigusr1_handler;
    sa.sa_flags = 0;
    sigemptyset(&sa.sa_mask);

    if (sigaction(SIGUSR1, &sa, NULL) == -1) {
        perror("sigaction");
        return 1;
    }

    quit = 0;
    while (!quit) {
        printf("Working ...\n");
        sleep(1);
    }

    printf("Exiting ...\n");
    return 0;
}

由于 quit 变量未指定为 volatile,我期望编译器的优化器会将代码中的 while 循环优化为:

    while (1) {
        printf("Working ...\n");
        sleep(1);
    }

但我没有看到这种情况发生。在一个终端上,我 运行 以下内容。

$ gcc -O3 foo.c && ./a.out 
Working ...
Working ...

在另一个终端上,我将 SIGUSR1 发送到我的程序。

$ pkill -USR1 a.out

在第一个终端上,输出显示调用了程序的信号处理程序并且 while-loop 退出。

Working ...
Working ...
handler
Exiting ...
$ 

由于 quit 不是 volatile,我如何演示循环的优化?

很难强制编译器从 while 条件中优化 quit 的加载。我能够通过删除 while 循环的主体来做到这一点:

#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>

#define UNUSED(x) (void) (x)

int quit;

void sigusr1_handler(int sig)
{
    UNUSED(sig);
    write(1, "handler\n", 8);
    quit = 1;
}

int main()
{
    struct sigaction sa;

    sa.sa_handler = sigusr1_handler;
    sa.sa_flags = 0;
    sigemptyset(&sa.sa_mask);

    if (sigaction(SIGUSR1, &sa, NULL) == -1) {
        perror("sigaction");
        return 1;
    }

    quit = 0;
    while (!quit) {
        //printf("Working ...\n");
        //sleep(1);
    }

    printf("Exiting ...\n");
    return 0;
}

在 gcc 5.4 版上使用 -O3 编译 x86-64 时,这会导致无限循环,不检查 quit 变量。注意 .L5:

处的跳转
.LC0:
        .string "handler\n"
sigusr1_handler(int):
        sub     rsp, 8
        mov     edx, 8
        mov     esi, OFFSET FLAT:.LC0
        mov     edi, 1
        call    write
        mov     DWORD PTR quit[rip], 1
        add     rsp, 8
        ret
.LC2:
        .string "sigaction"
main:
        sub     rsp, 168
        lea     rdi, [rsp+8]
        mov     QWORD PTR [rsp], OFFSET FLAT:sigusr1_handler(int)
        mov     DWORD PTR [rsp+136], 0
        call    sigemptyset
        xor     edx, edx
        mov     rsi, rsp
        mov     edi, 10
        call    sigaction
        cmp     eax, -1
        je      .L7
        mov     DWORD PTR quit[rip], 0
.L5:
        jmp     .L5
.L7:
        mov     edi, OFFSET FLAT:.LC2
        call    perror
        mov     eax, 1
        add     rsp, 168
        ret
quit:
        .zero   4

将定义更改为 volatile int quit; 会导致正确的行为。请参阅 movtestje 说明 .L6:

.LC0:
        .string "handler\n"
sigusr1_handler(int):
        sub     rsp, 8
        mov     edx, 8
        mov     esi, OFFSET FLAT:.LC0
        mov     edi, 1
        call    write
        mov     DWORD PTR quit[rip], 1
        add     rsp, 8
        ret
.LC2:
        .string "sigaction"
.LC3:
        .string "Exiting ..."
main:
        sub     rsp, 168
        lea     rdi, [rsp+8]
        mov     QWORD PTR [rsp], OFFSET FLAT:sigusr1_handler(int)
        mov     DWORD PTR [rsp+136], 0
        call    sigemptyset
        xor     edx, edx
        mov     rsi, rsp
        mov     edi, 10
        call    sigaction
        cmp     eax, -1
        je      .L11
        mov     DWORD PTR quit[rip], 0
.L6:
        mov     eax, DWORD PTR quit[rip]
        test    eax, eax
        je      .L6
        mov     edi, OFFSET FLAT:.LC3
        call    puts
        xor     eax, eax
.L5:
        add     rsp, 168
        ret
.L11:
        mov     edi, OFFSET FLAT:.LC2
        call    perror
        mov     eax, 1
        jmp     .L5
quit:
        .zero   4

你可以在这里玩两个例子:without volatile and with volatile