C언어 공부/개념 공부

2024.01.18 C언어 <메모리 동적할당(1)>

코딩입문시작 2024. 1. 18. 17:26

16.1 동적 할당 함수

 

학습목표

- 프로그램을 작성할 때 처리할 데이터 종류나 수를 장담할 수 없다면 필요한 변수나 배열의 공간을 실행 도중에 동적으로 확보하는 것이 좋다.

  • 동적 할당한 공간은 변수와 달리 이름이 없으므로 포인터에 주소를 대입하여 사용한다.
  • 동적 할당을 요청한 후에는 제대로 할당되었는지 반환값을 확인해야 한다.
  • 사용이 끝난 동적 할당 공간은 재활용을 위해 반환한다. 
  • 동적 할당한 저장 공간을 배열처럼 쓸 때는 포인터가 배열명의 역할을 한다.
  • 동적 할당한 저장 공간을 0으로 초기화하거나 크기를 바꿔 재할당하는 함수도 있다.

동적 할당은 실행 시점에 메모리 공간을 할당한다.

 

함수 구분 사용 예시
malloc 원형 void* malloc (unsigned int size);
기능 size 바이트 수 만큼 할당하고 시작 위치 반환
사용 예 int* p = (int*) malloc (sizeof(int));
calloc 원형 void* calloc (unsigned int cnt, unsigned int size);
기능 (cnt * size) 바이트 수 만큼 할당하고,
0으로 초기화 후 시작 위치 반환 
사용 예 double* p = (double*) calloc (5, sizeof(double));
realloc 원형 void* realloc (void* p, unsigned int size);
기능 p가 연결한 영역의 크기를 size 바이트의 크기로 조정하고 시작 위치 반환 
사용 예 char* p = (char*) realloc (p, 2 * strlen(str));
free 원형 void free (void* p);
기능 p가 연결한 영역 반환

 

 

16.1.1 malloc, free함수

 

보통, 프로그램에 필요한 저장 공간은 프로그램을 작성할 때 변수나 배열 선언을 통해 확보한다. 그러나 프로그램의 실행 중에 저장 공간을 할당할 수도 있다. 이렇게 사용한 저장 공간은 재활용을 위해 다시 반납해야 한다. 메모리를 동적 할당할 때는 malloc함수를, 반환할 때는 free함수를 사용한다. 

malloc, free함수 원형

※ <stdlib.h> (스탠다드라이브러리) 헤더파일을 포함시켜줘야함

 

필요한 바이트 수를 malloc함수의 인수로 준다. 직접 바이트 수를 인수로  주는 것보다 각 자료형에 대한 크기를 계산하여 주는 것이 효율적이다. 

 

malloc 함수는 (void*)형을 반환하므로 용도에 맞는 포인터형으로 형반환하여 사용한다.

 

pi = (int*) malloc (sizeof(int));

1. 저장 공간을 할당하고 (void*)형 반환

2. int형 변수로 활용하기 위해서 형 반환

3. int 형을 가리키는 포인터에 저장

 

동적으로 메모리를 사용할 때는 항상 포인터를 쓰므로 주의해야 할 것이 있다. malloc 함수의 반환값이 널 포인터가 아닌지 반드시 확인 하고 사용해야 한다. 메모리 할당 함수는 원하는 크기의 공간을 할당하지 못하면 0번지인 널포인터 (NULL pointer) 를 반환한다. (보통 NULL로 표기하는데 전처리 단계에서 0으로 바뀜) 널 포인터는 포인터의 특별한 상태를 나타내기 위해 사용하므로 간접참조 연산을 할 수 없다. 따라서 malloc 함수가 널포인터를 반환한 경우 그 값을 참조하면 실행 중에 오류메시지를 표시하고 비정상 종료된다. 

 

exit(1) 함수 : main 함수 뿐만 아니라 어떤 함수에서든 프로그램을 바로 종료할 수 있으며 예외 상황이 발생하여 프로그램을 바로 종료하는 경우 인수로 1을 주고 호출한다. 

 

사용이 끝난 저장 공간은 재활용할 수 있도록 반환해야 한다. 자동 지역 변수의 저장 공간은 함수가 반환될 때 자동으로 회수되지만 동적으로 할당한 저장 공간은 함수가 반환된 후에도 자동으로 회수되지 않는다. 따라서 반환되기 전에 free 함수로 직접 반환해야 한다. 프로그램에서 동적으로 할당한 저장 공간은 해당 프로그램이 종료될 때 운영체제에 의해서 자동으로 회수되어 다른 프로그램이 실행될 때 재활용된다. 

 

→ if? 메모리 반환을 잊게 되면 메모리 누수 (Memory leak) 를 일으켜 프로그램이 의도치 않게 종료될 수 있다.

 

프로그램이 사용하는 메모리 영역은 기억부류를 갖는다.

[기억부류 (Storage class) : 프로그램은 실행될 때 일정한 메모리 영역을 사용함, 이 영역은 몇 개의 영역으로 나뉘어 관리]

 

하나의 프로그램이 사용하는 메모리 영역 코드 영역 실행 파일을 위한 영역
스택 지역 변수 영역
기타 데이터 영역 전역 변수, 정적 변수 영역
동적 할당 영역

 

힙에 할당한 저장 공간은 쓰레기 값이 존재, 프로그램이 종료될 때까지 메모리에 존재

따라서 주소만 알면 특정 함수에 구애받지 않고 어디서나 사용이 가능하다. 동적 할당이 갖는 이러한 특징들 때문에 반환에 세심한 주의가 필요하다. 

 

힙 영역은 메모리의 사용과 반환이 불규칙적이기 때문에, 메모리에 저장공간이 넉넉히 남아 있어도 널포인터를 반환할 수 있다. 따라서 동적 할당 함수를 호출한 후에는 반드시 반환값을 검사하여 메모리의 할당 여부를 확인해야 한다. 

 

 

16.1.2 동적 할당 영역을 배열처럼 쓰기

 

형태가 같은 변수가 많이 필요할 때 하나씩 동적 할당하는 것은 비효율적이다. 할당한 저장 공간의 수만큼 포인터가 필요하기 때문이다. 많은 저장 공간을 한꺼번에 동적 할당하여 배열처럼 사용하면 된다. (코드를 보면서 확인하자!) 이때 할당한 저장 공간의 시작 위치만 포인터에 저장하면 포인터를 배열처럼 쓸 수 있다. 

 

 

할당한 메모리를 배열처럼 사용하여 값을 입력합니다. 포인터 pi를 마치 배열인 것처럼 배열 요소 표현식을 사용하면 됩니다. scanf 함수는 데이터를 저장할 공간의 주소가 필요하므로 'pi + i' 와 같이 직접 포인터 계산식을 사용하는 것도 가능하다. 

 

 

16.1.3 기타 동적 할당 함수

 

calloc 함수는 할당한 저장 공간을 0으로 초기화하고, realloc 함수는 크기를 조절한다.

 

함수 원형과 설명
calloc, realloc 함수 사용 예시

  • calloc 함수는 할당할 저장 공간을 모두 0으로 초기화한다.

 

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

  • realloc 함수는 저장 공간의 크기를 조정하는 데에 사용한다.
    저장 공간을 늘리는 경우 이미 입력한 값은 그대로 유지, 추가된 공간에는 쓰레기 값이 존재한다. 줄이는 경우라면 입력된 데이터는 잘려나간다.

 

 

추가적으로 ... 

이미 사용하던 저장 공간의 위치를 포인터가 기억하고 있더라도 재할당 과정에서 메모리의 위치가 바뀔 수 있으므로 항상 realloc 함수가 반환하는 주소를 다시 포인터에 저장해 사용하는 것이 좋다. 메모리의 위치가 바뀌는 경우 이미 있던 데이터는 계속 사용할 수 있도록 옮겨 저장하며 사용하던 저장 공간은 자동으로 반환한다. 또한 첫 번째 인수가 널포인터인 경우는 malloc과 같은 기능을 수행하여 두 번째 인수의 크기만큼 동적 할당하고 그 주소를 반환한다.