Study- MSC/Computer2008. 10. 22. 19:52
  앞선 포스팅에서 잠깐 배열의 고정성을 대체하는 방법으로 동적 할당이 있다는 이야기를 하였다. 이번 포스팅에서는 동적할당을 어떤식으로 사용하게 되는가에 대해서 이야기할 것인데, 그 전에 잠깐 포인터에 대하여 짚고 넘어가자.
  포인터는 크게 두 가지의 의미를 가지고 있다. 첫째, 메모리 주소 그 자체. 둘째, 그 메모리 주소를 가리키는 포인터 변수. 결국은 같은 용도이다. 일반적으로 int a와 같은 방식으로 변수를 선언하면 그 메모리 주소에 대해서는 관심을 가질 필요가 없다. 그냥 변수 이름만 알고 있으면 얼마든지 그 공간에 접근 할 수 있기 때문이다. 하지만 변수의 지역성 등의 문제로 외부 블록에서 한 변수의 공간에 접근하고 싶다면 그 공간의 주소, 즉 포인터를 사용할 수 밖에 없다.(이는 포인터의 사용의 한 예에 불과하다.) 이렇게 어느 메모리의 주소를 얻고 사용할 필요가 있게 된다면 포인터 변수를 이용해서 원하는 곳의 포인터를 얻어서 사용 할 수 있다.(포인터에 관한 자세한 설명은 하지 않겠다. 문법책을 참고하자.)
  자, 위에서 중요한 말을 하였다. '어느 메모리의 주소를 얻고 사용하려면 포인터 변수가 필요하다.' 그런데 분명 일반적인 변수를 생성하면 그 주소에는 관심이 없어도 된다고 하였는데 왜 굳이 주소가 필요한 것일까? 맞는 말이다. 분명 그렇게 따지만 필요할 경우는 외부 블록에서의 필요성을 제외하면 굳이 필요가 없다. 이런 방식이 가능한 이유는 일반적인 변수를 만들 때에는 어느 상황에서든 항상 일정한 '규칙에 맞게' 메모리 주소에 접근하기 때문에 그 규칙만 알면 접근이 가능하기 때문이다.(Stack 이야기를 하는 것이다.) 예를 들어 a라는 주소를 가진 곳에서부터 시작해서 10개의 정수형 배열을 만든다면 a부터 a + sizeof(int)*10 까지 메모리 공간을 주어주면 된다. 우리가 '그 크기'와 '언제 변수를 선언하는지' 알기 때문에 이런 식이 가능한 것이다.
  그러나 '언제' 변수를 만들지 모르고 '얼마나'크기를 설정 할 지 모른다면? 이런 경우에는 어쩔수 없이 메모리 중 빈 공간에(HEAP) 메모리를 할당 할 수 밖에 없다. 그런데 문제는 메모리 공간을 할당해 놓고 어디다 해 놓았는지 모른다는 것이다. 이럴 때 포인터를 이용 할 수 있다. 말이 조금 복잡해졌다. 다음 코드를 분석하면서 한번 정리해 보자.

int score[10];
printf("10명의 점수를 입력해 주세요 : ");
for(int i = 0 ; i < 9 ; i++) scanf("%d",&score[i]);

  자 위 코드는 '10개'라는 고정된 점수를 저장하는 변수를 배열로 만들어 놓고 10번 점수를 입력받는 것이다. 이런 경우에는 10개 이상으로는 받을 수 없게 되고 10개보다 적은 갯수를 받게 되면 나머지 공간은 낭비하게 된다. 우리는 이런 경우를 별로 원하지 않을 것이다. '몇 개를 입력받을지 입력받고 그 갯수만큼만 받는 것은 어떨까?' 다음 코드를 보자.

int num;
int *score;
printf("몇 명을 입력받으시겠습니까? : ");
scanf("%d",&num);
score = malloc(sizeof(int) * num);
printf("%d명의 점수를 입력해 주세요 : ",num);
for(int i = 0 ; i < num ; i++) scanf("%d",&score[i]);

  달라진 것은 몇명을 받을 것인지 저장하는 num변수를 만들고, 그 num만큼 공간을 할당한 후 그 공간만큼만 입력을 받은 것이다. 중요한 것은 malloc라는 함수로 원하는 크기만큼 메모리를 할당받았다는 것이다. 그런데 문제는 이 malloc으로 할당한것은 좋은데 어디다가 해 놓았는지 모른다. 그래서 malloc 함수는 할당 한 후 그 메모리 주소를 리턴하고, score라는 포인터 변수는 그 변수를 받는다. 
  이렇게 생각하자. 동적할당을 했다면 메모리 어딘가의 빈 곳(실제로는 방식이 있지만 이 포스팅의 범위를 벗어나므로 언급하지 않겠다.)에다가 할당해 버린다. 그 곳이 어디인지 즉 어느 메모리 주소인지는 알 수 없다. 다만 할당하는 순간에는 어디인지 알 수 있기 때문에 그 주소를 변수에다 넣게 되는 것이다. 이 변수가 바로 '포인터'변수이다. 포인터는 아까 메모리 주소라고도 이야기 했다. 이것은 일반 변수가 아닌 포인터 변수에만 넣을 수 있기 때문에 score라는 포인터 변수가 필요한 것이다.
  조금 이해가 가는가? score = malloc(sizeof(int) * num);에서 score = malloc();라는 부분을 잘 관찰하자. malloc로 어딘가에 메모리를 잡아 놓고 그 주소를 score에다가 넣어 놓았다. 이렇게 해 놓지 않으면 그 이후에는 절대 어디다 잡아 놓았는지 알 수 없다.
  말이 복잡해졌다. 언제 시간 내서 다시 한번 더 이해하기 쉽도록 포스팅을 수정해 보아야 겠다.(원래 진리일수록 간단할텐데 말이다.) 다음 포스팅에서는 이 동적할당의 개념을 확장시켜서 레퍼런스와 객체의 생성에 대해서 이야기 해 보겠다.
Posted by 머리
Study- MSC/Computer2008. 10. 1. 21:27
  예전에 비트맵을 분석해보려고 이것 저것 소스를 보다가 재밌는 코드를 본 적이 있다.
  비트맵을 파일 입출력으로 직접 읽어서 분석하는게 아니라 메모리를 할당한 후 파일에서 메모리로 전체를 복사시킨 후 파일은 닫고 메모리의 내용만 읽는 코드였다.
  흥미로운 방식이였다. 그러면서 든 생각이 그럼 바로 파일 입출력을 하는 것과 메모리에 올려서 하는 방식과 무엇이 다를까? 하는 것이였다. 그리고 이번 휴가를 통해서 바로 시험해 보았다.

실험 방법 : 어느정도 용량이 되는 TEXT 파일을 스트림에서 직접 읽는 방식과 메모리에 올려서 읽는 방식의 속도를 비교

소스 코드는 다음과 같다.


#include <stdio.h>
#include <stdio.h>
#include <windows.h>//GetTickTIme 호출

#define COUNT 1000000

//메모리를 통해서 파일을 읽는 것과 직접 FIle Stream에서 읽어 오는 것의 속도 비교

int main(void)
{

    FILE* fp;//파일을 읽을 파일 포인터
    char read[100];
    char a;
    char* str;

    unsigned int FileSize;
    int StreamTime;//스트림으로 읽었을 때의 시간
    int MemoryTime;//메모리로 했을 때의 시간

    //읽을 파일을 만드는 부분
    unsigned long i;

    if(fp = fopen("TestFile.txt","w"))
    {
        for(i = 0 ; i < COUNT ; i++)
        {
            fputs("TestFileText\n",fp);
        }

        fclose(fp);
    }


    //File Stream에서 읽기
    if(fp = fopen("TestFIle.txt","r"))
    {
        StreamTime = GetTickCount();  //시간 재기
        while(!feof(fp))
        {
            fgets(read,80,fp);
            printf("%s",read);
        };
        StreamTime = GetTickCount() - StreamTime;
        fclose(fp);
    }

    //Memory에 올린 후 읽기
    if(fp = fopen("TestFIle.txt","r"))
    {

        MemoryTime = GetTickCount();//재기 시작

        fseek(fp,0L,SEEK_END);
        FileSize = ftell(fp);//파일의 크기를 알기 위해 끝까지 이동 후 크기 저장

       
        str = malloc(sizeof(char) * FileSize);//크기 만큼 메모리 할당
        memset(str,0,FileSize);
        fseek(fp, 0L, SEEK_SET);//파일의 처음으로 이동
        fread(str,sizeof(char)*FileSize,1,fp);//파일을 메모리에 복사

        fclose(fp);//다 읽었으니 파일을 닫는다.

        printf("%s",str);//읽은 내용을 출력

        MemoryTime = GetTickCount() - MemoryTime;//측정 끝
        printf("소요 시간 : %d Tick\n",MemoryTime);

        free(str);//메모리 해제
    }

    printf("Stream : %d\nMemory : %d\n",StreamTime,MemoryTime);

    return 0;
}

실험 결과

내용 출력 하는 시간 포함해서 결과는

스트림 직접 읽기 : 45542 Tick
메모리 올려서 읽기 : 10656 Tick

약 23%정도 메모리로 읽는 방식이 더 빨랐다.
프린트 시간을 뺴서 계산 하면 더 정확한 결과를 얻을 수 있었을 것이다.

이번 실험으로 역시 메모리에 올려 놓고 돌리는 것이 속도 면에서는 더 효과적이라는 걸 직접 볼 수 있었다. 이런 것을 이용하면 Progress를 계산한다던가, 리소스 같은 것을 올려 놓고 읽으면 Loading TIme을 줄일 수 있을 것이다.
 
이렇게 메모리에 올리는 방식을 사용하는 예로 더블 버퍼링을 예로 들 수 있을 것이다. 더블 버퍼링도 메모리에 뿌릴 내용을 미리 그려 놓고 화면에 한꺼번에 뿌리는 방식을 이용한다.


그런데 의문점이 있어. 저렇게 하면 효과는 있지만 실제로 메모리에 대용량으로 할당하는 식으로는 잘 안할 거라고 생각해.
malloc()이런 걸로 몇 MB나 하는 걸 할당하는것도 무식하다고 생각하고 요즘 프로그램들 용량 엄청나잖아. 그런걸 보조하기 위해서 페이징같은 것이 있는거고

그래도 위 방법은 어느 정도 규모의 리소스 파일들을 읽을 때는 유용한 방법이 될 거라고 생각하는데, malloc같은 할당 함수 위에 다른 방법은 없는건지, 그리고 HEAP 영역 말고 이런 상황같은 것을 위한 다른 할당 가능 공간은 없는 건지 궁금하다. 한번 알아 봐야 겠다.


Posted by 머리