(주의 : 절대 코드 확장자를 .cpp로 쓰지 말것)
포인터를 할 때 포인터를 이용해 변수의 메모리 주소를 포인터에 저장해서 사용하기도 하였다. 다른 경우는 우리는 배열에 크기를 정해주고 사용하기도 하였다. 하지만 이번에는 scanf를 통해 메모리 공간을 사용자가 직접 원하는 만큼 메모리 공간을 사용하도록 해보자.
scanf를 통해 원하는 만큼 메모리 공간을 지정한다는 문구를 잘 생각해보자. scanf를 통해 입력을 받고 해당 크기만큼 배열이 생성된다. 기본적으로 scanf를 통해 값을 넣는다는 것은 프로그램이 실행되고 나서의 일이다. 반면 배열에 크기를 미리 지정해주는것은 코드를 작성할 때의 일, 즉 프로그램이 실행되기 전의 일이다. 전자와 같이 프로그램이 실행되고있는 시간동안 사용할 메모리 공간을 할당하는것을 우리는 동적할당 이라고 한다.
이제 본격적으로 동적 할당에 대해 알아보자. 동적할당에 쓰이는 메모리를 사용하기 위해서는 memory allocation의 약자인 malloc함수가 사용된다.
malloc함수의 기본적인 형태 및 특징은 다음과 같다.
1) Pointer = malloc(크기);
2) 메모리 할당에 실패하면 NULL반환
3) <stdlib.h>라는 헤더파일 안에 내장되어있다.
4) 필요 메모리 크기는 바이트 단위로 지정한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | #include<stdio.h> #include<stdlib.h> int main() { int *ptr; ptr = malloc(sizeof(double));//malloc함수의 인자로는 원하는 양의 크기를 입력해준다. int num = 30; int *ptr2; ptr2 = # printf("%p\n", ptr); printf("%p\n", ptr2); free(ptr); return 0; } | cs |
또한 결과값도 각각의 메모리 값이 잘 나왔다. 같은 메모리 주소 형태일 지라도 두 형태의 메모리는 서로 다른 점이 있다. 스택(stack)이라는 것과 힙(heap)이라는것이 있다. 다른점이라는것은 일반적인 변수의 주소는 스택에 저장되고, malloc함수를 이용해 할당된 메모리는 힙 부분에 저장되게 된다.
그렇다면 어떠한 점이 다를까? 스택(stack)에 저장된 메모리의 경우, 따로 메모리를 해제하지 않아도 된다. 반면 heap에 저장된 메모리의 경우 따로 해제를 해주어야 한다.
메모리를 해제해 주기 위해서는 free()함수를 써야하며, 이 free함수의 인자에는 malloc 등 힙 부분에 저장되는 메모리를 사용한 포인터를 넣어주면 된다. 위의 코드 14번째 줄과 같이 malloc을 사용해 메모리를 할당한 ptr변수에 대해 메모리 해제를 해주는것을 볼 수 있다. 메모리를 해제해 주는것은 필수이다. 만약 이를 해제하지 않는다면 메모리를 계속 사용하게 되면 시스템 메모리 부족현상이 생기게 되어 흔히 말하는 RAM누수 와 같은 메모리 누수의 가능성이 올라간다.
1 2 3 4 5 6 7 8 9 10 11 12 13 | #include<stdio.h> #include<stdlib.h> int main() { int *ptr; ptr = malloc(sizeof(double));//malloc함수의 인자로는 원하는 양의 크기를 입력해준다. *ptr = 100; printf("%d\n", *ptr); free(ptr); return 0; } | cs |
1 2 3 4 5 6 7 8 9 10 11 12 13 | #include<stdio.h> #include<stdlib.h> #include<memory.h> int main() { int *ptr; ptr = malloc(sizeof(double)); memset(ptr, 0x89, sizeof(int)); printf("0x%x\n", *ptr); free(ptr); return 0; } | cs |
memset함수의 인자중 세번째 인자인 크기 설정에 있어서 sizeof()로 쓸수도 있지만 일반적인 정수형태로 작성해 주어도 된다.
1 2 3 4 5 6 7 8 9 10 11 12 13 | #include<stdio.h> #include<stdlib.h> #include<memory.h> int main() { long *ptr = malloc(sizeof(long)); memset(ptr,0x89, 3); printf("0x%x",*ptr); free(ptr); return 0; } | cs |
해당 코드의 결과는 다음과 같다. 여기서 보면 위의 경우 sizeof(int), 즉 총 4바이트 크기에 대해서 값을 넣어주었다. 여기 같은 경우 필자가 memset(ptr,0x89,3) 이라고 작성하였는데 이것을 하나씩 해석해 보면 ptr이라는 포인터에 0x89라는 16진법 수를, 3바이트 크기 만큼 넣어주겠다는 것이다. 저기서 cd라고 되어있는 부분은 초기화 되지 않은 부분에 대해 임의의 수가 이스케이프 시퀸스 %x, 즉 16진법으로 변환되어 출력된 부분이다.
우선 여기서 의문점을 가지는 사람이 있을 수 있다. cd가 나올 수 있다 하자. 하지만 왜 0x898989cd가 아닌 0xcd898989로 나오는것인가? 앞에서 부터 차례로 채워나가야 하는것이 아닌가? 라고 말이다. 여기에 대한 답변으로는 Window 운영체제는 메모리에 대해 리틀 엔디언이라는 방식을 채택해 적용되어있다. 해당 리틀 엔디언 방식에 대해서는 추후에 다루어 보겠다.
memset함수에 대해 하나 주의할 점이 있다.
1 2 3 4 5 6 7 8 9 10 11 12 | #include<stdio.h> #include<stdlib.h> #include<memory.h> int main() { long *ptr = malloc(sizeof(long)); memset(ptr,0x89, 8); printf("0x%x",*ptr); free(ptr); return 0; } | cs |
다음과 같은 결과값과 함께 경고음이 들릴것이다. 무엇일까? 난 분명히 0x89라는 값을 8개를 설정해주겠다고 했는데 4개의 값만 나온다. 가장 기본적으로 우리가 봐야할것은 앞에서 우리는 malloc함수를 sizeof(long), 즉 총 4바이트의 크기만 할당해 주었다. 그러나 코드에서는 전체 크기인 4바이트를 넘는 8바이트를 설정해 주겠다고 코드에 작성해 주었다. 즉 오버플로우가 발생하는 경우가 생길 수 도 있다는 것이다. 자신이 아직 이에 대해 의하하다 생각이 된다면 한면 memset의 size값을 넣는 곳에 5~8사이의 값을 모두 넣어보자. 모두 경고음과 함께 4바이트만큼의 값만 나올것이다. 그 후 4를 넣어보면 아무런 경고음 없이 잘 출력되는것을 볼 수 있다.
결론적으로 주의할것은 자신이 할당한 정적 및 동적메모리 (혹은 배열) 에 대한 크기와 자신이 설정하고자 하는 크기를 주의해야한다는 것이다.
memset함수는 주로 memset((포인터),0,(size))와 같이 메모리의 값들을 모두 0으로 초기화 시키고 싶을때 주로 사용한다.
또한 현재 필자가 malloc과 memset을 함께 썼다고 꼭 동적할당에서만 memset을 쓸 수 있는것은 절대 아니다.
1 2 3 4 5 6 7 8 9 10 | #include<stdio.h> #include<memory.h> int main() { char a[] = { "Hello world" }; memset(a, '+', 5); printf("%s", a); return 0; } | cs |
결과값을 보면 알 수 있듯이 앞에서부터 5바이트만큼(여기서는 Hello라는 문자열이 되겠죠?)의 크기가 '+'라는 문자로 대입된것을 볼 수 있다.
'Language > C' 카테고리의 다른 글
NULL 포인터에 (0) | 2019.01.18 |
---|---|
scanf와 scanf_s에 대해서 (0) | 2019.01.17 |