[C] 필요한거 있으시면 말씀 해주세요~

장세민·2022년 10월 30일
0

📝 TIL

목록 보기
29/40
post-thumbnail

📌 동적 할당 함수

Before Starting

언제나 시작부터 변수나 배열 선언을 해서 저장 공간을 확보할 수 있는 건 아니다.
때로는 프로그램 실행 중에 저장 공간을 할당할 수도 있고,
사용한 저장 공간은 다시 실행 중에 재활용을 위해 반납해야 한다.

이렇게 프로그램 실행 중에 저장 공간을 할당하는 것을 동적 할당이라 한다.



📖 malloc, free 함수

프로그램 실행 중 메모리를 동적 할당할 때는 malloc 함수를, 반환할 때는 free 함수를 사용한다.
이 함수들을 사용할 때는 stdlib.h 헤더 파일을 인클루드해야 사용 가능하다.

함수를 사용해보자.

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3.  
  4. int main(void)
  5. {
  6. int *pi;
  7. double *pd;
  8.  
  9. pi = (int *)malloc(sizeof(int));
  10. if (pi == NULL)
  11. {
  12. printf("#메모리가 부족합니다.\n");
  13. exit(1);
  14. }
  15. pd = (double *)malloc(sizeof(double));
  16.  
  17. *pi = 10;
  18. *pd = 3.4;
  19.  
  20. printf("정수형으로 사용: %d\n", *pi);
  21. printf("실수형으로 사용: %.1lf\n", *pd);
  22.  
  23. free(pi);
  24. free(pd);
  25.  
  26. return 0;
  27. }

9행과 15행에서 메모리를 동적으로 할당한다.
동적할당 시 필요한 바이트 수를 malloc 함수의 인수로 줘야 하는데,

sizeof 연산자로 각 자료형에 대한 크기를 계산하여 주는 것이 좋다.

그렇게 해야 컴파일러에 따라 변수의 크기가 다르더라도 프로그램을 수정할 필요가 없다.
malloc 함수는 주어진 인수의 바이트 크기만큼 메모리에서 연속된 저장 공간을 할당한 후 그 시작 주소를 반환한다.

malloc 함수와 free 함수의 원형

malloc 함수의 반환형

void *malloc(unsigned int size);

malloc 함수는 동적 할당된 주소값을 반환하므로
반환형으로 (void *)를 사용하는 것이고,

free 함수의 반환형

void free(void *p);

free 함수는 반환값이 없으므로 반환형으로 void를 사용한다.


따라서 용도에 맞는 포인터형으로 형 변환하여 사용해야 한다.
즉, 9행은 동적 할당한 저장 공간을 int형 변수로 쓰기 위해 int형을 가리키는 포인터에 저장한 것이다.

반환값을 포인터에 저장한 후에는 간접 참조 연산을 수행하여
가리키는 저장 공간에 값을 저장하거나 출력할 수 있다.


🚨 동적으로 메모리를 사용할 때 주의할 점

항상 포인터를 사용하므로,

① malloc 함수의 반환값이 널 포인터인지 반드시 확인하고 사용해야 한다.

메모리 할당 함수는 원하는 크기의 공간을 할당하지 못하면
0번지인 널 포인터(NULL)를 반환한다.

널 포인터는 포인터의 특별한 상태를 나타내기 위해 사용하므로

간접 참조 연산을 사용할 수 없다.

malloc 함수가 널 포인터를 반환한 경우 그 값을 참조하면
실행 중에 에러 메시지를 표시하고 비정상 종료된다.

프로그램이 실행될 때 메모리의 상태에 따라 달라지므로
동적 할당 함수를 호출한 후에는 반환값을 검사하는 과정이 필요하다.

② 사용이 끝난 저장 공간은 재활용할 수 있도록 반환해야 한다.

동적으로 할당한 저장 공간은 함수 반환 후에도 그대로 메모리에 남아 있으므로
free 함수로 직접 반환해야 한다.
main 함수가 끝날 때는 굳이 반환할 필요가 없지만

메모리 해제를 하지 않는 습관은 메모리 누수(memory leak)를 일으킬 수 있기 때문에 조심해야 한다.



📖 동적 할당 영역을 배열처럼?

형태가 같은 변수가 많이 필요할 때 하나씩 동적 할당하는 것은
저장 공간의 수만큼 포인터가 필요하므로 비효율적이다.

따라서 할당한 저장 공간의 시작 위치만 포인터에 저장하면
포인터를 배열처럼 쓸 수 있다.

한 번 사용해보자.

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3.  
  4. int main(void)
  5. {
  6. int *pi;
  7. int i, sum = 0;
  8.  
  9. pi = (int *)malloc(5 * sizeof(int));
  10. if (pi == NULL)
  11. {
  12. printf("메모리가 부족합니다!\n");
  13. exit(1);
  14. }
  15. printf("다섯 명의 나이를 입력하세요: ");
  16. for (i = 0; i < 5; i++)
  17. {
  18. scanf("%d", &pi[i]);
  19. sum += pi[i];
  20. }
  21. printf("다섯 명의 평균 나이: %.1lf\n", (sum / 5.0));
  22. free(pi);
  23.  
  24. return 0;
  25. }

9행은 배열처럼 사용할 전체 저장 공간을 동적 할당하여
int형을 가리키는 포인터에 그 주소를 저장한다.

그러면 포인터의 주소 값을 int형의 크기만큼 증가시켜 전체 저장 공간을 배열처럼 사용할 수 있다.

pi = (int *)malloc(5 * sizeof(int));    // 9행. 저장 공간 20바이트 할당

18행은 할당한 메모리를 배열처럼 사용하여 값을 입력하는데,
포인터 pi를 마치 배열인 것처럼 배열 요소 표현식을 사용하면 된다.



📖 기타 동적 할당 함수

메모리를 동적 할당할 때 가장 많이 사용하는게 malloc 함수지만,
경우에 따라 더 유용할 수 있는 함수들이 있다.

calloc 함수

void *calloc(unsigned int, unsigned int);

calloc 함수는 메모리를 동적 할당하여 0으로 초기화된 메모리 공간을 얻고자 할 때 사용한다.

realloc 함수

void *realloc(void *, unsigned int);

또한 메모리를 동적 할당할 때는 대부분 입력되는 데이터에 맞게 저장 공간을 확보하지만,
처음에 예측한 크기와 맞지 않을 수 있다.
이때 realloc 함수로 저장 공간의 크기를 조절할 수 있다.

이 함수들을 사용하여 양수를 입력해보자.

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3.  
  4. int main(void)
  5. {
  6. int *pi;
  7. int size = 5;
  8. int count = 0;
  9. int num;
  10. int i;
  11.  
  12. pi = (int *)calloc(size, sizeof(int));
  13. while (1)
  14. {
  15. printf("양수만 입력하세요 => ");
  16. scanf("%d", &num);
  17. if (num <= 0) break;
  18. if (count == size)
  19. {
  20. size += 5;
  21. pi = (int *)realloc(pi, size * sizeof(int));
  22. }
  23. pi[count++] = num;
  24. }
  25. for (i = 0; i < count; i++)
  26. {
  27. printf("%5d", pi[i]);
  28. }
  29. free(pi);
  30.  
  31. return 0;
  32. }

12행에서 calloc 함수를 호출하는데 인수는 2개이다.

pi = (int *)calloc(size, sizeof(int));

두 번째 인수 sizeof(int)는 할당할 저장 공간의 크기를 바이트 단위로 주고,
첫 번째 인수 size로 그 개수를 준다.

예를 들어, 배열 요소가 5개인 double형 배열처럼 사용할 공간이 필요하다면

double *pd;
pd = (double *)calloc(5, sizeof(double));

다음과 같이 사용할 수 있다.

calloc 함수는 할당한 저장 공간을 모두 0으로 초기화하므로 따로 초기화하는 수고를 덜어준다.


만약 21행처럼 저장 공간의 크기를 조정해야 하면 realloc 함수를 사용한다.

pi = (int *)realloc(pi, 10 * sizeof(int));

이미 할당한 저장 공간의 포인터와 조정할 저장 공간의 전체 크기를 준다.

저장 공간을 늘리는 경우 이미 입력한 값은 그대로 유지되며 추가된 공간에는 쓰레기 값이 존재하지만,
저장 공간을 줄이는 경우 입력된 데이터는 잘려나간다.

저장 공간의 크기를 조정한 후에는 다시 그 주소를 반환하므로 포인터에 저장해 사용한다.


재할당 과정에서 메모리의 위치가 바뀔 수 있으므로
항상 realloc 함수가 반환하는 주소를 다시 포인터에 저장해 사용하는 것이 좋다.

메모리의 위치가 바뀌는 경우 이미 있던 데이터는
옮겨 저장하며 사용하던 저장 공간은 자동으로 반환한다.

또한 첫 번째 인수가 널 포인터인 경우 malloc과 같은 기능을 수행하여
두 번째 인수의 크기만큼 동적 할당하고 그 주소를 반환한다.

profile
분석하는 남자 💻

0개의 댓글