게시판 즐겨찾기
편집
드래그 앤 드롭으로
즐겨찾기 아이콘 위치 수정이 가능합니다.
[C언어] Memory Allocation (malloc)에 관해서
게시물ID : programmer_13292짧은주소 복사하기
작성자 : Haskell
추천 : 11
조회수 : 1172회
댓글수 : 23개
등록시간 : 2015/09/14 23:42:54
옵션
  • 창작글
  • 외부펌금지
틀린 내용이 있을 수도 있으니, 자세한 내용은 더 검색해보시길 바랍니다...ㅎㅎ (그리고 덧글로 틀린부분 태클 걸어주시면 겸허히 고치겠습니다.)
오유 가입하고 처음 적는 글이 팁이네요... @_@ (이런 진지한 글 오유랑 안어울리는거 같아요... ;ㅁ;)

들어가며...


C언어를 공부하는 과정에서 배열을 배우시고 나면 이런 질문을 던지시게 됩니다. 
대표적으로 "배열의 크기를 입력받아 할당하는 방법은 없을까?" 이런 질문 말이죠...ㅎㅎ
일단 질문에 대한 답으로 다음과 같은 코드를 작성할 수 있습니다. (꽤 의미깊은 코드임)

#include <stdio.h>

#include <string.h>


int main(int argc, const char * argv[]) {

    

    int i = 0;

    unsigned long arr_size  = 0;

    

    printf("생성할 배열의 크기를 입력해주세요: ");

    scanf("%lu", &arr_size);

    

    int arr[arr_size]; memset(arr, 0, arr_size);

    

    printf("\n");

    

    for(int i = 0; i < arr_size; i++) {

        printf("배열에 추가할 %d번째 숫자 입력: ", i);

        scanf("%d", &arr[i]);

    }

    

    

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

        printf("배열에 추가할 %d번째 숫자: %d\n", i, arr[i]);

    }

    

    return 0;

}

일단 어떤 목적으로 작성한 코드인지 간단히 설명해드리자면 아래와 같습니다.
  1. 배열의 크기를 사용자로 부터 입력받아 arr_size에 저장
  2. arr 배열을 1번에서 입력받은 arr_size로 초기화
  3. memset을 사용하여 배열의 모든 값을 0으로 초기화
  4. 배열에 사용자가 입력한 정수 데이터를 저장 (첫번째 for loop)
  5. 배열에 저장된 데이터를 화면에 출력 (두번째 for loop)
저 코드가 약간 논란이 될 수도 있는데요, 그 이유는 컴파일러에 따라 컴파일이 될 수도 있고, 안될 수도 있기 때문입니다.
조금더 상세히 말씀드리자면, C99 Standard를 지원하는 컴파일러는 위의 코드가 컴파일이 잘 됩니다만, C99 Standard를 아직 지원하지 않는 컴파일러는 2번과 4번에서 오류가 날거에요.

2번은 Variable Length Arrays (줄여서 VLA) 라고 가변길이배열인데, 원래는 입력 받은 크기를 malloc 이라는 Memory Allocation 함수를 사용하여 크기만큼 메모리를 가변적으로 할당하여야 했습니다만 이젠 컴파일러가 해줍니다.
4번은 타 언어처럼 For 루프 선언 부분에서도 저렇게 변수를 선언할 수 있게 되었습니다. (원래는 위에 i 선언해두고 시작했어야했죠...ㅎㅎ)

뭐 C99 이후부터 C++과 비슷하게 저런 것들이 가능하니 저거 컴파일 되신다면 사실 이문서 읽지 마시고 저거 쓰시면 됩니다. (끝)
근데 교수님들이 저런거 안좋아하시고 또 잘 모르십니다... (보통 80년대 중반에 대학다니셨던 분들이셔서...)

일단 위의 코드는 그냥 요즘 C는 이런게 된다~ 이런걸 보여드리려고 작성해둔 것이고요ㅎㅎ
여기에서는 일단 C99의 신기술(?)을 사용하지 않고 구현하는 방법에 대해 알아보도록 하겠습니다.

일단 배열이 뭐시당가?


배열은 간단히 말해서 연속된 자료형이라 할 수 있고, 내부의 처리 루틴을 기반으로 말하자면 메모리의 연속이라고 말할 수 있습니다.
아니 그게 무슨소리요? 하실건데요, 아래 소스코드를 한번 컴파일하여 실행해보시죠..ㅎㅎ

#include <stdio.h>


int main(int argc, const char * argv[]) {

    

    int b[5] = {01234};

    int i = 0;

    

    printf("b = %p = %d\n", b, *b);

    

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

        printf("b[%d] = %p = %d\n", i, &b[i], b[i]);

    }

    

    printf("\n");

    

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

        printf("b[%d] = %p = %d\n", i, b+i, *b+i);

    }

    

    return 0;

}

첫번째 루프는 기존 알고있었던 배열의 접근 방식으로 접근을 한 것이고, 두번째 루프는 배열을 포인터 계산을 통해 접근하는 방법입니다.
일단 b[0]과 b는 값부터 주소까지 똑같이 나올 것입니다. 그리고 루프 2개의 출력 값 또한 동일할거에요.

여기서 유의깊게 봐야하는 코드는 바로 두번째 for 루프입니다.
요상하게도 포인터로도 배열의 값에 접근이 가능합니다. 그 이유는 바로 정수형 배열로 선언되어 있는 b가 사실은 포인터 변수이기 때문입니다.
즉, b는 더도말고 덜도말고 그저 배열의 시작주소를 가리키고 있는 포인터 변수입니다. (아마 책에서 읽으셨을거에요. b 자체는 배열의 시작주소를 가리키고 있다고 말이죠)
따라서 내부에서는 "int b[5]"라고 선언하는 순간 b에는 int의 크기에다가 배열의 크기(5)를 곱한 만큼의 공간이 비어있는 어느 메모리 공간의 시작주소가 저장되는 것입니다.
그러므로 위의 코드에서는 "int b[5]"같은 경우에는 연속해서 20바이트의 공간이 비어있는 메모리 공간의 시작주소가 b에 저장된다고 볼 수 있겠습니다.
정리해보자면 배열은 내부적으로는 두번째 루프에서와 같이 포인터로 접근 하는 것인데, 사람이 알아보기 어려우니 보기좋게 하기 위해서 b[0]과 같은 방법으로 접근할 수 있게 해둔 것이죠. (이를 Syntactic Sugaring, '달콤한 문법' 이라고 합니다) 

뭔가 약간의 감이 오지 않습니까?ㅎㅎ

그렇다면 동적할당을 어떻게 구현할 수 있을까?


이제 동적할당을 어떻게 구현 할 수 있을지 고민해봅시다. (저는 컴퓨터에서 문제 해결에서 방법만이 아닌 원리 또한 알아야된다 생각해서요....ㅋㅋㅋ)
아래의 간단한 코드를 32비트 머신에서 최적화 없이 컴파일하여 실행한다고 가정해보도록 하겠습니다.

#include <stdio.h>


int main(int argc, const char * argv[]) {

    

    int a;

    

    return 0;

}

위의 코드를 실행하면 4바이트의 정수형 변수 a가 메모리의 어느 부분에 생성이 될겁니다. 그림으로 그리자면 메모리에 다음과 같이 위치하고 있을거에요. (가정)

001 005 009   013 017  021  025  029 
 a




 

이제 여기다가 정수가 5개정도 들어가는 정수형 배열을 추가적으로 선언하고 싶어요. 일단 정적배열로 선언 해본다면,

#include <stdio.h>


int main(int argc, const char * argv[]) {

    

    int a;

    int b[5];

    

    return 0;

}

그렇다면 코드가 위와같이 될 것이고, 메모리에 다음과 같이 위치하게 될 것입니다. (가정)

001 005 009   013 017  021  025  029 
 a  b [0] b[1] b[2] b[3]  b[4]  b[5]   

이말은 즉, 정적 배열 선언시 005에서 부터 4바이트씩 025까지 크기가 20바이트 만큼의 공간을 가진 배열 b가 선언되게 됩니다. 그리고 b에는 그 20바이트의 시작주소가 들어가게 되는 것이죠.
그렇다면 동적 선언도 저렇게 해버리면 되지 않을까요? 정리해서, 자료형의 크기에서 데이터 개수 만큼의 메모리 공간을 할당받고, 그 시작주소를 변수에 저장하면 되지 않을까요?
네 그렇게 하면 됩니다. (원리가 그거에요) 의외로 간단하쥬?

메모리 동적 할당 함수 : malloc


서론이 조금 길었던 것 같은데요, 이걸 어떻게 할 수 있을까요? "malloc" 이라는 함수를 알아보도록 하죠.

malloc는 이름을 보면 알 수 있듯이 Memory Allocation 의 준말로 메모리 할당에 관여하는 함수입니다. stdlib.h 라는 헤더 파일에 선언되어 있으므로 사용시에는 stdlib.h를 include 해주셔야 합니다.
stdlib.h 내 선언되어있는 malloc의 함수명세는 다음과 같습니다.

void *malloc(size_t);

void이지만(자료형이 정해져있지 않기 때문에) 실제로는 size_t 만큼의 공간을 할당하여 그 시작주소를 반환하는 함수입니다. size_t는 간단하게 바이트 크기라고 생각하시면 됩니다.

예를들어 정수형 자료 6개가 들어갈 수 있는 공간을 할당해본다고 해봅시다. 코드를 한줄 한줄 작성해나가 BOA요~
  1. 먼저 include 부분과 main 함수를 작성합시다.

    #include <stdio.h>

    #include <stdlib.h>


    int main(int argc, const char * argv[]) {

        

        return 0;


    }

  2. 그리고 정수형 자료 6개가 들어있는 공간의 시작주소를 가리켜야 할 포인터 변수 "p"를 하나 선언해봅시다. (p는 포인터 변수이니 스타가 붙어야겠죠?)

    int main(int argc, const char * argv[]) {

        

        int* p;

        

        return 0;

        

    }

  3. 그리고 정수형 자료의 크기를 가진 정수형 변수 "size_int"를 하나 선언해봅시다. ( sizeof 함수 다들 아시죠?)

    int main(int argc, const char * argv[]) {

        

        int *p;

        

        // sizeof(변수 또는 자료형) 인자로 받은 변수 또는 자료형의 크기를 반환하는 함수입니다.

        // 여기서는 정수의 크기 (int)가 필요하므로, sizeof(int)가 되겠죠?

        int size_int = sizeof(int);

        

        return 0;

        

    }

  4. 마지막으로 정수형 자료 6개의 크기를 가진 정수형 변수 "size_int6"를 하나 선언해봅시다. (정수형 자료 6개의 크기는 size_int의 6배와 같겠죠?)

    int main(int argc, const char * argv[]) {

        

        int *p;

        

        int size_int  = sizeof(int);

        int size_int6 = size_int * 6;

        

        return 0;

        

    }

  5. 이제 동적할당을 해보도록 하죠. malloc의 인자는 할당할 메모리의 크기라고 했습니다. 따라서 정수형 자료 6개의 크기를 가진 size_int6을 인자로 넣으면 되겠죠?

    int main(int argc, const char * argv[]) {

        

        int *p;

        

        int size_int  = sizeof(int);

        int size_int6 = size_int * 6;

        

        p = malloc(size_int6);

        

        return 0;

        

    }

  6. 하지만 위의 코드는 뭔가 이상합니다. 찾으셨나요? 네 맞습니다! 자료형이 일치하지 않아요. malloc은 void 이지만, p는 int* 자료형이죠. 형변환을 해줍니다.

    int main(int argc, const char * argv[]) {

        

        int *p;

        

        int size_int  = sizeof(int);

        int size_int6 = size_int * 6;

        

        p = (int *)malloc(size_int6);

        

        return 0;

        

    }

  7. 코드가 약간 지저분 하니 깔끔하게 바꿔볼까요?

    int main(int argc, const char * argv[]) {

        

        int *p;

        p = (int *)malloc(sizeof(int) * 6);

        

        return 0;

        

    }

즉 위와같이 malloc을 사용할 수 있습니다. 이해가 좀 되시나요?ㅎㅎ 이제 빠르게 사용할 수 있도록 일반화 시켜보도록 합시다.

자료형 *p;

p = (자료형 *)malloc(sizeof(자료형) * 할당크기);


그렇다면 자료 접근은 어떻게 할 수 있을까요? 아실 것 같지 않나요? 네~ 배열의 자료를 접근하듯이 자료를 접근하시면 됩니다. (아래는 예제 코드입니다)

#include <stdio.h>

#include <stdlib.h>


int main(int argc, const char * argv[]) {

    

    int* p;

    int  i;

    

    p = (int *)malloc(sizeof(int) * 4);

    

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

        p[i] = i;

    }

    

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

        printf("p[%d] = %d\n", i, p[i]);

    }

    

    return 0;

    

}


의외로 간단하죠?ㅎㅎ 하지만 메모리 동적할당의 단점이 있다면, 코드가 실행 된 이후에 할당된 부분이므로, 사용이 끝나면 직접 해제해주셔야 합니다.
그렇다면 해제는 어떻게 하느냐? 이거도 진짜진짜 간단합니다. "free"라는 함수를 사용하시면 됩니다. 일단 free 함수의 명세를 살펴보도록 하죠.

void free(void *);

리턴 값은 없고요, 파라메터로는 메모리에서 해제할 변수를 넘기시면 됩니다. 즉, 위의 코드에서는 다음과 같이 사용할 수 있겠습니다.

#include <stdio.h>

#include <stdlib.h>


int main(int argc, const char * argv[]) {

    

    int* p;

    int  i;

    

    p = (int *)malloc(sizeof(int) * 4);

    

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

        p[i] = i;

    }

    

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

        printf("p[%d] = %d\n", i, p[i]);

    }

    

    free(p); 

    

    return 0;

    

}

하지만 위는 그렇게 좋은 코드는 아닙니다. "free(p)"를 실행하였다고 하더라도, 메모리가 반환된 이후 그 부분이 사용되기 전까지는 그 값이 남아있기 때문이죠.
따라서 자칫하면 약간의 혼란을 가져올 수 도 있는데요, 따라서 "free(p)" 이후, p가 가지고 있는 주소 자체도 NULL로 바꿔주시는게 좋습니다...ㅎㅎ (그러면 기타 문제를 미연에 방지할 수 있음)

#include <stdio.h>

#include <stdlib.h>


int main(int argc, const char * argv[]) {

    

    int* p;

    int  i;

    

    p = (int *)malloc(sizeof(int) * 4);

    

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

        p[i] = i;

    }

    

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

        printf("p[%d] = %d\n", i, p[i]);

    }

    

    free(p); p = NULL;

    

    return 0;

    

}


결론


게시판을 읽던 도중 질문을 받게되어 작성해본 글인데...., 간만에 글을 써보는 터라 어떨지 모르겠습니다. :b
사실 지금은 C를 안한지 꽤 되어서 대부분 까먹었기도 하고 해서요....ㅋㅋㅋㅋㅋㅋ

그리고 여기서는 malloc 하나만 알아보았지만, realloc 등 메모리에 대한 더 많은 함수들이 있으니  궁금하신 분들은 "C 동적 메모리 할당" 키워드로 구글링 해보시면 더 자세하고 쉽게 기술된 문서들이 많으니 참고하시면 될 것 같습니다.
진짜 기초적인거만 쓴거라서.... =_=;;; 그래도 알찬거 같기도 하고요

적고나니 부끄럽네요 @_@;;; 실력이 좋지도 못한데.... 이렇게 적어보니 공부 더 많이 해야되겠다고 느꼈습니다... ;ㅁ;
열심히 해요 우리......


출처 제가 공부한 것들의 기억....
꼬릿말 보기
전체 추천리스트 보기
새로운 댓글이 없습니다.
새로운 댓글 확인하기
글쓰기
◀뒤로가기
PC버전
맨위로▲
공지 운영 자료창고 청소년보호