티스토리 뷰

C언어

C언어 정적 메모리할당

블로그 강승 2021. 3. 15. 22:50

C언어 정적 메모리할당

C언어 정적 메모리할당에 대해 Do it! C언어 입문(저자:김성엽) 이란 책을 통해 공부했으며, 책의 카테고리를 바탕으로 필자가 이해한 내용을 요약하는 글이다. 내용이 궁금하거나 찾아야 할 내용이 있으면, 필자가 정리한 글을 다시 보면서 복습하기위해 작성했다. 바로 아래에서 글을 확인하도록 하자.

 

프로그램과 프로세스

C언어로 컴파일 작업과 링크 작업이 끝나면 실행파일이 만들어지고 이걸 프로그램(Program)이라고 함. 프로그램은 CPU에서 바로 실행이 안됨. 운영체제가 프로그램을 메모리에 재구성하는 작업을 해야 하는데, 이걸 프로세스(Process) 라고함. CPU는 프로세스에 저장된 명령을 실행 가능. 그래서 프로세스를 '실행 중인 프로그램' 이라고도 함.
아래는 프로세스 구성임.

프로세스 구성 이미지

※세그먼트는 64Kbytes 이하의 메모리 블록임.

 

코드 세그먼트

실행 파일이 실행되어 프로세스가 만들어지면 이 기계어 명령들은 프로세스의 '코드 세그먼트'에 복사되어 프로그램 실행에 사용됨.

 

데이터 세그먼트

프로그램이 시작해서 끝날 때까지 계속 사용되는 데이터는 '데이터 세그먼트'에 보관됨. 컴파일할 때 이 영역의 데이터가 정해짐.

 

스택 세그먼트

프로그램 실행 중에, 임시 데이터를 저장하는 메모리.

 

정적 메모리 할당

컴파일러가 코드를 기계어로 번역하는 시점에 변수를 저장할 메모리 위치를 배정하는 것을 정적 메모리 할당(Static Memory Allocation)이라고 함. 프로그램이 실행될 때 메모리의 크기가 이미 결정되어 있으며, 실행되는 중간에 크기를 변경할 수 없는 메모리를 정적으로 할당된 메모리라고 함.
※정적(Static)은 static 키워드랑 전혀 상관 없는 의미임.

 

변수가 메모리에서 유지되는 시간

전역 변수 -> 데이터 세그먼트(프로그램이 종료될 때까지 자신의 메모리 공간을 가짐)
지역 변수 -> 스텍 세그먼트(함수가 호출될 때 메모리 공간이 생성되고 종료되면 없어짐)

 

정적으로 할당된 메모리를 관리하는 방법

전역 변수는 프로그램의 시작부터 끝까지 계속 유지되기 때문에 변수의 주소를 추적할 필요가 없다.
하지만 지역변수는 함수가 호출될때마다 메모리 할당과 해제가 반복되기 때문에 주소를 알아야 한다.

int Test()
{
int a,b,c,d;
a=5;
c=3;
}

이런 함수가 있다면 Test 함수의 시작 주소에 a, b, c, d 순서로 변수의 메모리 할당이 이루어진다.

a b c d

a가 시작되는 메모리를 Test 함수의 START 포인터, d의 오른쪽 끝부분을 Test 함수의 END 포인터임.
이때 a=5;, c=3; 을 한 행위는 'START 포인터 + 0' 주소에 5를 대입, 'START 포인터 + 8' 주소에 3을 대입한 것과 같다.
END 포인터는 Test 함수 안에 또 다른 함수가 호출될 때 END 포인터를 사용한다.

 

지역변수와 스택

스택(Stack)은 자료 구조(Data Structure)의 한 종류임. 스택은 베이스 포인터(Base Pointer, BP)를 기준으로 데이터가 추가될 때마다 순서대로 쌓아 올리는 구조이며 새로운 데이터가 추가될 위치를 스택 포인터(Stack Pointer, SP)가 가리키게 됨.(함수의 지역변수를 관리할 때 스택을 이용함)

1 2 3  
1 2 3 4
1 2 3  

1번 왼쪽에 Base Pointer가 위치하고 3의 오른쪽에 Stack Pointer가 있었는데, 4가 추가되면서, SP는 4의 오른쪽에 위치한다(이때 SP의 주소는 4 {32비트 운용체제 기준}만큼 감소함). 4가 제거되면서 SP은 다시 3의 오른쪽에 위치한다(SP 주소는 4만큼 증가함). BP는 그대로 1의 왼쪽에 위치한다. 이렇게 데이터를 추가하는 작업을 PUSH, 데이터를 제거하는 작업을 POP라고 함.
※SP와 BP가 같다면 스택에 데이터가 없다는 뜻임.
※어셈블리어로 스택 다루기

mov ax, 4 /* push는 레지스터를 이용하기 때문에 ax 레지스터에 값 4를 저장함 */
push ax /* ax 레지스터에 저장된 값(4)을 스택에 추가하고 SP 자동으로 감소함 */
pop ax /* 스택에서 값 4를 꺼내서 ax 레지스터에 저장하고 SP 자동으로 증가함 */

 

sub 명령과 add 명령을 사용하는 방법

sub SP, 12 /* SP 값을 12감소한다. 라는건 PUSH를 3번 명령한거랑 같음 */
add SP, 12 /* SP 값을 12 증가시킨다는건 POP를 3번 명령한거랑 같음 */

 

컴파일러가 스택에 할당된 지역 변수를 사용하는 원리

함수에 변수 a, b, c가 선언되면 a,b,c 순서대로 메모리에 형성된다. 이때 b라는 변수에 5라는 값을 넣고 싶다면 c를 POP 하고(bx라는 CPU 내부에 있는 범용 레지스터에 잠시 저장) b를 POP 한 후(ax 레지스터에 잠시 저장), 값 5를 대입하고, b를 PUSH, c를 PUSH 한다.
※프로그래머가 하는 일이 아니라 컴파일러 내부에서 일어나는 일임.
※CPU는 변수 a, b, c에서 직접 값을 넣거나 빼지 않고 CPU에서 연산을 수행할 때 사용되는 메모리인 ax, bx, cx 레지스터에서 수행함.(a를 꼭 ax에서 수행할 필요는 없음)

pop bx /* 위의 연산 과정을 어셈블리어로. 맨위에껄 pop해서 bx에 저장 */
pop ax /* pop해서 ax에 저장 */
mov ax, 5 /*5를 ax에 대입 */
push ax /*b영역에 5가 저장 */
push bx /*c 다시 스택에 추가 */

※push와 pop 명령은 레지스터에서만 사용할 수 있음.

 

베이스 포인터를 사용하여 스택에 할당된 지역 변수 사용하기

위와 같은 PUSH, POP을 이용한 지역변수 이용은 매우 비효율 적이다. 아깐 변수가 3개여서 다행이지, 만약 20개라고 가정했을 때, 첫 번째 변수 a를 사용하려면 몇 번의 POP와 PUSH를 해야 하는가... 하지만 BP를 기준으로 BP-4, BP-8 이런 식으로 주소를 지정해서 값을 바꾸면 엄청 쉽게 지역변수를 이용할 수 있다.
※스택 포인터를 기준으로 삼기엔 제한됨, PUSH가 일어났을 때 스택 포인터가 이동하기 때문에.

 

함수를 호출할 때 스택 메모리가 변화하는 과정

#include <stdio.h>
void Test()
{
int y;
}
void main()
{
int a,b,c;
Test();
}
a b c main IP main BP y

main 함수의 BP에 a, b, c 순서대로 메모리가 할당되고 c의 오른쪽 칸에 SP가 위치함. 그 순간 Test 함수가 호출되면, main함수의 현재 진행하는 위치를 저장하는 Instruction Pointer에서 값을 읽고 스택에 저장, 그리고 다시 main 함수로 돌아오면 BP를 main 함수에 맞춰야 하기 때문에 현재 BP의 주소를 스택에 저장한 후에, BP에 현재 SP의 주소를 대입. 그리고 Test함수 안에 있는 변수 y를 스택에 저장.

이제 Test함수가 끝나면 현재 SP는 y의 오른쪽에 위치하고 BP는 y의 왼쪽에 위치해있음. SP에 BP의 주소를 넣으면 y의 공간이 사라짐. 그리고 POP를 실행하면 main BP값을 읽어 들여, BP는 다시 a의 왼쪽에 위치하고, POP를 실행했기 때문에 SP는 main IP의 오른쪽에 위치함. 다시 한번 POP를 실행하면 main 함수에서 Test 함수를 부르고 난 뒤의 위치를 읽어 들이고 SP는 c의 오른쪽에 위치함.

 

스택 프레임이란

함수를 호출할 때 일어나는 스택의 변화를 스택 프레임(Stack Frame)이라고 함. 이것도 컴파일러가 C언어의 소스코드를 기계어를 바꾸는 시점에 정해지기 때문에 정적 메모리 할당임. 따라서 지역변수나 배열의 크기를 변경하려면 스택 프레임이 변경되기 때문에 새로운 실행 파일을 만들어야 함.

 

글을 마음대로 배포하셔도 되지만, 출처(링크)는 반드시 남겨주시기 바랍니다.

'C언어' 카테고리의 다른 글

C언어 다차원 포인터  (0) 2021.03.16
C언어 동적 메모리 할당  (0) 2021.03.15
C언어 배열과 포인터  (0) 2021.03.15
C언어 표준 입력 함수  (0) 2021.03.15
C언어 포인터  (0) 2021.03.14
댓글