C언어/stdlib.h

[C언어] malloc 함수 (heap 메모리 할당)

아무일도없었다 2023. 3. 1. 22:26

사용범위

Windows, Unix 등 모든 OS에서 사용가능한 표준 API 함수

기능

C언어 표준 함수로 동적메모리(Heap Memory) 할당을 받기 위해 사용한다.

헤더

#include <stdlib.h>

※ 함수 사용 시 stdlib.h 파일을 include 하지 않는다면 컴파일 시 error 발생 ※


함수

void* malloc(size_t size);

파라미터

  • size_t size
    • malloc 함수를 통해 할당받을 memory의 크기를 입력한다.

반환값

성공 시   유효한 포인터 주소를 반환한다. 
실패 시   NULL을 반환한다. 

 

Memory 가 부족한 경우 malloc 함수가 실패할 수 있다.

추가로 Memory가 남아있지만 지속적인 메모리 할당 & 해제를 반복하다 보면 내부적으로 메모리가 파편화되어 용량이 남아있어도 실패하는 경우도 드물게 존재한다.

따라서 반드시 malloc 사용시에는 NULL 포인터가 반환되는지 확인을 해야 한다.

 


잡학지식

 

C언어는 가비지 컬렉션(GC)가 없기 때문에 malloc으로 할당받은 heap 메모리의 관리는 개발자의 몫이 된다.

 

이는 양날의 검으로 잘 사용한다면 엄청난 효율의 메모리 관리가 가능하지만, 반대로 말하면 잘못사용할 경우 굉장히 비효율적인 메모리 사용을 할 수 있고, memory leak과 같은 굉장히 크리티컬한 문제도 발생할 가능성이 있기 때문이다.

 

따라서 malloc으로 할당받은 포인터는 반드시 free 함수를 사용하여 메모리를 해제해야 한다.

 

물론 메모리 재사용 관련된 로직을 사용한다면 free를 하지 않아도 되는 거 아닌가?라는 생각을 할 수 있지만 원칙적으로는 프로세스가 종료되기 전 반드시 heap 메모리는 free를 통해 해제해야 한다.

 

하지만 요즘 OS들은 모두 똑똑해서 프로세스 종료 시 할당받은 heap 메모리를 모두 정리해 준다.

(먼 옛날 과거 OS 중에서는 프로세스가 종료되어도 정리 안 해주는 녀석도 있었다고 한다...)

 

 


 

malloc 함수를 통해서 할당받은 메모리 영역은 초기화가 안된 쓰레기값으로 가득 차있다. 

 

따라서 보통은 malloc 함수를 통해 메모리를 할당받은 후 memset 함수를 사용해서 초기화를 진행한다.

 

하지만 malloc + memset 의 속도보다 calloc 함수를 통한 초기화된 heap 영역 할당하는 속도가 더 빠르다.

 

따라서 malloc + memset 보다는 calloc 함수 사용을 추천한다.

 

그렇다면 언제 malloc을 사용해야 할까?

 

malloc 함수는 전체 초기화가 필요하지 않은 경우에 사용하면 calloc 보다 효율적이다.

 

typedef struct {
    int a;
    int b;
} test_str_t;

int main()
{
    test_str_t *heap = NULL;

    heap = (test_str_t*)malloc(sizeof(test_str_t));
    if(heap) {
        heap->a = 1;
        heap->b = 2;
        
        ...
        
        free(heap);
    }

    return 0;
}

 

위에 예시코드에서는 malloc 함수를 통해 할당받은 영역의 모든 변수에 값을 할당했기 때문에 초기화를 하지 않아도 상관없게 된다.

 

따라서 이러한 case에서는 calloc 보다 malloc이 더 효율적이게 된다.

 


 

man page에서는 malloc 함수에 0을 전달할 경우 NULL 또는 Unique한 포인터 값을 반환할 수도 있다고 나와있다.

(https://man7.org/linux/man-pages/man3/malloc.3.html)

 

malloc(3) - Linux manual page

malloc(3) — Linux manual page MALLOC(3) Linux Programmer's Manual MALLOC(3) NAME         top malloc, free, calloc, realloc, reallocarray - allocate and free dynamic memory SYNOPSIS         top #include void *malloc(size_t size); void free(void *p

man7.org

 

 

NULL이 나왔다면 할당에 실패했다는 것이고 다시 말해 크기가 0인 메모리는 할당 불가능하다로 이해하였다.

 

그렇다면 어떠한 포인터 주소는 무엇일까?

 

실제로 테스트해 본 결과 malloc(0)을 해서 NULL 이 아닌 포인터가 반환되었고, memcpy를 사용하여 값을 Write하고 Read 해봤는데 정상적으로 동작하였다.

 

하지만 실제로 사용하는 것을 권장하지 않는다

 

가장 큰 이유 중 하나는 malloc(0)을 통해 전달받은 영역의 정확한 size를 모르기 때문이다. (Heap overflow 버그로 문제가 생기면 정말 골치가 아프다..)

 

두 번째로는 이 영역을 사용해도 되는가에 대한 정확한 문서가 없기 때문이다.

 

다만 할당받은 포인터의 경우 free() 함수로 넘겨도 된다는 정의가 있기 때문에 사용하지 말고 free로 넘겨야 한다.

 

하지만 malloc() 함수의 파라미터에 0을 넣지 않도록 예외처리를 처음부터 하는 것이 더 합리적으로 보인다.

 


 

<소스 코드>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX_NAME_LEN  32
#define MAX_VALUE_LEN 64

typedef struct {
    int idx;
    char name[MAX_NAME_LEN];
    char value[MAX_VALUE_LEN];
} my_str_t;

int main() {
    my_str_t *heap = NULL;
    
    heap = (my_str_t *)malloc(sizeof(my_str_t));
    if(heap != NULL) {
    	memset(heap, 0, sizeof(my_str_t));
        heap->idx = 1;
        strncpy(heap->name, "Hacker", MAX_NAME_LEN - 1);
        strncpy(heap->value, "Park", MAX_VALUE_LEN - 1);
        
        printf("idx[%d] name[%s] value[%s]\n", heap->idx, heap->name, heap->value);
    }
    
    free(heap);
    
    return 0;
}

 

※ 실행 결과

idx[1] name[Hacker] value[Park]

 

반응형