본문 바로가기

코딩연습/과제

(C언어)메모리구조 동적할당 파일 입출력

프로그램이 실행될때는 메모리 영역이 4가지고 나눠져 할당됩니다. 운영체제에 의해서 할당되는 메모리 구조는 다음과 같습니다.

코드영역:실행되는 프로그램의 코드가 저장되는 메모리 공간입니다. 즉 C언어를 통해 작성한 함수, 명령문들이 저장되는 공간입니다.

 

데이터 영역 : 전역변수와 정적변수값이 저장되는 메모리 공간입니다. 전역변수와 정적변수는 main()문밖에서 변수를 선언했을때 만들어지며 프로그램을 종료할때까지 사라지지않고 메모리 공간에 남아있게됩니다.

 

힙 영역 : 사용자가 원하는 시점에 메모리를 할당하고 소멸할 수 있는 변수들이 할당되는 영역입니다.

 

스택 영역 : 지역변수와 매개변수값이 저장되는 메모리 공간입니다. 합수 안에서 선언된 일반적인 변수를 통칭하며 함수가 종료된때 저장되어있던 메모리값이 소멸됩니다.

 

여기서 힙영역을 통해서 데이터 영역, 스택 영역의 변수들로 구현하기 힘든 코드를 작성하고, 동시에 메모리를 절약할 수 있습니다.

 

힙영역에서 동적할당을 사용할때 다음과 같은 함수를 씁니다.

malloc()함수 : 메모리공간을 힙영역에 할당한다.

free()함수 : malloc()함수에 의해 할당된 메모리 공간을 소멸시킨다.

realloc()함수: 힙영역안에 이미 할당된 변수의 메모리 공간을 변경한다.

 

※stdlib.h헤더파일을 선언해야 사용가능합니다.

 

malloc()

#include <stdlib.h>        // malloc을 사용하기 위함
  
// 기본 형태
void* malloc(size_t size);    // size 크기 만큼 메모리를 할당함
  
// 예)
int* arr = (int*)malloc(sizeof(int) * 6);    // 길이가 6인(int형) 동적 메모리공간을 할당함

기본 형태는 "void*"형 이지만 malloc앞에 캐스팅연산을 하여 사용하면 됩니다.

(이때 반환되는 값은 성공적으로 배열을 만들었으면 해당 배열의 주소값이, 실패했다면 NULL값이 반환됩니다.)

할당할 때 크기는 할당하는 배열의 형에 맞게 sizeof를 한 다음에 원하는 길이만큼 곱해주면 됩니다.

 

#include <stdlib.h>        // malloc을 사용하기 위함
  
// 예)
int* arr = (int*)malloc(sizeof(int) * 6);          // 길이가 6인(int형) 동적 메모리공간을 할당함
  
char* arr = (char*)malloc(sizeof(char) * 12);      // 길이가 12인(char형) 동적 메모리공간을 할당함
  
double* arr = (double*)malloc(sizeof(double) * 4); // 길이가 4인(double형) 동적 메모리공간을 할당함

그리고 malloc으로 할당한 배열은 free함수를 이용해서 해제를 해야합니다.

malloc(),2차원배열

malloc을 이용하여 2차원배열도 동적으로 만들수있습니다.

#include <stdlib.h>        // malloc을 사용하기 위함
  
// 5 * 3 배열을 동적으로 할당하기
  
int **arr;                 // 생성할 2차원배열
arr = (int**)malloc(sizeof(int*) * 5);             // 5행을 생성한 뒤
for (int i = 0; i < 5; i++)
{
    arr[i] = (int*)malloc(sizeof(int) * 3);        // 각각의 행에 3열씩 추가합니다.
}
  
// 생성한 메모리 해제
for (int i = 0; i < 5; i++)
{
    free(arr[i]);     // 각 행별로 선언된 열을 해제
}
free(arr);            // 행을

free()

#include <stdlib.h>        // malloc을 사용하기 위함
  
// 기본 형태
void free(void* ptr);
  
// 예)
int* arr = (int*)malloc(sizeof(int) * 6);        // 길이가 6인(int형) 동적 메모리공간을 할당함
free(arr);  

이와 같이 동적으로 할당한 배열을 free를 이용하여 해제할수있습니다.

 

이 해제 과정은 선택이 아닌 필수입니다.

그렇지 않으면 동적으로 할당한 메모리 공간이 해제되지 않아 메모리 누수가 발생하는데, 즉 자동으로 메모리가 비워지지 않습니다.

 

예제)

#include <stdio.h>
#include <stdlib.h>
  
int main()
{
    int* arr;
    int size;
     
    printf("수를 입력하세요: ");
    scanf("%d", &size);
     
    arr = (int*)malloc(sizeof(int) * size);
     
    if (arr == NULL)
        printf("배열이 생성되지 않음\n");
    else
    {
        for (int i = 0; i < size; i++)
            arr[i] = i;
        for (int i = 0; i < size; i++)
            printf("arr[%d] = %d\n", i, arr[i]);
    }
     
    free(arr);
    printf("메모리 해제 완료\n");
    return 0;
}

 

realloc()

#include <stdlib.h>        // realloc을 사용하기 위함
  
// 기본 형태
void* realloc(void* ptr, size_t size);    // ptr을 size크기로 재할당 함
  
// 예)
int* arr = (int*)malloc(sizeof(int) * 6);    // 길이가 6인(int형) 동적 메모리공간을 할당함
arr = (int*)realloc(arr, sizeof(int) * 8);   // arr을 길이가 8인(int형) 배열로 다시 재할당함

반환 값은 malloc과 같지만 한가지 유의해야할 사항이 있습니다.

int* arr = (int*)malloc(sizeof(int) * 6);  에서 다음 줄로 넘어갈때 realloc이 실패할경우에는 기존의 주소값을 잃어 버리게 됩니다.

그래서 기존에 할당된 메모리를 free할수 없어서 메모리 누수가 발생하게 됩니다.

 

그래서 realloc을 쓸때는 다음의 방법을 고려해야합니다.

// 기존의 배열을 남긴다.
  
#include <stdlib.h>        // malloc을 사용하기 위함
  
// 예)
int* arr = (int*)malloc(sizeof(int) * 6);   
int* arr2 = (int*)realloc(arr, sizeof(int) * 8);   // 기존의 arr을 놔두고 새롭게 arr2를 만든다.
if (arr2 != NULL)
{
    // 배열이 성공적으로 재할당되었다면 arr을 새로 재할당된 arr2를 할당한다.
    arr = arr2;
}

배열을 재할당받아야 한다면 새로 배열을 만들고 그 후에 성공했을때 예전배열과 바꿔치기를 하는 것입니다..

(포인터이기에 가능한 연산입니다.)

만약에 실패했다면 기존의 배열을 그대로 가지고 있는 것이 됩니다.

 

예제)

#include <stdio.h>
#include <stdlib.h>
#define BASIC_SIZE 5
  
void printArray(int _arr[], int _size)
{
    for (int i = 0; i < _size; i++)
    {
        printf("%d", _arr[i]);
        if (i != _size - 1)
            printf(", ");
        else
            printf("\n");
    }
}
  
int main()
{
    int* arr = (int*)malloc(sizeof(int) * BASIC_SIZE);
    int adder = 1;
    int counter = 0;
    int input = 0;
  
    printf("(-1 선택시 배열 출력)\n");
    do
    {
        printf("수를 입력하세요 : ");
        scanf("%d", &input);
  
        if (input == -1)
            printArray(arr, counter);
        else if (counter >= BASIC_SIZE * adder - 1)
        {
            printf("배열 재할당\n");
            adder++;
            int* arr2 = (int*)realloc(arr, sizeof(int) * BASIC_SIZE * adder);
            if (arr2 != NULL)
            {
                arr = arr2;
                arr[counter++] = input;
            }
            else
            {
                printf("배열 재할당 실패\n");
                adder--;
                break;
            }
        }
        else
            arr[counter++] = input;
    } while (input != 0);
  
    free(arr);
    printf("메모리 해제 완료\n");
    return 0;
}

정말 간단한 예제입니다.

처음에 배열은 BASIC_SIZE 크기인 5만큼 할당되어있습니다.

여기에 계속 수를 넣게 되면 counter가 증가하게 됩니다.

이 counter가 BASIC_SIZE * adder - 1보다 크거나 같게 된다면 즉, 배열 크기의 끝에 도달하게 되면 adder를 1증가 시킨 후에 배열을 BAIC_SIZE * adder의 크기로 재할당하게 됩니다.

이전의 배열이 5 * 1이었다면 이후의 배열의 크기는 adder가 1증가하기 때문에 5 * 2인 10의 크기를 가진 배열로 재탄생하게 됩니다.

'코딩연습 > 과제' 카테고리의 다른 글

절대경로 : 상대경로  (0) 2019.08.04
DNS란?  (0) 2019.08.04
객체와 클래스  (0) 2019.08.01
네트워크 전송에서 GET방식과 POST방식  (0) 2019.07.25
사설IP vs 공인IP, NAT  (0) 2019.07.12