크래프톤정글4주차 - 동적 메모리 할당, malloc

김태성·2024년 2월 3일
0
post-thumbnail

동적 메모리 할당

메모리에는 사람이 간섭하지 못하는 부분이 있다.
그것은 heap 메모리인데, 여기에 있는 데이터를 변화를 시키지 못한다.

하지만 이것만 들으면 이해가 잘 되지 않을것이니 우리는 stack 과 heap, 그리고 data의 차이점을 알아야 한다.

정적 세그먼트 - code, data 영역
1. code

  • 제일 밑의 text가 code 부분이다. 쉽게 말하면 우리가 작성한 소스코드가 들어가는 공간이며, 기계어도 포함된다. 프로세스 종료시까지 계속 유지가 된다.
  1. data
  • global, static, array, structure 등이 저장된다.
    global, array, structure 등은 파이썬에서도 쓰니까 넘어가고(structure는 파이썬의 dictionary같은거라고 생각하면 된다.)
    여기서 static이 정적변수, 그러니까 int 같은것이다.

동적 세그먼트 - heap, stack
3. heap

  • 프로그래머가 동적으로 사용하는 영역이다.
    malloc, free, new, delete 등에 의해 할당/반환되는 영역이다.
  1. stack
  • 지역변수, 매개변수, 복귀 번지 등이 저장되어있는 프로그램이 자동으로 사용하는 임시메모리이다.
    함수호출을 할때 생성되고, 종료할때 반환된다.

주의

위 그림에도 나와있듯이 한정된 메모리에 stack 과 heap 을 나눠쓰기 때문에 데이터 관리를 잘못하면 오버플로우가 되서 프로그램이 재대로 작동하지 못한다.






그럼 여기서 궁금증이 생기는데, heap영역은 왜 굳이 쓰는 것일까?
그 이유를 알아보자.

heap 영역을 사용할때의 장점

  • 프로그램에 필요한 개체의 개수나 크기를 미리 알 수 없는 경우에 사용 가능.
  • 개체가 너무 커서 스택 할당자에 맞지 않는 경우 사용 가능.
  • 런타임에 크기가 결정되는 동적 배열 및 리스트와 같은 경우는 힙을 사용하는것이 보다 공간을 효율적으로 사용할 수 있다...
    => 이것에 대한 추가적인 설명으로, 동적 메모리를 관리할때 '메모리 단편화'라는것이 발생한다고 한다.
    하지만 heap는 계속 할당/반환 을 해 주기 때문에 이러한 단편화가 줄어들고, 효율이 올라간다는 것이다.

heap 영역을 사용할때의 단점

  • 할당과 반환을 계속 해줘야 하기 때문에 속도 저하
  • heap 손상시 속도 저하
  • heap 경합으로 인한 속도 저하
  • 잘못 썼을때 stack overflow / heap overflow 가 발생해서 데이터가 오염됨.







여기까지 살펴보았을때 우리는 heap의 특징을 몇 개 알게 되었다.

  • heap은 동적 메모리를 효율적으로 관리하는 영역이다.(malloc)

  • heap은 stack과 같은 메모리를 서로 갈라먹고 있기 때문에 한쪽이 넘치면 오버플로우가 발생한다.

  • heap은 관리하는데 추가적인 작업이 필요하지만, 동적 배열/리스트에서 발생하는 '메모리 단편화'를 줄여 효율을 높힌다.

  • 또한 프로그램에서 필요한 개체의 개수/크기가 모를때도 사용이 가능하다.

한마디로 요약하자면 프로그램을 사용할때 계속해서 변하는 '동적메모리' 라는 것이 있고,
이것을 효율적으로 관리하기 위한 메모리 구조라고 생각하면 되는 것 같다.

그리고 여기서 heap이 왜 불변성을 가지는 것인가 에 대한 궁금증이 생기는데

이 그림 하나로 모든 설명이 가능한다.

간단하게 요약하자면

  • thread 는 고유 stack 과 register를 배정받아 실행이 된다.

  • 하지만 text,data,heap 은 서로 공유를 하기 때문에 한쪽 thread에서 멋대로 변경을 해버리면
    멀티 쓰레드 방식에서 감당할 수 없는 오류가 발생할 가능성이 생기기 때문이다.

malloc 함수

malloc은 악명이 상당히 높다.
잘못 사용하면 메모리가 질질 새버리며 오버플로우가 터져서 런타임을 길게 잡지도 못하게 된다.



잠깐 다른 이야기를 하자면
오래된 게임이 지속적인 업데이트를 계속 하게 되면
초기 개발자들이 짜놓은 코드가 어떻게 굴러가는지, 어떤식으로 작동되는지를 정확하게 파악하지 못하고
메모리 할당에 실수가 생기게 되는데, 이때 런타임이 길어지면 메모리 누수가 심각해져서
재접속을 해야 하거나 게임을 도저히 플레이 할 수 없는 상황도 만들어지게 된다.

그 대표적인 예가 던전엔파이터이다.

인터넷에 돌아다니는 짤 중 하나를 긁어왔다. 최근에 발생한 메모리 누수 이슈인데
뭔놈의 던파가 7기가나 먹고있는게 보인다.
이건 지금 처음 발생한 일도 아니고 옛날 안톤시절부터 내려오던 던파의 전통같은거다.
오죽하면 강정호 디렉터 시절 시로코챌린지 ~ 검은연옥 사이 시절에 메모리 누수가 너무 심해서
디렉터가 특별인력을 배정해서 메모리 누수를 적극적으로 잡겠다고 말까지 했으니까 말이다.

부디 그곳에서는 행복하길 바랍니다



다시 본론으로 돌아와서
메모리 누수는 발생하기 시작하면 상당히 골치아픈 상황이다.
이것은 게임을 하는 플레이어 뿐만 아니라 서비스를 제공하는 기업 입장에서도
반드시 잡아야 하는 큰 이슈라고 생각하며, 특히나 런타임이 긴 프로그램이라면 더더욱 중요해진다.

하지만 더욱더 골 아파지는건 malloc 함수 자체는 엄청나게 간단하다는것이다.
그러니까 우리는 앞으로 여러번 실패하고 많은 사례를 접해야 할 것이다.

malloc()

보통 동적메모리 할당을 할 때 malloc 계열 함수들을 사용한다.
stdlib.h 헤더에 정의되어 있으니 선언해주면 된다.

malloc()에는 독특한 특징이 있는데

void* malloc(size_t size); // malloc 원형

이렇게 void* 를 리턴한다.
그래서 이 void값을 내가 원하는 타입으로 형변환 해서 적절하게 쓰면 된다.

왜 void를 쓰는가? 라고 궁금증이 생길건데
어떤 자료형을 저장하느냐에 따라 계속 변형을 해야되는데, 자료형에 맞게 일일히 선언하는것이 힘들기 때문이다.

그러니까 우리는 int형으로 받고 싶다면

int* a = (int*b)malloc(sizeof(int)*10); 

이런식으로 쓰면 된다는 것이다.

calloc()

calloc은 함수를 먼저 보자.

void* calloc(size_t n, size_t size);

이렇게 n,size를 각각 입력해주면 된다.
malloc과 같이 n은 몇개가 들어가는지
size는 할당할 크기를 적어부면 된다.

예시를 들자면
2바이트 20개 / 4바이트 10개 등등
원하는대로 하면 된다.

하지만 malloc 과의 결정적인 차이가 있는데,
calloc을 선언하게 되면 할당된 메모리가 0으로 초기화 된다는 점이다!

realloc()

malloc과 calloc으로 선언한 메모리를 더 늘리거나 줄이기 위해서 사용된다.
예를들어서 malloc으로 14칸의 메모리를 할당했으나 6칸을 더 주고싶다
하면 realloc을 사용하면 된다.
함수의 원형을 보자.

void* realloc(void* p, size_t size);

free()

드디어 마지막으로 사용하는 free 함수이다.

void free (void* p)

이렇게 하면 할당을 해제하게 된다.

profile
닭이 되고싶은 병아리

0개의 댓글

관련 채용 정보