본문 바로가기
C,C++/C++

[C/C++] malloc과 new의 차이와 동작 원리

by woohyeon 2020. 4. 7.
반응형

개인적으로 학습용으로 작성하는 내용이라 틀린 부분이 있을 수 있습니다.


[malloc 함수와 new 연산자]
우선 표면적인 가장 큰 차이는 malloc은 함수new는 연산자(operator)라는 것이다.

malloc 함수와 new 연산자

 

malloc 함수는 바이트 단위의 Size를 인자로 받아 사용 가능한 메모리 공간의 시작 주소를 반환해준다. 물론 malloc은 메모리 공간의 요소들을 자동으로 초기화해주지 않는다. 기본적으로 쓰레기 값을 가진 메모리 주소를 반환한다. 특정 값으로 초기화를 원한다면 calloc 함수를 사용할 수 있다. 만약 Size로 들어온 크기만큼 할당할 수 없다면 NULL을 반환한다.

malloc은 오로지 POD(Plain Old Data) 타입에 대해서만 크기를 할당해줄 수 있다. POD 타입이란 쉽게 말해 연속된 메모리를 가진 자료형이다. 더 쉽게 말하면 C와 호환이 가능한 타입을 말한다. char, int, float, double 등 값 타입과 배열 및 포인터 모두 POD 타입이다. 변수로만 이루어진 구조체 또한 연속된 메모리를 가지기 때문에 POD타입이다. 그런데 C++에선 구조체 내부에 함수 선언이 가능하다. 하지만 C에선 구조체 내부에 함수를 선언할 수 없다. 따라서 함수를 가진 구조체는 POD 타입이 아니다. 따라서 클래스 또한 POD 타입이 아니다. 물론 클래스가 생성자를 포함한 어떠한 함수라도 가지지 않으면 클래스도 POD 타입이다. 하지만 이 경우 클래스는 구조체와 같은 개념이 된다. POD 타입은 C++의 <type_traits> 헤더의 is_pod를 이용하여 확인해볼 수 있다.

 

new 연산자newnew[]로 나뉘는데, 전자는 원하는 자료형에 대한 초기값을 인자로 받고, 후자는 배열의 크기를 인자로 받는다.  new 연산자는 실제로 다음과 같이 구현되어 있으며 내부적으로 malloc을 사용한다. 이런 말을 들어봤을지 모르겠지만, C++에서 사용되는 것 중 C에서 없던 기능이라면 내부적으로 C를 통해 구현되어 있을 확률이 높다.   

new 연산자

위 코드는 malloc을 이용하여 할당된 메모리 시작 주소를 block에 반환한다. size는 물론 바이트 단위이며, new 연산자 사용 시 인자로 넣어준 값과는 다르다. 

아래 코드를 보면 new 연산자를 호출하기 전에 스택에 8과 12를 push 해준다. 각 값은 sizeof(자료형) * 배열의 크기이다.  ('0Ch'는 12를 16진수로 나타낸 값이다.)

8, 0Ch(12) 바이트

 

new[] 연산자도 결국은 new 연산자를 호출한다. (malloc은 어차피 바이트 단위로 할당하기 때문에) 

 

다음으로 new 연산자는 메모리 할당과 동시에 초기값을 지정할 수 있는데, 사실 malloc 호출 후 별개로 값을 넣어주는 것뿐이다. 아래 코드를 보면 new 연산자 호출과 별개로 초기값 23(17h)를 넣어주고 있다. eax는 아까 할당받은 메모리 주소가 저장된 block(포인터) 변수이며, 마지막에 block을 pInt1에 넘겨주는 것을 볼 수 있다.

 

참고로 value 타입에 대해 값 초기화를 원하지 않는다면 new int()가 아닌 new int와 같은 방식을 사용한다.

 

만약 new 연산자가 할당하는 타입이 클래스 타입일 경우 클래스의 생성자를 호출한다. 물론 이는 당연히 malloc 호출 후에 별개로 실행되는 과정이다. 즉 new 연산자는 POD 타입은 물론이고 클래스 타입까지 메모리를 할당해줄 수 있다. 

 


아래 내용은 다음 글을 참고하여 작성하였습니다.

https://3dmpengines.tistory.com/1660


[malloc의 동작 원리]
malloc이 내부적으로 어떻게 메모리를 가져오는지 알아보자. 운영체제마다 malloc의 동작이 다를 수 있는데 다음의 내용들은 리눅스 기준이라 윈도우즈도 정확히 이렇다곤 못하겠다. 다만 원리 자체는 비슷할 것이라 생각한다.

우선 시스템 메모리에 직접적으로 관여할 수 있는 주체는 운영체제(커널)이라는 것을 알아야한다. user-level에서 사용하는 메모리 할당 함수는 결국 커널 수준에서 제공하는 시스템 호출(system call)을 통해 할당한다. user-level에서 시스템 호출을 통해 시스템의 자원을 받아올 수 있는 함수로는 리눅스의 brk/sbrk 또는 윈도우즈의 VirtualAlloc이 있는데, 이들은 최소로 할당해줄 수 있는 크기가 페이지(page) 단위로 매우 크다는 특징이 있다. 즉, 이러한 함수들로는 1바이트, 2바이트와 같이 매우 작은 단위의 메모리를 할당해줄 수가 없다.

그런데 우리가 흔히 사용하는 malloc 함수는 왜 위에서 자원을 받아올 수 있는 함수로 언급이 되지 않았을까? 

사실, malloc은 항상 운영체제가 관리하는 시스템 자원을 가져다 주진 않는다. malloc은 보통의 경우 미리 할당된 메모리 풀(memory pool)을 사용하며, 위에서 언급했던 함수들이 프로그램 실행 초기에 만들어준다. 즉, 실행할 프로그램이 메모리에 로드되면 brk, VirtualAlloc와 같은 시스템 호출 함수를 통해 메모리 풀을 사용자 영역에 만들어준다. 그리고 malloc은 사용자 영역에 생성된 메모리 풀의 메모리를 할당해준다.

https://kali-km.tistory.com/entry/%EC%9C%88%EB%8F%84%EC%9A%B0-%EB%A9%94%EB%AA%A8%EB%A6%AC%EA%B5%AC%EC%A1%B0%EC%99%80-%EB%A9%94%EB%AA%A8%EB%A6%AC%EB%B6%84%EC%84%9D-%EA%B8%B0%EC%B4%88

 

윈도우즈에선 이 pool을 프로세스 기본 힙(process defalut heap)이라고 부르는 것 같다. 프로세스 기본 힙이란 프로그램이 메모리에 로드되어 프로세스가 만들어지면 OS가 프로세스 별로 할당해주는 힙 영역인데 리눅스와 비슷해보인다.


메모리 풀(정확한 명칭은 아님)은 힙 관리자(heap manager)에 의해 관리되는데, 초기에 시스템으로부터 받은 큰 메모리 블럭을 또 크기 별로 나누어 관리한다. 메모리 크기가 작은 블럭은 small bin에, 크기가 크다면 large bin이라는 곳으로 분류된다. (단위는 검색해보면 나온다.) 힙 관리자는 best-fit 방식으로 malloc이 요청한 메모리를 할당한다. best-fit이란 요청받은 메모리 블럭과 최대한 비슷한 크기의 메모리를 찾아 할당해주는 방식이다. 여기엔 가장 비슷한 크기의 메모리 블럭을 찾아야 하는 오버헤드가 발생한다.

사용자가 요청한 크기가 작다면 small bin에서, 크다면 large bin에서 메모리 블럭을 찾아 반환해주는데, 만약 반환하는 블럭의 크기가 필요한 크기보다 크다면 블럭을 쪼개서 반환하고 남은 블럭은 다시 원래 메모리에 병합한다. 만약 large bin으로도 감당할 수 없는 크기라면, 그제서야 page 단위로 메모리를 반환하는 mmap과 brk 또는 VirtualAlloc을 호출하여 시스템 자원을 가져다 준다. 




댓글