Category (Click)
개발보드 덕질하기

[2023 방통대 C 스터디] 12. [malloc.h] 양날의 검, 동적 메모리 할당

2023 방통대 C 스터디 강의노트
한국방송통신대학교 컴퓨터과학과의 C언어 스터디에 사용한 자료입니다.
배움은 끝이 없기에 강의노트라 하더라도 오류가 있을 수 있으며, 이 점에 대해서는 둥글게 지적 부탁드립니다.
읽기 전 아래 내용을 숙지하여주시기 바랍니다.
강의 노트에서 중요한 부분은 이렇게 노란색 전구()로 강조합니다.
한국어로 번역했을 때 뜻이 명확하지 않은 일부 용어는 의미를 분명히 하기 위해 한국어로 뜻을 번역하지 않고 영어 원문을 그대로 사용하거나 말음을 한국어로 표기합니다.
강의 노트는 스터디 내용을 대체할 수 없으며 이해를 위한 자세한 내용은 스터디에서 언급합니다.
강의 노트는 Light 테마에서 보시는것을 권장합니다.
스터디는 Windows 11, Visual Studio IDE 환경에서 진행합니다.
mac인 경우 패러렐즈를 이용해 Windows 11 ARM을 설치하여 동일하게 진행할 수 있습니다.
모든 강의 노트의 저작권은 작성자 차완기에게 있으며 무단 복제를 금합니다.

#방통대 2023 C 스터디 강의노트 태그 전체 글보기

변수의 메모리 할당에는 할당 시점에 따라 정적 할당동적 할당 두 가지 방법이 있습니다.
정적 할당은 위 코드와 같이 변수에 사용할 메모리의 공간을 미리 정해두는 것입니다. 이렇게 만들어진 변수들은 컴파일 과정에서 할당될 메모리의 크기가 연산되고 프로그램에 저장됩니다. 정적으로 할당된 변수들은 크기가 고정되기 때문에 공간이 부족한 경우 늘릴 방법이 없습니다.
이와 반면에 동적 할당은 malloc.h동적 메모리 할당 함수를 이용해 상황에 맞게 메모리의 공간을 조절할 수 있습니다. 지금 사용하고 있는 Notion뿐만 아니라 브라우저, 게임 등 대부분의 프로그램에서 필요한 메모리의 공간을 미리 알 수 없기 때문에 메모리를 동적으로 할당하는 기법을 사용합니다.

메모리 할당 - malloc()

memory allocation의 약자인 malloc() 함수는 지정한 크기의 메모리 블록을 할당하고, 해당 메모리 블록의 시작 주소를 반환합니다.
void* malloc(size_t size);
C
복사
size : 할당할 메모리 공간(byte단위)
반환값: 할당된 메모리 블록의 시작 주소. 할당 실패 시 NULL 반환
만약 int형 변수 하나를 위한 공간을 생성한다면 인자값으로 sizeof(int)를 입력하고, 2칸의 int형 배열을 입력한다면 sizeof(int) * 2를 입력하면 됩니다. 이때 반환값은 void * 타입이기 때문에 타입 지정 포인터에 저장하기 위해서는 형 변환이 필요합니다.
코드
위 예제는 문자열 “Hello, World!”가 저장된 배열 str_p를 다른 메모리 공간에 복사한 후 복사된 배열의 포인터를 char형 포인터 변수 str_cpy_p에 저장하는 예제입니다.
9번과 10번 라인에서 str 문자열의 길이와 메모리에서 차지하는 공간을 계산하고 12번 라인에서 mem_size 만큼의 메모리 블럭을 생성해 포인터를 str_cpy_p에 저장합니다. 그 후 13번 라인에서 str의 내용을 str_cpy_p에 복사하고 14번 라인에서 str_cpy_p의 문자열 마지막 위치에 NULL문자를 삽입합니다.
출력 결과는 아래와 같습니다.
original: Hello, World! copied : Hello, World!
Plain Text
복사
malloc()을 포함해 앞으로 다룰 메모리 동적 할당 함수들은 메모리 할당에 실패하면 NULL을 반환합니다. 위 사진과 같이 컴퓨터에 장착된 메모리보다 더 큰 100 GB의 공간을 할당하려 하면 메모리 할당에 실패하게 되어 NULL을 반환하며, if문을 이용해 할당 실패 여부를 모니터링해야 합니다.

메모리 할당 및 초기화 - calloc()

calloc() 함수는 malloc() 함수에 할당된 공간을 0으로 설정하는 memset() 함수가 추가된 버전이라고 생각하면 됩니다. 구조체 생성화 같이 메모리 할당과 동시에 0으로 초기화를 해야 하는 경우 주로 사용합니다.
입력 방식이 malloc()과 다르기 때문에 조심하여야 합니다.
void* calloc(size_t num, size_t size);
C
복사
num : 할당하고자 하는 공간의 수
size : 각 공간의 메모리 크기(byte단위)
반환값: 할당된 메모리 블록의 시작 주소. 할당 실패 시 NULL 반환
코드
코드
위 두 코드는 동일일한 동작을 수행하며, 아래와 같은 결과를 출력합니다.
0000000000
Plain Text
복사

메모리 재할당 - realloc()

이미 할당된 메모리의 크기를 늘리거나 줄일 때는 이름부터 reallocation의 약자인 realloc() 함수를 사용합니다. realloc()함수는 함께 입력한 크기 만큼의 새 공간을 생성(malloc())하고 기존 내용을 복사(memcpy())하는 과정을 거칩니다.
메모리 재할당은 사용하지 않는 메모리 블럭을 찾고, 메모리를 복사하는 과정을 거치기 때문에 오버해드가 큽니다. 오버해드가 클 수록 전체 코드의 실행 속도가 느려지며, 오버해드가 큰 함수를 “비용이 비싸다” 라고 말합니다.
realloc()는 메모리 단편화가 발생할 수 있어 자주 사용하지 않는것이 좋습니다.
이를 해결하기 위한 방법으로는 구조체를 생성해 구조체 안에 다음 데이터의 포인터를 저장하게 하는 방법이 있습니다. 이러한 방법을 활용하면 메모리를 재할당하는 대신 새로운 메모리를 할당하고 기존 구조체에 새로 할당된 메모리의 포인터만 입력하면 되어서 효율적입니다.
void* realloc(void* ptr, size_t size);
C
복사
ptr : 크기를 변경하고자 하는 메모리 블록의 시작 주소(포인터)
size : 변경하고자 하는 메모리 블록의크기(byte단위)
반환값 : 변경된 메모리 블록의 시작 주소를 void 포인터 형태로 반환합니다. 실패 시 NULL 포인터를 반환합니다.
코드
위 코드의 출력은 아래와 같습니다.
id: 1 name: max age: 20 id: 2 name: min age: 10 id: 3 name: noo age: 300
Plain Text
복사

free()

메모리를 사용하지 않을때에는 메모리의 할당을 해제해줄 수 있습니다. 이를 위한 함수가 바로 free()입니다.
void free(void* ptr);
C
복사
ptr : 해제하고자 하는 메모리 블록의 시작 주소(포인터)
free()메모리 누수를 막는 아주 중요한 함수로, 아래 내용을 통해 중요한 이유를 알아보도록 하겠습니다.
코드
위 코드는 for문 안에서 각 루프마다 10000칸짜리 char형 배열을 생성하는 예시입니다. 6번 라인에서 동적할당한 메모리의 시작점(포인터)을 포인터 변수인 string_data_p에 저장하는것을 알 수 있는데요, 동작을 끝낸 10번 라인에 도달하게 되면 지역변수인 string_data_p가 사라지고 다음 루프가 다시 실행되게 됩니다.
string_data_p가 사라지게 되면 불필요한 메모리 공간이 계속 남아있지만 데이터의 시작점(포인터)을 모르기 때문에 운영체제는 메모리가 낭비되고 있다는 점을 인식하지 못하는데요, 이러한 현상을 메모리 누수라 합니다.
실제로 코드를 실행해 보면 위 영상처럼 프로그램에서 사용하고 있는 메모리가 계속적으로 증가하고 있음을 확인할 수 있습니다.
사실 운영체제의 메모리 최적화 기법중 하나인 Garbage Collection에 의해 할당하고 잃어버린 메모리를 자동으로 해제하기는 하는데요, 완벽하지 않고 큰 성능 저하를 발생시킬 수 있어 프로그램이 종료되기 전 메모리의 할당을 해제하는편이 좋습니다. 파이썬은 신경안써도 자동으로 해줍니다..
코드
그럼 위의 코드를 이렇게 수정해보도록 하겠습니다.
메모리가 낭비되지 않아 사용량이 증가하지 않는 것을 확인할 수 있습니다.