프로그래밍 언어/C언어

예시로 알아보는 volatile 한정자

원원 2022. 1. 12. 00:56

안녕하세요. 오늘은 C언어를 사용하여 volatile 한정자를 알아보겠습니다.

 

volatile를 알아보기 전에 최적화를 알아야 합니다. 컴파일을 할 때 최적화(Optimization)이라는 기능이 있습니다. 최적화 기능을 사용하면 의미 없는 코드를 기계어로 변환할 때 코드를 무시해버리기도 해서 속도 향상이나 크기 줄이기가 가능합니다.

dev 컴파일러

 

visual studio 컴파일러

이런 식으로 컴파일러마다 최적화 옵션이 다 있습니다.
예를 들어서 최적화 기능을 켜고 아래의 코드를 글로 컴파일 해보겠습니다.

1
2
3
4
5
int i = 0;
i=1;
i=2;
i=3;
i=4;
cs

2~4번 무시하고 i=4만 실행한다고 생각하면 됩니다 (만약 뒤의 코드에서 i를 사용하지 않으면 아무것도 안합니다)

 

volatile는 변수 앞에 쓰고 최적화를 하지 말라는 뜻입니다. 최적화 옵션을 키고 volatile를 변수 앞에 붙이면 최적화를 안 합니다.  예제로 확인해 보겠습니다.

예제1)

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
 
int main()
{
int i = 0;
    i = 1;
    i = 2;
    i = 3;
    i = 4;
    printf("%d \n", i);
}
cs

위의 코드는 i에 대해서 최적화를 하는 코드이고 i에 대해서 최적화를 안 하려면 volatile int i = 0; 하면 됩니다

 

아래의 코드는 최적화를 어셈블리어로 본 코드입니다. 어셈블리어로 보면 최적화를 어떻게 했는지 볼 수 있습니다. 어셈블리어를 몰라도 코드를 무시했는지 안 했는지만 보면 됩니다. 

1
2
3
4
5
6
7
8
9
10
   int i = 0; //차이점
    i = 1;
    i = 2;
    i = 3;
    i = 4;
    printf("%d \n", i);
00601040  push        4  
00601042  push        offset string "%d \n" (0602100h)  
00601047  call        printf (0601010h)  
0060104C  add         esp,8  
cs

1~5번 코드는 어셈블리어가 없습니다. 그냥 7번 줄에서 바로 4를 넣어버렸습니다

 

아래의 코드는 i만 최적화를 안 하겠다는 선언을 한 코드입니다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
   volatile int i = 0; //차이점
001F1044  mov         dword ptr [ebp-4],0  
    i = 1;
001F104B  mov         dword ptr [i],1  
    i = 2;
001F1052  mov         dword ptr [i],2  
    i = 3;
001F1059  mov         dword ptr [i],3  
    i = 4;
001F1060  mov         dword ptr [i],4  
    printf("%d \n", i);
001F1067  mov         eax,dword ptr [i]  
001F106A  push        eax  
001F106B  push        offset string "%d \n" (01F2100h)  
001F1070  call        printf (01F1010h)  
001F1075  add         esp,8  
cs

두 코드를 비교해 보면 volatile를 사용한 코드가 최적화를 안 하므로 더 깁니다. 또한 i에 일일이 상숫값을 넣어주는 작업이 어셈블리어로 표현되어 있습니다
(int i = 1;   ==  001F104B  mov dword ptr [i],1  두 코드는 같은 의미입니다)

 


예제2)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <stdio.h>
#include <time.h>
 
int TESTCASE = 1000;
int TIME = 0;
clock_t start;
 
void Timer_Start()
{
    start = clock();
}
 
void Timer_Stop()
{
    TIME += ((int)clock() - start) / (CLOCKS_PER_SEC / 1000);
    printf("TIME : %d ms\n", TIME);
}
 
int main()
{
    Timer_Start();
 
    volatile long long= 0;
   // long long l = 0;
    long long j = 0;
    printf("start \n");
 
    for (l = 0; l < 2000000000; l++)
        j++;
 
    printf("end \n");
 
 
    Timer_Stop();
}
cs

21번줄,34번줄은 23~31줄 코드가 실행되는 시간을 측정해 주기 위한 함수입니다
23번째줄과 24번째줄을 각각 주석/주석해지하고 실행결과를 보겠습니다

volatile 사용(23번줄 주석해제, 24번줄 주석)


volatile 미사용(23번줄 주석, 24번줄 주석해제)

최적화를 시키면 0ms가 나오고 최적화를 안 시키면 5598ms가 나옵니다. 최적화를 시키면 0ms 나오는 이유가 28번째 줄은 j++를 시켜도 j를 어디에서 사용하지 않으므로 아예 실행을 안 합니다. 그러나 j를 사용하는 코드를 넣는다면(예로들어 printf("%d",j); ) 28번 줄은 실행이 되긴 하는데 최적화를 안 한 것보다는 빠릅니다

 


예제3)
MCU를 예로 들면 LED를 제어할때 핀에 0을 넣고 1을 넣습니다.
P=0;
P=1;
이런 식으로 LED를 제어하는데 최적화를 해버리면 원하는 대로 동작이 안됩니다. 그래서 MCU의 레지스터들은 전부 volatile를 붙여서 최적화를 안되게 합니다.


- ABOV, keil컴파일러 (sfr variables are always volatile. The compiler will not optimize accesses to this type of variables.)
->  sfr         P0         = 0x80;         // P0 Data Register

- Renesas
-> #define P0           (*(volatile __near unsigned char  *)0xFF00)