c언어 malloc 사용 이유 뇌에 넣어드림
c언어 malloc 사용 이유 뇌에 넣어드림 — #malloc #개발자의도구들 #c언어malloc #malloc사용이유 컴퓨터공학과 학사과정 중 공부한 내용을 정리하였...
#malloc #개발자의도구들 #c언어malloc #malloc사용이유
컴퓨터공학과 학사과정 중 공부한 내용을 정리하였습니다.
\* 본글은 PC버전에 최적화 되어있습니다.
C언어의 장벽
C언어 입문할 때 가장 어려운(?) 개념이 뭐냐고 묻는다면, malloc과 pointer라고 생각됩니다. 이유는 C는 일반적으로 사용되는 언어와 달리, 컴퓨터 구조나 OS에 대한 지식이 충분히 있어야 되기 때문립니다. malloc과 Pointer를 제대로 이해하려면 메모리에 대한 이해가 전반적으로 있어야합니다.
오늘은 간단한 메모리 관련 지식과, C에서 malloc을 왜 사용하는지, 어떻게 사용하는지, 사용하면 어떻게 되는지 등 등을 알아봅시다
malloc
malloc은 동적으로 메모리를 할당하는 C언어의 내장 라이브러리 함수인데요. 동적으로 메모리를 할당한다니 무슨말인가 싶을겁니다. 아니 애초에 메모리 할당이 뭘까요?
memory
우리가 화면에 작성한 코드를 컴퓨터가 어떻게 실행하는지에 대해 원리를 알 필요가 있습니다. 너무 깊게는 말고 핵심만 요약해서 전달해드릴게요.
우선 코드가 있다고 해봅시다.
#include <stdio.h>const int MAX\_SIZE 10;char characters\[MAX\_SIZE\];int currentIndex = 0; void makeNewCharacter(char newChar) { if (currentIndex < MAX\_SIZE) { characters\[currentIndex\] = newChar; currentIndex; } else { printf("Array is full. Cannot add more characters.\n"); }}int main() { makeNewCharacter('A'); makeNewCharacter('B'); makeNewCharacter('C'); printf("Contents of characters array: "); for (int i = 0; i < currentIndex; i) { printf("%c ", characters\[i\]); } printf("\n"); return 0;} |
|---|
위 코드에서는 전역으로 선언된 변수, 메서드 내에서 선언된 변수, 코드 매개 변수 등 다양한 변수들이 존재합니다. 뿐만 아니라, 메서드도 존재하고, for문도 존재하고 .... 등 등 세분화 하면 많은 요소들이 코드안에 들어있음을 알 수 있습니다.
이런 코드를 컴퓨터에게 동작시키려면, 컴퓨터가 연산할 수 있도록 컴파일러가 번역해주겠죠? 그럼 그 번역한 코드는 어디에 들어있을까요?
기본적으로 컴퓨터는 CPU를 통해 연산하게 되는데, CPU는 연산 과정동안 메모리를 참조하게 되어 있습니다. 이게 메인 메모리가 되었든, 캐시메모리가 되었든 어떤 것이든 메모리를 참조 해야하죠. 따라서 위에서 번역된 코드들도 실행을 위해서는 모두 메모리로 옮겨 져야 합니다.
이제 연산을 실행하면 되는데요, 작업 효율을 위해서, 프로그램의 코드는 메모리 내부에 Logical하게 구분되어 저장되어 있습니다.
일반적으로 메모리는 아래와 같은 영역으로 나뉘게 됩니다.
프로그램 코드 내에서는 변수의 종류에 따라 메모리에 할당되는 영역이 모두 다릅니다. CODE부분에는 주로 메서드, 반복문, 조건문 등 프로그램 코드에 해당하는 부분이, Data는 전역변수나 정적변수가 들어갑니다. 컴파일 시점에 보통은 크기가 결정되어 있습니다.
HEAP의 경우에는 동적으로 할당되는 영역으로, 우리가 오늘 배울 malloc 사용시 할당되는 부분입니다. STACK은 함수내에서의 지역변수나, 매개변수등이 들어갑니다. HEAP과 STACK은 계속해서 크기가 변경될 수 있습니다.
#include <stdio.h>const int MAX\_SIZE 10; // DATA (only Read)char characters\[MAX\_SIZE\]; // DATAint currentIndex = 0; // DATAvoid makeNewCharacter(char newChar) { // method -> CODE if (currentIndex < MAX\_SIZE) { // if -> CODE characters\[currentIndex\] = newChar; // newChar -> stack currentIndex; } else { printf("Array is full. Cannot add more characters.\n"); // printf-> CODE, literal -> DATA(only Read) }}int main() { // method -> CODE makeNewCharacter('A'); makeNewCharacter('B'); makeNewCharacter('C'); printf("Contents of characters array: "); for (int i = 0; i < currentIndex; i) { // if -> CODE , i -> stack printf("%c ", characters\[i\]); } printf("\n"); return 0;} |
|---|
위 그림을 토대로 처음 제시한 코드영역을 다시 나눠 보았습니다. 눈치채셨을 수도 있겠지만, 위코드에서 Heap영역에 선언된 변수는 존재하지 않습니다. Heap영역에 변수를 선언하기 위해서는 malloc을 써야하거든요.
printf("Literal")의 경우 Literal 영역이 항상 보존되어야 하므로, Data의 only read로 저장됩니다.
malloc을 왜 쓰는가?
위의 메모리 영역 그림을 잘 이해하셨다면 malloc을 왜 사용하는가?에 대한 질문은 결국 Heap영역의 동적 메모리는 왜 사용하는가?에 대한 질문이 되겠습니다.
동적메모리는 말그대로 1. 동적인 활동이 자주일어나는 환경에 사용하면 됩니다. 자료구조를 예로 들면 링크드리스트나, 그래프, 스택, 큐 등이 될 수 있겠네요. 사실상 거의 모든 자료구조가 동적으로 사용되닌깐, 동적 메모리에서 구현되면 좋습니다.
동적메모리는 프로그램의 2. 처리 효율을 높여줄 수 있습니다. 코드영역, 데이터영역 등은 정적 메모리 영역으로 여기에 있는 코드나 변수는 프로그램이 끝날때까지 살아있습니다. 특히나 코드영역은 원본 프로그램의 코드 자체를 보존해야하기 때문에 보통 읽기 전용 메모리 영역에 들어갑니다.
malloc을 사용해보자!
malloc()을 사용한다는 것은 위 그림처럼, heap영역의 일부를 할당받겠다는 것입니다. 이제 공간을 확보했으니 사용할 수 있겠지요.
malloc()사용을 위해서는 파라미터와 반환값을 외워둘 필요가 있습니다.
| // parametermalloc(size\_t size); |
|---|
malloc을 호출할때는 size\_t 형식을 받는데, 이는 부호없는 정수 타입입니다
. 사용하고자하는 데이터 크기를 나타냅니다.
| // returnint \array = malloc(10 \ sizeof(int)); |
|---|
return은 void\로 포인터를 반환합니다. 즉 어떤 데이터든 시작번지의 주소를 반환하는 것이죠. void\의 경우에는 정할 수 없다의 의미로 쓰여, 사용자가 직접 casting하여 사용하거나, 포인터 선언부에서 선언된 자료형을 따라갑니다.
overflow
malloc()은 처음 size\_t parameter를 받기 때문에, 할당 받은 크기보다 큰 데이터를 넣고자 하면, overFlow가 발생하게 됩니다. 가량 1바이트 크기를 받았는데, 1바이트 이상으로 필요한 정수를 저장하려면 overFlow가 발생합니다.
| // overflowchar \c = malloc(1); \c = 256; // 1byte -> 0~255 or -128~127 -> overflow!! |
|---|
Array
배열의 경우에는 물리적(physically)으로 인접하게 메모리를 할당받습니다. 반환 값은 배열의 시작주소입니다.
| // arrayint \iArray = (int\) malloc(10 \* sizeof(int)); // size = 10인 정수형 배열 생성 |
|---|
인덱스 접근
동적으로 할당된 배열도 index로 접근이 가능한데 일반 배열처럼 아래와 같이 표현할 수 있습니다.
| // arrayint \iArray = (int\) malloc(10 \* sizeof(int)); // size = 10인 정수형 배열 생성 iArray\[3\]; // index = 3에 접근 |
|---|
연속적인 공간을 할당받은 배열이기 때문에, 실제 주소값을 계산할때는 아래와 같은 식이 적용됩니다.
tpye보다 작은 값, 큰값을 넣으면?
현재 우리는 int \*iArray를 사용중인데요. 이는 하나의 인덱스에 최대 4바이트 크기를 저장하고 있습니다. 근데 만약 1Byte짜리 char을 넣으려고 하면 어떻게 될까요? 혹은 8바이트 크기의 데이터를 넣으면 어떻게 될까요??
C언어는 형식에 있어서 비교적 자유롭기 때문에 데이터가 들어갑니다. char의 경우 int 공간의 1바이트만을 사용하고, double의 경우에는 8바이트로 두 index에 해당하는 공간을 차지합니다.
물론 데이터 형식이 int기 때문에 해석할때 4바이트 단위로 끊어 읽혀, 원래 데이터와 다른 값이 저장될 수 도 있습니다. 따라서 형식을 다르게 저장하는것은 좋지 않습니다.
free
이제 malloc()사용의 마지막입니다. 앞서 말씀드린대로, Code, Data영역에 저장된 데이터는 프로그램이 끝날때까지 살아있어야합니다. 이는 메모리 공간을 계속해서 차지해 있다는 것입니다.
Stack의 경우는 임시적인 변수들로, 매개변수나, 지역변수라고 앞서 이야기 했습니다. 이 변수들은 사용 후에 자동으로 메모리를 반환하기 때문에 사용자가 딱히 관리해줄 필요가없습니다.
하지만, malloc의 경우는 어떨까요?
우리가 실제 데이터를 삭제한다는 의미는 무엇일까요? 메모리에 있는 값을 제거한다? 제거는 어떻게 하나요? 다 0으로 셋팅한다?? 조금 헷갈립니다.
사실 우리가 데이터를 삭제한다는 개념은 메모리 내의 데이터를 관여하는 것이 아니라, 그 메모리와 포인터 사이의 연결고리를 끊는것입니다.
즉 메모리가 재할당되기 전까지 계속 데이터가 남아있다는 것입니다. 이런 성질을 이용하면, 포인터와 메모리 주소번지를 다시 연결시켜주면, 데이터 복구도 얼마든지 가능합니다.
이미 사용된 메모리의 주소번지는 재할당가능 상태가 되고, 다시 할당되어서,새로운 데이터로 덮어쓰여집니다.
즉, malloc의 경우 Heap메모리의 특정 번지를 사용하고나서, 더 이상 필요없어진다면, free()를 호출해줘야합니다. 이후 이전에 사용한 포인터는 null로 변환해야 합니다.
| int \iArray = (int\) malloc(10 \* sizeof(int)); // size = 10인 정수형 배열 생성 // freefree(iArray); iArray = null |
|---|
포인터를 초기화 하지 않는다면, dangling pointer가 되어, 해당 포인터는 더 이상 유효한 값을 읽어들 일 수 없게 됩니다.










