C언어 (23) 동적 메모리 할당2
지난번에 안본 realloc과 free를 보고, 동적 메모리를 끝내자.
동적 메모리 할당 함수
함수명 | 설명 | 반환 값 |
---|---|---|
malloc(size) | 지정된 크기만큼 메모리를 할당하며 초기화되지 않은 메모리를 반환. | 성공 시 할당된 메모리의 포인터 반환, 실패 시 NULL 반환 |
calloc(n, size) | 지정된 개수와 크기만큼 메모리를 할당하고 모든 바이트를 0으로 초기화. | 성공 시 할당된 메모리의 포인터 반환, 실패 시 NULL 반환 |
realloc(ptr,size) | 기존 할당된 메모리 블록을 새로 지정된 크기만큼 재할당 | 성공 시 재할당된 메모리의 포인터 반환, 실패 시 NULL 반환. 기존 포인터는 영향을 받지 않음 |
free(ptr) | 할당된 메모리를 해제. | 반환 값 없음, 메모리 해제 후 포인터는 더 이상 유효하지 않음 |
이전 글에서 malloc과 calloc은 확인했다.
realloc & free
1
void* realloc(void* p, size_t size);
realloc() 함수는 동적 메모리로 할당되어 있는 영역에서 size만큼 재할당을 한다.
그 다음 재할당된 메모리의 시작 주소를 반환한다.
realloc에 주어지는 p는 이미 할당되어 있는 동적 메모리의 시작 주소여야 한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
#include <stdlib.h>
int main() {
int* p = (int*)malloc(sizeof(int) * 2);
p[0] = 10;
p[1] = 20;
p = (int*)realloc(p, sizeof(int) * 4);
p[2] = 30;
p[3] = 40;
for (int i = 0; i < 4; i++) {
printf("p[%d] : %d ", i, p[i]);
}
free(p);
p = NULL;
return 0;
}
1
p[0] : 10 p[1] : 20 p[2] : 30 p[3] : 40
우선 p에 malloc() 함수로 8바이트만큼 할당한 다음, realloc()을 이용하여 메모리를 확장한다.
그러면 p[2], p[3]에 값을 넣을 수 있게 된다.
같은 방법으로 메모리를 축소시킬 수도 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
#include <stdlib.h>
int main() {
int* p = (int*)malloc(sizeof(int) * 4);
p[0] = 10;
p[1] = 20;
p[2] = 30;
p[3] = 40;
p = (int*)realloc(p, sizeof(int) * 2);
for (int i = 0; i < 4; i++) {
printf("p[%d] : %d ", i, p[i]);
}
free(p);
p = NULL;
return 0;
}
1
p[0] : 10 p[1] : 20 p[2] : 683737424 p[3] : 399
이번에는 p에 malloc() 함수로 16바이트만큼 할당한 다음, realloc()을 이용하요 메모리를 축소했다.
그러면 p[2], p[3]의 값에는 접근할 수 없게 된다.
free()
free() 함수는 계속 쓰고 있었지만 동적으로 할당 받은 메모리를 해제한다.
1
void free(void* p)
이렇게 주소를 받는다. 그런데 어떻게 주소만 가지고 동적 할당한 메모리를 해제할 수 있을까?
그 이유는 동적 할당을 할 때 딱 크기만큼만 할당하는 것이 아니기 때문이다.
그림처럼 Data부분이 실제 할당이 되고, 앞에 header로 얼마만큼 할당하는지에 대한 정보를 따로 저장한다.
이를 heap chunk 구조라고 부르는데, 여기의 size를 보고 그만큼을 주소에서 free() 할 수 있게 된다.
이것에 대한 내용은 나중에 heap 구조를 따로 볼 것 같다.
또 free 함수는 잘못 사용하면 여러 오류를 초래할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#include <stdlib.h>
int main() {
int* p = (int*)malloc(sizeof(int) * 1);
*p = 10;
printf("%d\n", *p);
printf("%p\n", p);
free(p);
printf("%d\n", *p);
printf("%p\n", p);
return 0;
}
1
2
3
4
10
00000286A3C9D080
-1547072544
00000286A3C9D080
이전 예제에서 free(p) 이후에 포인터를 NULL로 초기화 해줬다.
왜냐하면 free는 할당된 메모리를 해제해도 p에 저장된 주소가 그대로 남아있다.
그렇기에 free(p)를 해도 이후에 접근을 하게 되면 잘못된 접근이 생겨나게 된다.
double free, use after free 취약점처럼 free()에 관련된 취약점들이 있으니 조심해야 한다.
메모리 관련 함수
C언어 라이브러리에는 메모리에 관련된 함수들이 몇 가지 있다.
이 함수들은 string.h에 선언되어 있다.
memset
memset은 메모리를 특정 값으로 채우는 함수이다.
1
void *memset(void *str, int c, size_t n);
str은 주소, c은 설정할 값, n은 설정할 바이트의 수이다.
1
2
3
4
5
6
7
8
9
#include <stdio.h>
#include <string.h>
int main() {
char str[10];
memset(str, '*', sizeof(str) - 1);
str[9] = '\0';
printf("%s\n", str);
return 0;
}
1
*********
이렇게 코드를 실행하면 str[0] ~ str[8]까지 *로 채워지고 출력한다.
memcmp
두 메모리를 비교해 동일한지 확인을 한다.
1
int memcmp(const void *str1, const void *str2, size_t n);
str1과 str2의 메모리를 n바이트 만큼 비교하는 함수이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>
#include <string.h>
int main() {
char str1[] = "Hello nope";
char str2[] = "Hello World";
int result1 = memcmp(str1, str2, 5); // 5바이트 비교
int result2 = memcmp(str1, str2, 8); // 5바이트 비교
if (result1 == 0) {
printf("5글자가 같습니다.\n");
}
else {
printf("5글자가 다릅니다.\n");
}
if (result2 == 0) {
printf("8글자가 같습니다.\n");
}
else {
printf("8글자가 다릅니다.\n");
}
return 0;
}
1
2
5글자가 같습니다.
8글자가 다릅니다.
이렇게 str1과 str2의 메모리를 바이트 단위로 비교한다.
memcpy
메모리 영역에 데이터를 복사하는 함수이다.
1
void *memcpy(void *dest, const void *src, size_t n);
1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
#include <string.h>
int main() {
char src[] = "Hello, World!";
char dest[20];
memcpy(dest, src, strlen(src) + 1);
printf("%s\n", dest);
return 0;
}
1
Hello, World!
이렇게 src의 데이터를 dest에 복사할 수 있다.
memmove
memmove도 memcpy처럼 메모리를 복사하는 함수이다.
1
void *memmove(void *dest, const void *src, size_t n);
1
2
3
4
5
6
7
8
9
#include <stdio.h>
#include <string.h>
int main() {
char str[] = "Hello, World!";
memcpy(str + 7, str, 5);
printf("Result: %s\n", str);
return 0;
}
1
Hello, Hello!
역시 src의 데이터를 dest로 복사하는 것으로 src의 데이터를 dest로 복사할 수 있다.
그러면 memcpy와 memmove의 차이점은 무엇일까?
memmove는 원본 데이터를 임시로 복사한 다음 목적지로 복사를 하지만 memcpy는 바로 진행한다.
그렇기에 속도 측면에서는 memcpy가 유리하지만 안전성 측면에서는 memmove가 유리하다.
가변 인자
가변 인자는 함수 인자 수를 고정하지 않고 함수를 호출할 수 있는 방법이다.
C에서 가변 인자를 사용할 때는 stdarg.h 헤더 파일을 포함하고, 매크로들을 이용해야 한다.
va_list는 가변 인자를 처리하는 목록, va_start는 가변 인자의 시작을 의미한다.
va_arg는 가변 인자 목록에서 하나씩 가져오고, va_end로 목록 처리를 마친다.
코드로 확인하면
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>
#include <stdarg.h> // 가변 인자를 처리를 위한 헤더 파일
void add(int num, ...);
int main() {
add(2, 20, 30); // 20 + 30
add(3, 40, 50, 60); // 40 + 50 + 60
return 0;
}
void add(int num, ...) {
va_list args; // 가변 인자 목록을 저장할 변수 선언
va_start(args, num); // 가변 인자의 시작을 설정
int total = 0;
for (int i = 0; i < num; i++) {
total += va_arg(args, int); // 가변 인자에서 하나씩 가져와서 더함
}
va_end(args); // 가변 인자 목록의 처리가 끝났음을 알림
printf("%d\n", total);
}
1
2
50
150
이렇게 가변 인자를 통해 인자를 유연하게 사용할 수 있는 함수도 만들 수 있다.
동적 할당은 왠지 헷갈린다는 이미지가 있었는데 이전 것들을 꼼꼼히 보고 보니까 너무 쉽다.
다음은 잡다한 전처리기와 분할 컴파일에 대해 보면 정말 끝이다.