우리가 흔히 사용하는 데스크탑을 구성하는 부품을 보면, 메인보드(마더보드), CPU, 주기억장치(DRAM), 보조기억장치(SSD, 하드디스크(HDD)), 그래픽카드(GPU) 등의 하드웨어로 구성돼있습니다.
컴퓨터에서 프로그램은 보조기억장치에 저장돼있다가 주기억장치에 적재된 후 CPU의 연산 장치가 실행합니다. 기억공간은 주기억장치의 기억공간을 의미합니다.
기억공간은 세가지 영역으로 나눌 수 있는데, 변수에 따라 기억공간에서 할당되는 영역이 다릅니다.
영역 | 설명 |
---|---|
데이터(data) 영역 | 전역변수, 정적변수 저장 / 프로그램 시작시 할당되고 종료시 소멸 |
힙(heap) 영역 | 사용자가 임의로 할당 및 소멸 시키는 영역 / 자유 기억공간이라고하며, 실행되면서 크기가 변하여 '메모리 동적 할당'에 사용 |
스택(stack) 영역 | 지역변수, 매개변수 저장 / 함수 호출시 할당되고 종료시 소멸 |
프로그램이 실행되기위해서는 주기억장치의 기억공간을 할당받아야합니다. C에서 변수를 선언하는 것은 기억공간을 확보하는 것을 의미합니다.
프로그램에서 기억공간을 확보하는 방법은 크게 '메모리 정적 할당'과 '메모리 동적 할당' 두가지가 있습니다.
'메모리 정적 할당(memory static allocation)'은 프로그램 실행 전에 프로그램 작성 단계에서 필요한 기억공간의 크기를 미리 할당하는 것 입니다. 기억공간의 크기를 미리 결정하므로 변수와 배열 선언이 해당됩니다.
void memory_static_allocation(){
int a = 1;
int array[3] = {1,2,3};
}
위의 예시에서 함수 호출시 int형 변수 'a'는 지역변수이므로 스택 영역에 4byte 만큼의 크기로 할당되고 1이 저장됩니다. 마찬가지로 int형 자료 3개를 갖는 배열 'array'는 스택 영역에 12byte 만큼의 크기로 할당되고 각각 1,2,3이 저장됩니다. 그리고 호출 종료시 스택영역에서 사라집니다.
int main(){
char name[20];
printf("이름 : ");
scanf("%s", name);
printf("입력한 이름 : %s", name);
}
만약 위처럼 이름을 입력받아 출력하는 프로그램이 있다면, 사람마다 이름의 글자수가 다를 수 있으므로 여유있는 크기로 배열의 크기를 할당해 주어야합니다. 이는 곧 기억공간의 낭비로 이어질 수 있습니다.
int main(){
int size;
char name[size];
printf("이름 글자 수 : ");
scanf("%d", &size);
printf("이름 : ");
scanf("%s", name);
printf("입력한 이름 : %s", name);
}
따라서 기억공간의 낭비를 위해 이름의 글자수와 이름을 모두 사용자가 입력하게하여 기억공간의 낭비를 줄이려고 위처럼 프로그램을 작성하게되면, 에러가 발생합니다. C에서는 배열의 크기가 상수로 선언되어야하기때문입니다.
이러한 문제를 해결하기위해 '메모리 동적 할당'이 사용됩니다.
'메모리 동적 할당(memory dynamic allocation)'은 프로그램 실행 중에 사용자에 의해 입력되는 자료를 활용하여 힙 영역에 알맞은 크기만큼의 기억공간을 할당할 수 있습니다. 이는 곧 메모리 정적 할당에서 발생했던 기억공간의 낭비를 해결할 수 있습니다.
메모리 동적 할당은 특정 함수를 사용하여 이루어지는데, 그 함수를 사용할 때, 컴퓨터가 사용하지 않는 기억공간을 찾아 할당한 후, 그 위치에대한 포인터를 리턴하는 과정에서 시간이 낭비되므로 프로그램의 실행이 늦어지는 결과를 유발합니다. 따라서 꼭 필요한 상황에서만 사용하는 것이 좋습니다.
※ 메모리 동적 할당에 사용되는 함수들을 사용하기 위해서는
#include <stdlib.h>
로 'stdlib' 헤더파일을 include 해주어야합니다.
'malloc() 함수'는 allocate memory block으로, 힙 영역에 기억공간을 byte 단위로 동적 할당합니다.
void* malloc(size_t size);
의 형태로 사용하며, size 만큼의 byte 단위로 기억공간을 힙 영역에 할당하고, 기억공간의 시작 주소를 리턴합니다.
만약 기억공간 부족 등 으로 할당에 실패시, null 포인터를 리턴하며, null 포인터는 선행 처리시 '0'으로 바껴, 상수값 0과 같습니다.
malloc()은 void*를 리턴하므로 다른 모든 타입으로 형변환이 가능합니다.
malloc()은 할당된 기억공간을 초기화하지는 않습니다.
※ size_t :
typedef unsigned int size_t;
typedef로 unsigned int를 size_t로 재정의 한 것 입니다.
하지만 엄밀히 말하면 unsigned int와는 차이가 있습니다.
힙 영역에 할당되면 프로그램 종료시 OS에 의해 기억공간이 해제되지만, 프로그램 종료시까지는 유지되기때문에 기억공간이 낭비되고, 프로그램의 실행 속도가 저하될 우려가 있습니다. 나아가서는 기억공간은 한정적이기 때문에 계속 할당해주다보면 기억공간이 부족할 수 있습니다. 따라서 'free() 함수'를 사용하여 사용이 끝났거나 낭비되고 있는 기억공간을 반납해주어야합니다.
void free(void *p);
의 형태로 사용하며, 해당 포인터의 기억공간을 반납하여 해제합니다.
'calloc() 함수'는 'allocate and and clear memory block'으로, malloc() 함수처럼 동적으로 힙 영역에 기억공간을 할당한 후, 해당 기억공간을 0으로 초기화합니다.
void* calloc(int n, int size);
의 형태로 사용하며, malloc() 함수와 달리 size의 크기를 갖는 기억공간 n개를 할당하고, 실패시 마찬가지로 null 포인터를 리턴합니다.
'realloc() 함수'는 'resize memory block'으로, 이미 할당된 기억공간의 크기를 변경해줍니다.
void* realloc(void *p, int size);
의 형태로 사용하며, 포인터 p가 가리키는 기억공간의 크기를 size의 크기로 변경하고, 재할당된 기억공간의 포인터를 리턴합니다.
이때 변경하려는 size가 원래 크기보다 큰 경우, 원래 기억공간의 뒤쪽에 그만큼의 공간이 비어있다면 그 위치에서 크기만 늘리지만, 비어있지않다면 그만큼의 여유가 있는 기억공간으로 이동해 재할당하고, 원래 내용을 그대로 복사합니다.
메모리 동적 할당 함수를 사용하여 기억공간을 할당한 후 '기억공간 관리 함수'를 사용하여 해당 기억공간에 있는 자료를 활용할 수 있습니다.
※ 기억공간 관리와 관련된 함수를 사용하기위해서는
#include <mem.h>
로 'mem' 헤더파일을 inlcude 해줘야합니다.
'memcmp 함수'는 'compare memory blocks'로, 기억공간의 자료를 주어진 크기만큼 비교하여 같은지 여부를 판별합니다.
int memcmp(const void *s1, const void *s2, size_t n);
의 형태로 사용하며, s1과 s2가 가리키는 기억공간의 내용을 n byte 만큼 비교하여 s1>s2 일 경우 양수를, s1=s2 일 경우 0을, s1<s2 일 경우 음수를 리턴합니다.(이때 각 문자의 ASCII 코드값을 사용하여 비교합니다.)
※ memcmp() 함수 vs strncmp() 함수 : strncmp() 함수는 중간에 NULL 문자가 있으면 종료되기때문에 그 뒤의 내용이 다르더라도 같다고 판별하지만, memcmp() 함수는 끝까지 비교하기때문에 정확한 판별이 가능합니다.
#include <stdio.h>
#include <string.h>
#include <mem.h>
int main(){
char *p1 = "abc\0d";
char *p2 = "abc\0e";
int a = strncmp(p1, p2, 5);
int b = memcmp(p1, p2, 5);
printf("%d\n", a);
printf("%d\n", b);
}
0
-1
'memcpy() 함수'는 'copy memory block'으로, 기억공간의 자료를 다른 기억공간으로 복사합니다.
void* memcpy(void *dest, const void *src, size_t n);
의 형태로 사용하며, src(source)에 있는 대상 자료를 dest(destination)로 n byte 만큼 복사하고 dest의 값을 리턴합니다.
'memset() 함수'는 'initialize memory block'으로, 기억공간을 지정한 문자로 채웁니다.
void* memset(void *s, int c, size_t n);
의 형태로 사용하며, s가 가리키는 기억공간을 c의 값으로 n byte만큼 채우고 s의 값을 리턴합니다.