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