티스토리 뷰



메모리 할당


프로그램과 프로세스

        

프로그램은 프로그래머가 만든 실행 파일이다. 프로그램과 프로세스가 뭐가 다른지 알아보자. 이 둘의 차이는 명확하다. 프로그램 자체는 보조기억장치에 존재하며 실행되기를 기다리는 명령어와 정적인 데이터의 묶음이다. 

이 프로그램의 명령어와 정적 데이터가 메모리에 올라가게 되면 프로세스가 된다.  

프로세스는 실행파일에 있는 명령들을 CPU가 직접 실행할 수 없기 때문에, CPU가 이 명령들을 실행할 수 있도록 먼저 운영체제가 실행 파일의 명령들을 읽어서 메모리에 재구성 하는것이다. 즉 다시 말해 프로세스란 실행 중인 프로그램이다. 


메모리 할당이란?


사용할 메모리 공간을 계획적으로 잘 나누는 것을 말한다. 





정적 메모리 할당


컴파일러가 코드를 기계어로 번역하는 시점에 변수를 저장할 메모리 위치를 배정하는 것 메모리 크기나 개수를 

변경하려면 코드를 변경해야한다.

전역 변수는 프로그램이 종료될 때까지 자신만의 메모리 공간을 가진다. 수명이 프로그램과 같다.

지역 변수는 함수의 호출과 종료가 반복시 다른 메모리 공간에 할당된다. 수명이 함수와 같다.


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


int Test () {

int a, b, c, d;

a = 5;    //START 주소에 5를 넣음

                                               

c = 3;    //START + 8 주소에 3을 넣음

}



int Test () {                    A (){

int a, b, c, d;                 int e;

.....                            }

A();

}


                                                                                      


스택에 대해서


자료 구조의 한 종류이다. 

두 개의 포인터 (bp - base pointer, sp - stack pointer)로 많은 양의 데이터를 효과적으로 관리하는 이론

베이스 포인터를 기준으로 데이터가 추가될 때마다 순서대로 쌓아 올리는 구조이다.

Ollydbg을 이용해서 스택 알아보기

    

PUSH 와 POP

      



컴파일러가 지역 변수를 저장할 메모리 공간을 확보하는 법


void Test (){

int a, b, c;

}


sub sp, 12  //sp값을 12만큼 줄여 공간 확보함  

...

add sp ,12  //sp값을 12만큼 증가시켜 공간을 제거함          



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





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

      

int g_num1, g_num2;


void B(int parm3){

static int s_num3 = 0;

int local4;

}


void A(int parm1, int parm2){

static int s_num2 = 0;

int local3 = 1;

B(local3);

}


void main (){

static int s_num1 = 1;

int local1 =  5, local2 = 3;

A(local1, local2);

}


 


스택 프레임이란


함수를 호출할 때 일어나는 스택의 변화를 일컫는다. 컴파일러가 C언어 코드를 기계어로 번역하는 시점에 결정된다.

지역 변수를 추가하거나, 배열 크기를 변경시 스택프레임이 수정된다.



정적 메모리 할당의 한계


기본 스택 메모리 크기는 1Mbyte를 넘을 수 없다. 

프로그램이 실행되어 함수가 호출될 때까지 스택 크기를 예측하기 어려움이 있다. 메모리의 크기가 하드 코딩되어 있어서 나중에 조절 할 수 없다.


동적 메모리 할당이란?


실행 시간 동안 사용할 메모리 공간을 할당하는 것을 말한다. 맨 처음 프로세스가 실행 중인 프로그램이라고도 한다했다. 즉 이 동적 메모리 할당이란 프로세스에서 더 큰 메모리를 할당할 수 있게 Heap 이라는 공간을 제공한다. 

Heap 은 프로그래머가 원하는 시점과 크기 만큼 할당이 가능하다.



malloc 함수로 동적 메모리 할당하기


Heap 은 변수를 선언하는 행위로 메모리를 할당할 수 없다. C 표준 함수인 malloc 사용해서 메모리를 할당한다.


void * malloc (size_t size);        //size_t는 unsigned int 와 같은 자료형

void * p = malloc(100);             //100바이트의 메모리를 할당하여 포인터 p에 저장함

short *p = (short *)malloc(100);    //총 100바이트를 2바이트* 50개 그룹으로 나눈다.

int *p = (int *) malloc (100);      //총 100바이트를 4바이트* 25개 그룹으로 나눈다.


free 함수로 할당된 메모리 해제하기


Heap에 할당한 메모리는 프로그램이 끝날 때까지 자동으로 해제되지 않는다.

해제를 안해주면 Heap에 메모리를 할당할 공간이 부족해진다.


free(p);    //p가 가지고 있는 주소에 할당된 메모리를 해제해야함


#include <stdio.h>

#include <malloc.h>


void main () {

char *p_name;

p_name = (char*) malloc (32);

if(p_name !=NULL){

printf("your name : ");

gets(g_name);


printf("Hi! %s\n", p_name);

free(p_name);

}

else    printf("Memory Allocation error!!");

}


malloc 함수를 사용시 주의할 점


#include <stdio.h>


void Test(){

short *p =(short *)malloc(100);

}

void main (){

int i;

for(i=0; i<100; i++) Test(); //100바이트씩 100번 동적할당

}


p 가 제거되면 동적 할당된 메모리의 주소 값을 알 수 있는 방법이 없음.


malloc 함수를 사용시 주의할 점


1. 할당되지 않은 메모리를 해제하는 경우


char *p;

//p = (char *)malloc(32);   //포인터 변수 p에 메모리가 할당되지 않았음

free(p);                    //p는 할당된 메모리의 주소를 가지고 있지 않아서 실행할때 오류 발생함


2. 정적으로 할당된 메모리를 해제하는 경우


int data =5;

int * p =&data; //p는 지역 변수 data의 주소를 가지게 됨

free(p);        //p는 힙에 할당된 주소가 아니기 때문에 실행시 오류 발생


3. 할당되는 메모리를 두 번 해제하는 경우


int *p = (int* )malloc (12);

free(p);

free(p);        


동적 메모리 할당의 단점


Heap의 동적으로 메모리를 할당하고 해제하는 작업을 프로그래머가 직접 해야한다.

작은 메모리를 할당해서 사용시 오히려 비효율적일 수 있다.

코드가 정적 메모리가 간결하다.

        

배열과 비슷한 형식으로 동적 메모리 사용하기

        

할당되지 않은 메모리를 해제하는 경우

-> 12바이트 크기의 메모리를 세 그룹으로 나누어서 사용시 처음 4바이트를 

제외한 나머지 8바이트는 어떻게 사용할 수 있을까?


                                                        




동적 메모리를 할당하는 또 다른 방법


int * p = (int *) malloc (sizeof(int) *3);                    //sizeof(int) * 3 = 12


short *p = (short *)malloc (sizeof(short) * 6);        //sizeof(short) * 6 = 12




정적 메모리 할당을 사용시 발생할 수 있는 문제점  


배열을 사용시 메모리가 스택에 정적으로 할당된다. 이 때 배열의 크기를 나타내는 data_size가 들어가는 부분 변수가 아닌 상수로만 할당할 수 있다. 따라서 아래와 같이 선언하면 오류가 발생하게 된다.


int data_size = 5;

int data[data_size] //오류 변수가 오면 값이 뭔지 몰라서 컴파일러가 크기를 결정할 수 없다.


int data[5] //배열의 크기에는 변수가 아닌 상수가 들어가야 한다.


정적 메모리 할당을 사용하여 숫자를 입력 받아 합산하기


#include <stdio.h>

#define MAX_COUNT 5


void main () {

int num[MAX_COUNT], count = 0, sum = 0, i; // 이때 위에서 언급한 부분을 생각해보자. 

    // MAX_COUNT는 변수가 아닌 상수이다. 이유는 

#define이라는 키워드를 사용했기 때문이다.(이 키워드 모르면

http://jihoon6078.tistory.com/64 에서 전처리기 참고)

while(count < MAX_COUNT) {

printf("숫자를 입력하세요 (9999를 누르면 종료) : ");

scanf("%d", num + count);

if(num[count] == 9999)break;

count++;

}


for(i=0; i< count; i++){

if(i>0) printf(" + ");

printf(" %d", num[i]);

sum = sum + num[i];

}

printf(" =%d\n", sum);

}



동적 메모리 할당을 사용하여 숫자를 입력 받아 합산하기


#include <stdio.h>

#include <malloc.h>

void main (){

int *p_num_list, count = 0, sum = 0, limit = 0, i;

printf("사용할 최대 개수를 입력하세요 : ");

scanf("%d", &limit);

p_num_list = (int *) malloc(sizeof(int)*limit);        

//사용자가 입력한 개수만큼 정수를 저장할 메모리를 할당


while(count < limit){

printf("숫자를 입력하세요 (9999를 입력시 종료) :" );

scanf("%d", p_num_list + count);

if( *(p_num_list + count) == 9999) break;

count++;

}


for(i =0; i <count; i++){

if(i > 0) printf(" + ");

printf("%d", *(p_num_list + i) );

sum = sum + *(p_num_list + i);

}

printf(" = %d\n", sum);

free(p_num_list);            //사용했던 힙메모리를 제거

}



다차원 포인터


다차원 포인터 개념


간접으로 여러 번 가리키는 포인터

차원 : 자신이 가리키는 대상의 개수만큼 증가

2차원 : 가리키는 대상이 2개, 1차원 : 가리키는 대상이 1개, 0차원 : 대상 그 자체

모든 차원의 포인트 크기는 4바이트이다.


다차원 포인터 정의


키워드를 두 개 이상 사용해서 선언한 포인터이다. 



* 키워드는 최대 7개 사용가능

포인터 변수를 선언할때 사용한 * 키워드 개수만큼 * 연산자 사용가능





*키워드 3개 사용하여 선언하면 4가지 표현 사용 가능




일반 변수의 한계와 다차원 포인터


주소 값을 저장할 수 있는 크기라면 주소 저장 가능

int 형 변수를 &연산자를 사용하여 다른 변수의 주소 값 저장 가능

포인터 변수가 아닌 경우 *연산자 사용 불가

일반 변수에 저장된 주소로 이동하여 값을 대입하거나 읽기 불가

포인터 변수는 자신이 저장하고 있는주소에 가서 값을 읽거나 대입 가능

          -> 일반 변수에 주소를 저장하지 않고 포인터 변수에 주소 값을 저장하는 이유


2차원 포인터


2차원 포인터의 선언과 사용


* 키워드 두개를 사용하여 선언  short **pptr;

* 연산자 최대 2개까지 사용 가능 pptr, *pptr, **pptr

주소 이동 두번 가능



*pptr 를 사용하면 변수 pptr에 저장되어 있는 주소로 이동하여 저장된 주소 값을 읽거나 저장 가능하다.

**pptr를 사용하면 pptr가 가리키는 대상에 저장된 주소 값을 대상으로 사용한다.


2차원 포인터의 구성


2차원 포인터는 두번 이동 가능

첫 번째 가리키는 대상에는 최종 대상의 주소 값 저장 

2차원 포인터는 1차원 포인터 변수를 가리키는 것이 안정적인 구조이다.

2차원 포인터가 가리키는 대상이 주소 값이 아닌 다른 값을 가진 경우 오류 발생한다.

다차원 포인터으로 좋은 구조

오른쪽으로 하나씩 이동할 때마다 차원을 하나씩 줄여주는 구조



                                        




        

1차원 포인터 변수에 1차원 포인터 변수의 주소를 저장하면?

        

1차원 포인터 변수를 사용하여 다른 1차원 포인터 변수의 주소 값 저장 가능

1차원 포인터 변수는 * 연산자 1개만 사용가능

-> 아래의 코드 처럼 1차원 포인터 q는 일반 변수 data까지 이동하지 못한다.

2차원 포인터를 사용하는 구조는 비슷(q -> p -> data)하지만 실용성이 떨어진다.


int *q, *p, data =3;

p = &data;        //변수 data의 주소가 1차원 포인터 p에 저장

q = (int *)&p;    //변수 data의 주소가 들어 있는 1차원 포인터 p의 주소를 형변환후, 

   1차원 포인터 q에 저장

        


2차원 포인터가 가리키는 첫 대상이 일반 변수

        

일반 변수가 다른 변수의 주소값을 저장하고 있다면 구조적으로는 문제 없음

각 변수 간에 자료형을 맞추기 위해 형 변환해야함

일반 변수는 *연산자 사용 불가 그 다음 일반 변수로 이동 불가

2차원 포인터가 가리키는 첫 번째 일반 변수의 크기가 4바이트이고 두 번째 대상의 주소 값을 가지고 

있다면 두번째 대상을 가리킬 수 있음


2차원 포인터가 가리키는 대상을 동적으로 할당하기

        

2차원 포인터가 가리키는 대상을 malloc 함수로 동적 할당하여 사용 가능

- >가리키는 첫 대상이 4바이트 크기의 주소 값만 저장되면 사용 가능


short **pp, data=3;

pp = (short **)malloc(4);

*pp = &data;

**pp = 5;


동적으로 할당된 메모리는 포인터 X *연산자 사용 불가하다

동적 할당된 메모리는 간접적으로 1차원 포인터처럼 사용

동적 할당 시 의미 분명하게 전달하는 방법

동적으로 할당한 메모리를 해제하는 순서가 매우 중요


2차원 포인터가 가리키는 대상을 동적으로 할당하면 좋은 점

        

2차원 포인터가 가리크는 대상을 malloc 함수로 여러 개 동적 할당하여 사용 가능

접근하고 싶으면 포인터의 주소 연산을 사용하면 된다.


short **pp = (short**)malloc(3*sizeof(short*));

//할당된 포인터에 접근하려면 *(*pp+0), *(*pp+1), *(pp+2)

      


댓글