volatile
gcc compiler는 성능 향상(optimization)을 목적으로 경우에 따라 변수의 사용에 대해 무시하거나 사용 위치를 변경할 수 있는데 volatile을 사용하면 다음의 optimization을 하지 않고 코드를 만들어낸다.
- Optimization case
- 객체(변수)가 사용되지 않아도 된다고 판단할 때 무시한다.
- 루프 문 내부에서 사용되는 객체(변수)가 input용도로만 사용되는 경우 루프문 바깥으로 이전한다.
 
- 메모리, I/O 주소 등에 접근 시 생략될 가능성이 있거나 access 횟 수가 의도와 다르게 적게 호출될 가능성이 있는 경우 반드시 volatile을 사용하여 컴파일러로 하여금 관련 주소의 코드를 optimization 하지 않도록 해야 한다.
Using C
1) 무시되는 case
- d1 값을 10번 읽어들일 때.
volatile-1.c
void discard1()
{
        int i;
        int d1 = 1;
        int sum = 0;
        for (i = 0; i < 10; i++)
                sum += d1;
}
void discard2()
{
        int i;
        volatile int d2 = 1;
        int sum = 0;
        for (i = 0; i < 10; i++)
                sum += d2;
}
int main()
{
        discard1();
        discard2();
}
- 다음과 같이 disassemble 해보면 discard1() 함수의 경우 아무것도 하지 않음을 알 수 있다.
$ gcc -O2 volatile-1.c -o volatile-1
$ objdump -d volatile-1
(...생략...)
000083b4 <discard1>:
    83b4:       e12fff1e        bx      lr
000083b8 <discard2>:
    83b8:       e24dd008        sub     sp, sp, #8
    83bc:       e3a0300a        mov     r3, #10
    83c0:       e3a02001        mov     r2, #1
    83c4:       e58d2004        str     r2, [sp, #4]
    83c8:       e2533001        subs    r3, r3, #1
    83cc:       e59d2004        ldr     r2, [sp, #4]
    83d0:       1afffffc        bne     83c8 <discard2+0x10>
    83d4:       e28dd008        add     sp, sp, #8
    83d8:       e12fff1e        bx      lr
(...생략...)
2) 루프 밖으로 이동되는 케이스
#include <stdio.h>
int loop1(int * addr)
{
        int i;
        for (i = 0; i < 10; i++)
        {
                *addr += 1;
        }
        return i;
}
int loop2(volatile int * addr)
{
        int i;
        for (i = 0; i < 10; i++)
        {
                *addr += 1;
        }
        return i;
}
int main()
{
        int l1 = 0;
        volatile int l2 = 0;
        loop1(&l1);
        loop2(&l2);
        printf("l1=%d, l2=%d\n", l1, l2);
}
- 다음과 같이 disassemble 해보면 loop1() 함수의 경우 10번 반복하지 않고 1번만 결과 값을 저장함을 알 수 있다.
$ gcc -O2 volatile-2.c -o volatile-2
$ objdump -d volatile-2
(...생략...)
00008408 <loop1>:
    8408:       e1a03000        mov     r3, r0
    840c:       e3a0000a        mov     r0, #10
    8410:       e5932000        ldr     r2, [r3]
    8414:       e0822000        add     r2, r2, r0
    8418:       e5832000        str     r2, [r3]
    841c:       e12fff1e        bx      lr
00008420 <loop2>:
    8420:       e3a0300a        mov     r3, #10
    8424:       e5902000        ldr     r2, [r0]
    8428:       e2533001        subs    r3, r3, #1
    842c:       e2822001        add     r2, r2, #1
    8430:       e5802000        str     r2, [r0]
    8434:       1afffffa        bne     8424 <loop2+0x4>
    8438:       e3a0000a        mov     r0, #10
    843c:       e12fff1e        bx      lr
(...생략...)
$ ./volatile-2
l1=10, l2=10
Using Inline Assembly
1) 무시되는 case
volatile-3.c
void discard3(int * input)
{
        int output;
        asm ("ldr %0, [%1]"
                        : "=r" (output)
                        : "r" (input)
                        : "cc");
}
void discard4(int * input)
{
        int output;
        asm volatile ("ldr %0, [%1]"
                        : "=r" (output)
                        : "r" (input));
}
int main()
{
        int d3 = 0;
        int d4 = 0;
        discard3(&d3);
        discard4(&d4);
}
- 다음과 같이 disassemble 해보면 discard3() 함수의 경우 아무것도 하지 않음을 알 수 있다.
$ gcc -O2 volatile-3.c -o volatile-3
$ objdump -d volatile-3
(...생략...)
000083a4 <discard3>:
    83a4:       e12fff1e        bx      lr
000083ac <discard4>:
    83a8:       e5900000        ldr     r0, [r0]
    83ac:       e12fff1e        bx      lr
(...생략...)
2) 루프 밖으로 이동되는 케이스
volatile-4.c
int loop3(int * addr)
{
        int i;
        int tmp = 0;
        for (i = 0; i < 10; i++)
        {
                asm ("add %0, #1\n      str %0, [%1]"
                        : "=r" (tmp)
                        : "r" (addr)
                        : "memory");
        }
        return tmp;
}
int loop4(int * addr)
{
        int i;
        int tmp = 0;
        for (i = 0; i < 10; i++)
        {
                asm volatile ("add %0, #1\n     str %0, [%1]"
                        : "=r" (tmp)
                        : "r" (addr)
                        : "memory");
        }
        return tmp;
}
int main()
{
        int l3 = 1;
        int l4 = 1;
        loop3(&l3);
        loop4(&l4);
}
- 1
$ gcc -O2 volatile-4.c -o volatile-4
$ objdump -d volatile-4
(...생략...)
000083cc <loop3>:
    83cc:       e3a0300a        mov     r3, #10
    83d0:       e2800001        add     r0, r0, #1
    83d4:       e5820000        str     r0, [r0]
    83d8:       e2533001        subs    r3, r3, #1
    83dc:       1afffffd        bne     83d8 <loop3+0xc>
    83e0:       e12fff1e        bx      lr
000083e4 <loop4>:
    83e4:       e3a0300a        mov     r3, #10
    83e8:       e2800001        add     r2, r2, #1
    83ec:       e5820000        str     r2, [r0]
    83f0:       e2533001        subs    r3, r3, #1
    83f4:       1afffffb        bne     83e8 <loop4+0x4>
    83f8:       e1a00002        mov     r0, r2
    83f8:       e12fff1e        bx      lr
(...생략...)
기타
- Extended Asm – Assembler Instructions with C Expression Operands – volatile | gnu.org
- Using volatile | ARM
- [Linux] ACCESS_ONCE()와 volatile | F/OSS
- [Linux] 최적화 장벽? | F/OSS