본문 바로가기
씨 언어에서 변수는 어디에 저장 되나요?

뭘 알아야 이해를 하지

씨 언어에서 프로그램을 할 때 수치나 문자열을 많이 사용하지요.
수치와 문자열을 상수(Constant)라고 하고 이들을 저장하는 장소를 변수라고 부른답니다. 이러한 변수는 어떤 데이타형을 사용하느냐에 따라 크기와 표현범위가 달라지죠.
일반적으로 char, int, float, double 있으며 여기에다 signed, unsigned, long, short의 수직식어와 함께 사용을 하지요. 즉, 문자는 char, 수치는 int, long, short, unsigned, signed이며 실수형은 float를 사용하지요.
이러한 변수들이 저장되는 공간은 어디일까요?
씨 언어에서 변수를 저장하는 기억 클래스 4가지가 있으며 register, auto, static, extern이랍니다. 일반적으로 암 컴파일가 프리웨어인 arm-gcc 이냐 상용컴파일러이냐에 따라 기억 클래스 위치가 달라 질 수도 있어요. 그럼 여기에서는 프리웨어인 arm-gcc 위주로 설명을 드릴께요.
우선 링크 스크립트에서 각각의 영역을 다음과 같이 정의를 했답니다.
__TEXT_START__ = 0x00000000;
__DATA_START__ = 0x00010000;
__BSS_START__ = 0x30020000;

그리고 Startup.s 파일에서 스택 위치는 다음과 같이 정의를 했어요.
STACK_BASEADDR=0x30040000
SVCStack=(STACK_BASEADDR-0x2800)

아래 그림은 변수를 저장하는 기억 클래스 영역 나타내고 있어요. 각 영역에 대해서 알아보죠.

 

 

 

 

첫 번째, register 변수입니다. 함수나 블록이 실행될 때 유효하지만 컴파일러는 암 코어 레지스터①에 할당하게 된답니다. Register 변수로 정의하면 대부분의 컴파일러는 암 코어 레지스터에서 실행하게 함으로 보다 빠른 입출력 효과를 기대할 수 있으나, 항상 레지스터가 비이 있지 않기 때문에 상황에 따라 register 변수지만 스택⑥에 저장이 되기도 하죠.

[레지스터 변수가 암 코어 레지스터에 할당]
-000|register_func()
| (auto int) [SD:0x3003D7A8] count1 = 3 | (register int) [R2] count2 = 14
| (register int) [R1] count3 = 3
| (register int) [R0] count4 = 4
| (register int) [R12] count5 = 5
| (register int) [R14] count6 = 6
| (register int) [R4] count7 = 7
| (register int) [R5] count8 = 8
| (register int) [R6] count9 = 9
| (register int) [R7] count10 = 10
| (register int) [R8] count11 = 11
| (register int) [R10] count12 = 12
| (register int) [R9] count13 = 13
| (auto int) [SD:0x3003D7AC] count14 = 14 | (auto int) [SD:0x3003D7B8] han = 36
| (auto int) [SD:0x3003D7B4] star = 42
| (auto int) [SD:0x3003D7B0] value = 27

두 번째, auto 변수 입니다. 기억장소의 지정 없이 선언된 지역 변수는 기본적으로 auto가 되고, 함수에 들어갈 때 자동적으로 변수는 스택에 할당돼요. 함수 또는 블록이 실행될 때 유효함으로 그 이외에는 존재할 수 없으며, 흔히 지역변수라고 부른답니다. 그리고 변수는 초기화를 해 주는 것이 바람직해요. 그 이유는 변수가 할당 될 때 이전에 스택에서 사용한 값이 계속 남아 있었다면, 그 값이 그대로 변수에 할당되기 때문이지요. 지역 변수는 해당 함수 또는 블록에서만 사용해야 되기 때문에 이전에 사용한 값은 필요가 없고 초기화 된 상태에서 사용하는 게 좋겠죠. 대신 초기화를 하는 명령어들이 추가가 되어 바이너리 사이즈 증가와 시피유가 메모리에서 명령어를 읽어오기 위해 메모리 접근을 한번이라도 더 해야 하니 배터리 소모량도 많이 진다는 사실을 잊어버리면 안 된답니다.
이제 예제를 살펴봐요.


하드웨어 디버기로 변수 초기화를 할 때 어떤 명령어가 만들어 졌는지 살펴봐요.

[변수 초기화 명령어]
|void auto_func()
34 |{
SR:000003E8|E1A0C00D__auto_func:mov_____r12,r13
SR:000003EC|E92DD800 stmdb r13!,{r11-r12,r14,pc}
SR:000003F0|E24CB004 sub r11,r12,#0x4
SR:000003F4|E24DD00C sub r13,r13,#0x0C
| int i, one, star;
36| one = 0;
SR:000003F8|E3A03000 mov r3,#0x0
SR:000003FC|E50B3014 str r3,[r11,#-0x14]
37| star = 0;
SR:00000400|E3A03000 mov r3,#0x0
SR:00000404|E50B3018 str r3,[r11,#-0x18]

[one, star 변수가 스택에 할당됨]

-000|auto_func()
| (auto int) [SD:0x3003D7D4] i = 1 | (auto int) [SD:0x3003D7D0] one = 0 | (auto int) [SD:0x3003D7CC] star = 0
세 번째로 static 변수입니다. 내부 정적 변수와 외부 정적 변수로 지정을 할 수가 있어요. 함수나 블록이 소멸되어도 그 값을 유지해요. static을 키워드로 사용하지 않으면 당연히 auto 변수이기 때문에 함수나 블록이 소멸될 때 같이 소멸되지요. static을 사용할 때 변수를 초기화를 하느냐 하지 않느냐에 따라 메모리에 저장되는 곳이 ③ 또는 ④ 가 된답니다. 그리고 만약 변수를 초기화하지 않았다면 컴파일 될 때 자동적으로 0으로 초기화해 준답니다. 여기서 자동으로 '0' 초기화한다는 것은 Statup.s 파일에서 BSS영역을 초기화하는 부분입니다.

[BSS 초기화 부분]


[하드웨어 디버거에서 .bss_start와 .bss_end 주소 확인]
___address___to_________|path\section___________________________|acc|init|physical
P:00000000--0000065B|\\general\.text |R-X|L- |
P:0000065C|\\general\.glue_7 |R-X|L- |
P:0000065C|\\general\.glue_7t |R-X|L- |
D:00010000--0001003F|\\general\.data |RW-|L- |
D:30020000--3002000F|\\general\.bss |RW-|-- |

static으로 선언된 변수는 데이타 영역③, ④에 저장 됩니다.
예제를 살펴보죠.



[메모리에 할당된 변수]
(auto int) [SD:0x3003D7EC] auto_count = 0 (static int) [D:0x1003C] inter_static_Init = 5 (static int) [D:0x30020000] inter_static_UnInit = 0 (static int) [D:0x10038] extern_static_Init = 10 (static int) [D:0x30020004] extern_static_UnInit = 0
여기서 중요한 사실은 초기화된 데이타 영역③ (b-3,d-3)이랍니다. 현재 노어 플래시 영역에 초기화된 데이타 영역③에 저장되어 있는데, 시스템 동작 중에 이 변수들의 값을 Write를 하는 소스가 있다면(g-3,j-3) 이 영역은 에스디램이 아니라서 그냥 Write가 되지 않는다는 점입니다.

___address___to_________|path\section___________________________|acc|init|physical
P:00000000--0000065B|\\general\.text |R-X|L- |
P:0000065C|\\general\.glue_7 |R-X|L- |
P:0000065C|\\general\.glue_7t |R-X|L- |
D:00010000--0001003F|\\general\.data |RW-|L- | 노어 플래시 영역
D:30020000--3002000F|\\general\.bss |RW-|-- |

그리고 d-3와 같이 함수 진입 시점에 초기화 값이 있다면, 이 함수가 재 진입했을 때 inter_static_Iinit 은 다시 10으로 초기화 되지 않고 마지막에 누적된 값이 적용된답니다.

[void static_func() 을 두 번 돌았을 때]
(auto int) [SD:0x3003D7D4] auto_count = 1
(static int) [D:0x1003C] inter_static_Init = 5
(static int) [D:0x30020000] inter_static_UnInit = 2
(static int) [D:0x10038] extern_static_Init = 10
(static int) [D:0x30020004] extern_static_UnInit = 2

제가 질문을 하나 할게요.
a-3, b-3, c-3, d-3에서 모두 static를 빼면 어떻게 될까요? c-3과 d-3은 auto 변수가 되겠지만 a-3, b-3은 글로벌 변수가 된답니다.
글로벌 변수는 static로 선언한 것과 똑 같이 동작됩니다.


[void global_func() 을 진입했을 때]
(auto int) [SD:0x3003D7D4] auto_count = 0 (static int) [D:0x10034] global_Init = 10 (static int) [D:0x30020008] global_UnInit = 1
마지막으로 extern
프로그램이 외부 변수를 초기화하지 않으면 자동적으로 컴파일러에 의해 0으로 초기가 됩니다. 함수의 외부에서 정의되어 프로그램 전체에서 사용 가능하지요. 위치는 함수 외부 어느 곳이나 상관없지만, 정의된 이후로만 사용할 수 있으므로 모든 함수에서 사용할 수 있도록 파일의 선두에 정의를 합니다.
개발자가 값을 변경시키지 않는 한, 한번 저장된 값은 프로그램 종료 시까지 값이 유지를 하지요. 스택에 저장하기엔 너무 큰 데이터를 저장하기 위해서는 외부 정적 변수나 extern 변수로 지정해서 정적 데이터 영역에 저장해야 합니다. 그렇지 않으면 스택이 위험하겠죠. ㅎㅎ
프로그램 방법은 ‘extern’키워드를 사용하면 됩니다.

[void extern_func() 에서 regvar 1일 때]
(static int) [D:0x10034] extern_Init = 10 (static int) [D:0x3002000C] extern_UnInit = 1 (auto int) [SD:0x3003D7D4] regvar = 1
이제 정리를 하면 다음과 같아요.

[초기화된 변수]
____address|_data__________|value__________|symbol
SD:00010000| 12 51 55 22 22555112 \\general\Global\$d
SD:00010004| 00 07 00 00 700 \\general\Global\$d+0x4
SD:00010008| F0 7F 00 00 7FF0 \\general\Global\$d+0x8
SD:0001000C| 00 07 00 00 700 \\general\Global\$d+0x0C
SD:00010010| F0 7F 00 00 7FF0 \\general\Global\$d+0x10
SD:00010014| 00 07 00 00 700 \\general\Global\$d+0x14
SD:00010018| 00 07 00 00 700 \\general\Global\$d+0x18
SD:0001001C| 05 80 01 00 18005 \\general\Global\$d+0x1C
SD:00010020| 05 80 01 00 18005 \\general\Global\$d+0x20
SD:00010024| 59 04 8E 00 8E0459 \\general\Global\$d+0x24
SD:00010028| 32 00 00 00 32 \\general\Global\$d+0x28
SD:0001002C| 30 00 00 00 30 \\general\Global\$d+0x2C
SD:00010030| 30 00 00 00 30 \\general\Global\$d+0x30
SD:00010034| 0A 00 00 00 0A \\general\Global\extern_Init
SD:00010038| 0A 00 00 00 0A \\general\Global\global_Init
SD:0001003C| 0A 00 00 00 0A \\general\main\extern_static_Init
SD:00010040| 05 00 00 00 5 \\general\main\static_func\inter_static_Init


[초기화 안된 변수]
____address|_data__________|value___|symbol
SD:30020000| 00 00 00 00 0 \\general\main\static_func\inter_static_UnInit
SD:30020004| 00 00 00 00 0 \\general\main\extern_static_UnInit
SD:30020008| 00 00 00 00 0 \\general\Global\global_UnInit
SD:3002000C| 00 00 00 00 0 \\general\Global\extern_UnInit

 

Linked at 친절한 임베디드 시스템 개발자.. at 2010/06/12 22:44

... ; 303 링커스크립트파일(Linker Script file)이 뭐죠? 304 씨 언어에서 변수는 어디에 저장 되나요? 305 소스 수정하면 다시 플래시에 쓰기를 해야 하나요? 3 ... more

Commented by highseek at 2010/06/12 22:56
수직어 -> 수식어
Commented by soto at 2010/06/15 09:17
감사요..^^;
즐거운 하루 되세염~~!!!
Commented by 히언 at 2010/06/16 23:29
수정했사옵니다. 냐호호
※ 이 포스트는 더 이상 덧글을 남길 수 없습니다.
친절한 임베디드 시스템 개발자 되기 강좌 글 전체 리스트 (링크) -



댓글





친절한 임베디드 개발자 되기 강좌 글 전체 리스트 (링크) -