[C]메모리 동적 할당
📚C 언어에서의 기억공간
컴퓨터는 CPU, 주기억장치, 보조기억장치 등으로 구성되어 있다. 프로그램은 보조기억장치에 저장되어 있다가 프로그램이 시작되면 주기억장치인 RAM에 적재되어 CPU의 연산 장치에 의해 실행된다.
어떤 프로그램이 실행되면 운영체제는 프로그램 실행을 위해 기억공간을 할당하게 되는데, 이때 할당되는 기억공간의 위치는 데이터, 힙, 스택 영역으로 나누어 볼 수 있다.
구분 | 설명 |
---|---|
데이터 영역 | 전역변수와 정적변수가 저장되는 영역 |
힙 영역 | 프로그래머의 필요에 의해 할당/소멸이 이루어지는 영역 |
스택 영역 | 지역변수와 매개변수가 저장되는 영역 |
📚메모리 정적 할당
메모리 정적 할당은 변수 선언이나 배열 선언 같이 프로그램을 작성하는 단계에서 필요한 기억공간의 크기를 결정하는 것이다. 정적할당은 기억공간을 할당하는 가장 쉬운 방법으로 변수 선언과 같이 할당시켜 줘야 할 기억공간의 한계 크기를 명확히 알고 있을 경우에 사용한다. 메모리 정적할당은 프로그램이 시작될 때 미리 기억공간의 크기를 고정하여 할당시켜 버린다.
프로그램에서 사용할 변수의 기억공간의 크기를 정확히 알고 있다면 메모리 정정 할당은 기억공간을 쉽게 사용하면서 에러 발생 확률을 줄일 수 있다는 장점이 있다. 하지만 보통 예상되는 기억공간의 크기보다 약간 더 크게 잡게 되고 사용하게 될 기억공간의 크기를 정확히 알지 못하거나 사용되는 자료 크기가 차이가 심하다면 기억공간의 낭비를 가져오는 문제점이 있다.
💡배열 크기 선언
배열의 크기는 컴파일 타임에 결정되어야 한다. 따라서 변수나 const 상수로 배열의 크기를 결정할 수 없다. #define을 배열 크기에 대응시키는 것은 컴파일 타임에 이루어지기 때문에 가능하다.
📚메모리 동적 할당
메모리 동적 할당은 힙 영역을 이용하여 프로그램 실행 중에 입력되는 자료에 맞게 기억공간을 확보한다. 메모리 동적 할당은 많은 기억공간을 한꺼번에 할당받아 배열로 사용할 수 있으므로 매우 효율적이다.
메모리 동적 할당은 시간 지체라는 단점도 있다. 메모리 동적 할당을 위해 malloc() 이라는 함수를 사용하는 순간 컴퓨터는 사용하지 않는 기억공간을 할당하고 이에 대한 포인터를 리턴하는 과정을 거치게 된다.
📄메모리 동적 할당의 장점
메모리 동적 할당은 많은 자료를 처리하는 배열의 크기를 실행 시간에 정의해야 하는 경우에 매우 유용하다. 메모리 동적 할당은 프로그램 실행 시 기억공간의 크기를 지정할 수 있고 재조정이 가능하다는 이점이 있다.
📄메모리 동적 할당 순서
- 기억공간을 동적으로 할당받을 변수를 포인터를 이용하여 선언한다.
- malloc() 함수를 이용하여 기억공간을 동적으로 할당한다.
- 기억공간의 사용이 끝나면 free() 함수를 이용하여 기억공간을 해제한다.
📄malloc()
1
2
void *malloc(size_t number_of_bytes);
// number_of_bytes에서 주어지는 크기만큼 기억공간을 동적으로 할당한다.
malloc() 함수는 기억공간을 동적으로 할당하는 함수이고 힙 영역에 기억공간을 할당한다. malloc() 함수는 인자로 할당받고자 하는 기억공간의 크기를 바이트 단위로 전달한다. 힙에 그 크기만큼 기억공간을 할당하고 할당한 기억공간의 첫번째 주소를 반환한다. 기억공간 할당에 실패하면 null 포인터를 반환한다.
그리고 malloc() 함수는 void *로 명시하여 어떤 타입으로든 형변환이 가능하다. 또한 malloc() 함수는 동적으로 기억공간을 할당만 할 뿐, 할당된 기억공간을 초기화해 주지는 않는다. 따라서 할당된 기억공간의 초기화를 위해 memset() 함수를 사용해야 한다.
📄free()
1
2
void free(void *p);
// 동적으로 할당된 기억공간을 해제.
힙 영역에 할당된 기억공간은 프로그램이 종료될 때까지 유지된다. 프로그램이 종료되면 운영체제에 의해 할당된 모든 기억공간 전부가 해제되지만, 프로그램이 실행되는 동안은 계속 유지되기 때문에 기억공간이 낭비될 수 있고, 프로그램의 실행속도를 떨어뜨리는 원인이 될 수 있다. 그리고 기억공간은 한정된 자원이므로 계속 공간을 할당받기만 한다면 언젠가 기억공간의 부족 현상이 일어날 수 있다. 따라서 동적으로 할당받은 메모리 공간이 더 이상 필요 없을 때는 명시적으로 반납해야 한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <stdio.h>
#include <stdlib.h>
#pragma warning(disable:4996)
void main()
{
// 입력받을 문자 수 저장 변수 선언
int size;
// 동적 할당된 기억공간을 연결할 포인터
char* str;
printf("문자열의 크기를 입력하세요: ");
scanf("%d", &size);
// 입력받을 문자 수(size + 1)에 맞게 동적 할당
str = (char*)malloc(size + 1);
// 기억공간 할당 성공 여부 판단
if (str == NULL)
{
puts("기억공간 할당 실패!");
exit(1);
}
printf("문자열을 입력하세요: ");
// 동적으로 할당된 기억공간에 문자열 저장
scanf("%s", str);
printf("동적 할당된 메모리에 저장된 문자열: %s \n", str);
free(str);
}
📚기타 동적 할당 함수
📄calloc()
1
2
void *calloc(int n, int size);
// size의 크기를 가지는 기억공간 n개를 할당받는다.
calloc() 함수는 malloc() 함수와 동일하게 동적으로 힙 영역에 기억공간을 할당하는 역할을 한다. 하지만 기억공간을 0으로 초기화한다는 점에서 차이가 있다. 또한 기억공간의 할당에 실패할 경우 null값을 반환한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
#include <stdlib.h>
#pragma warning(disable:4996)
void main()
{
int* a;
a = (int*)calloc(5, sizeof(int));
for (int i = 0; i < 5; ++i)
{
printf("%d \n", a[i]);
}
free(a);
}
📄realloc()
1
2
void *realloc(void *p, int size);
// 동적 할당받은 기억공간의 크기를 변경한다.
realloc() 함수는 포인터 p가 가리키고 있는 기억공간의 크기를 지정된 size의 크기로 변경한다. 재할당된 기억공간의 블록의 포인터를 반환하게 된다.
a라는 배열이 처음에 20byte만큼 할당되었고 다시 40byte로 확대하여 재할당하는 경우에 만약 배열 a 뒤쪽의 기억공간이 비어 있다면 배열 a를 40byte로 늘리기만 하면 되므로 배열 a는 그 자리에서 길이만 늘어납니다. 하지만 배열 a 다음의 기억공간이 비어있지 않다면 배열 a의 길이를 늘릴 수 없기 때문에 배열 a가 확대된 크기만큼의 여유 기억공간이 있는 쪽으로 옮겨가야 한다. 이때 realloc() 함수는 배열 a를 이동시키면서 기존 배열 a에 들어 있던 모든 내용을 그대로 복사하므로 재할당에 의해 위치는 바뀌더라도 내용은 그대로 유지된다.
동적 할당이 필요한 이유는 컴파일 할 시점에서 필요한 기억공간의 크기를 모를 때가 있기 때문이다. 또한 재할당이 필요한 이유는 실행 중에라도 필요한 기억공간의 크기를 가늠할 수 없을 때가 있기 때문이다.
📚기억공간 관리
C 언어는 기억공간 관리를 위해 memcmp(), memcpy(), memset() 함수 등을 제공한다. malloc() 함수 같은 메모리 동적 할당 함수는 stdlib.h 표준 헤더 파일을 통해 제공되지만, 기억공간 관리를 위한 함수는 memory.h 헤더 파일을 통해 제공된다.
📄memcmp()
1
2
int memcmp(void *s1, void *s2, size_t n);
// s1과 s2가 가리키는 기억공간의 내용을 n byte만큼 비교.
memcmp() 함수는 기억공간에 들어 있는 자료를 주어진 크기만큼 비교하여 같은지 여부를 알 수 있게 해주는 함수이다. s1과 s2는 비교하려는 기억공간의 포인터이고, n은 비교하고자 하는 크기이다. s1과 s2의 내용을 n byte만큼 비교하여 s1 > s2이면 양수, s1 < s2이면 음수, s1 = s2이면 0을 반환하게 된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
#include <string.h>
#pragma warning(disable:4996)
void main()
{
char* s1 = "aaab";
char* s2 = "aaac";
int stat;
// 3자리까지 같으므로 0을 반환
stat = memcmp(s1, s2, 3);
printf("%d", stat);
}
📄memcpy()
1
2
void *memcpy(void *dest, const void *src, size_t n);
// src에서 n byte만큼 dest에 복사.
memcpy() 함수는 기억공간의 자료를 다른 기억공간 영역으로 복사하기 위한 함수이다. 형식에서 dest는 복사될 곳을 가리키는 기억공간 포인터이고, src는 복사하려는 기억공간의 포인터, n은 복사하려는 byte 수이다. src의 첫 부분부터 n byte만큼의 자료를 dest에 복사하고 실행 결과를 dest의 값을 반환한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include <string.h>
#pragma warning(disable:4996)
void main()
{
char src[] = "1234567890";
char dest[] = "abcdefghijklmnopqrstuvwxyz";
char* stat;
printf("memcpy() 실행 전 dest의 데이터 : %s \n", dest);
stat = (char*)memcpy(dest, src, strlen(src));
if (stat)
{
printf("memcpy() 실행 후 dest의 데이터 : %s \n", dest);
}
else
{
printf("복사 실패 \n");
}
}
📄memset()
1
2
void *memset(void *s, int c, size_t n);
// 포인터 s가 가리키는 곳을 c 값으로 n byte만큼 채운다.
memset() 함수는 기억공간의 자료를 지정한 문자로 채우는 함수이다. 할당된 기억공간의 초기화나 내용 삭제를 위해 사용한다. 형식에서 s는 값을 채울 기억공간의 포인터, c는 기억공간에 채우게 될 문자, n은 채우려는 크기인 byte 수이다. 실행 결과로 s의 값이 반환된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
#include <string.h>
#pragma warning(disable:4996)
void main()
{
char s[] = "1234567890";
printf("memset() 실행 전 s의 데이터 : %s \n", s);
memset(s, '*', strlen(s));
printf("memset() 실행 후 s의 데이터 : %s \n", s);
}
📄문자열 처리 함수와 기억공간 관리 함수
문자열과 기억공간은 연속되 공간이라는 점에서 공통점이 있어 기억공간 관리 함수의 동작도 문자열 처리 함수와 비슷하다. strcpy() 함수에 대응되는 memcpy() 함수, strcmp()에 대응되는 memcmp() 함수 등 기본 동작 방식은 같다.
기억공간 관리 함수와 문자열 처리 함수의 차이점은 아래와 같다.
- 인수와 리턴값의 타입
기억공간 관리 함수는 임의의 자료를 대상으로 하므로 인수와 리턴값이 모두 void형이지만 문자열 처리 함수는 항상 문자열을 대상으로 하므로 인수나 리턴값이 대부분 char형이다. - 함수 실행 후 종료시점
문자열의 경우에 시작 주소만 알려 주면 null 문자를 통해 끝을 인식하므로 문자열의 길이를 별도로 알려 줄 필요가 없지만 기억공간 관리 함수는 크기를 알려주지 않으면 실행의 끝을 모른다. 따라서 모든 기억공간 관리 함수의 끝에 작업 대상, 즉 실행의 끝을 알리는 크기를 지정하는 n이라는 인수가 있다.
Leave a comment