더듬이의 헬로월드

Hello, World!

프로그래밍 언어/C++ [기본]

[C++기본] 27.가변 배열

더듬이 2021. 9. 23. 19:12
728x90

기존 배열의 문제점

int b;
cin>>b;
int arr[b];

위 두 코드는 정상적으로 실행될까?

안된다면, 그 이유는 무엇일까?

b는 사용자의 입력에 따라, 10이 될 수도, 100이 될 수도 있다.

그리고, arr의 사이즈는b에 따라 결정된다.

문제는, 이게 런타임 중에 동작한다는 것이다.

이 배열은 지역변수, 스택 메모리 영역에 생성된다.

그럼, 함수가 실헹 될 떄, 메모리를 얼마나 잡아야 하는지 모를 것이다.

크기를 가늠할 수가 없기 때문이다.

그래서 우리는 그때 그때 크기가 늘어나고, 줄어드는 가변 배열을 만들고 싶은 것이다.


가변 배열 자료형

가변 배열을 위한 자료형을 만들어 보자.

첫번째로, 이 가변배열에 데이터를 저장할 곳을 만들어 주어야 한다.

typedef struct Holymoly {

    int data[x];

}Holymoly;

이 코드는 사용자가 입력한 크기만큼의 크기를 갖는 가변 배열이다.

가변 배열 자체가 하나 하나 데이터를 저장한다면, 가변적인 대응이 가능할까?

위에서 설명했던 기존 배열의 문제점과 같은 문제점이 생일 것이다!

그렇다면, 뭐가 필요할까?

typedef struct Holymoly {

    int* PInt;

}Holymoly;

포인터를 이용해서 내가 할당하는 메모리의 주소를 넘겨주는 방식으로 할당하면 해결 가능하다.

두번째로, 내가 어디까지 데이터를 입력했는지 기억해야 한다.

typedef struct Holymoly {

    int* PInt;
    int Icount;

}Holymoly;

세번째, 데이터의 최대치를 기록해야 한다.

typedef struct Holymoly {

    int* PInt;
    int Icount;
    int IMaxCount;

}Holymoly;

Int형 데이터가 최대 몇개가 들어가야 하는지 기록해야, 이를 넘어도 다시 최대치를 늘릴 수 있을 것이다.

카운트와 최대치 값이 같아졌을 때, 늘려주면 될 것이다.


실제 사용!

    Holymoly v;
    v.PInt = (int*)malloc(40);
    v.Icount = 0;
    v.IMaxCount = 10;

이 가변 배열은, 40바이트 크기의 int자료형을 담을 수 있는 가변 배열로 선언되었다.

현재 들어간 데이터 수, 카운트는 0이며, 최대치는 10으로, 현재로써는10개의 데이터만 담을 수 있다.

초기화 함수를 만들고 싶다면?

위의 방식대로 설계한다면, 새로운 데이터가 입력될 때 마다 적어주어야 할 것이다.

이를 함수로 만들어서 좀 더 편하게 사용하자!

main.cpp

#include <iostream>
#include "Holymoly.h"

using namespace std;

int main(){
    Holymoly v;
    InitArr(&v);
}

Holymoly.h

typedef struct Holymoly {

    int* PInt;
    int Icount;
    int IMaxCount;

}Holymoly;

void InitArr(Holymoly *pHol);

Holymoly.cpp

#include <iostream>
#include "Holymoly.h"

void InitArr(Holymoly* pHol) {
    pHol->PInt = (int*)malloc(sizeof(int)*10);
    pHol->Icount = 0;
    pHol->IMaxCount = 10;
}

→의 의미는 . 과 동일하다.

→은 포인턿 요소를 선택하는 것이다.

단지 포인터인지, 참조인지를 구분하는 연산자이다.

pHol->PInt = (int*)malloc(sizeof(int)*10);

그리고 이렇게 적어주는 것이 기존의 코드보다 사용자의 의도를 알아차리기에 훨씬 더 좋은 코드가 될 것이다.

현재 상황은 이렇다. 스택메모리에서, 포인터를 이용해 힙 메모리에서의 개변 배열을 관리한다.

그렇다면, 메모리는 언제 해제될까??

스택 영역의 메모리는 프로그램 종료와 동시에 해제되게 된다.

그럼, 메인 함수에서 포인터가 가리키던, 힙 영역의 데이터 배열은 어떻게 될까?

그대로 남아서 메모리 공간 부족의 문제를 초래한다.

이제, 해제 함수를 만들어보자

void ReleaseArr(Holymoly* pHol) {
    free(pHol->PInt);
    pHol->Icount = 0;
    pHol->IMaxCount = 0;
}

PInt의 메모리를 해제시키고,

현재 카운트와 최대치를 0으로 설정하였다.

데이터 입력 함수

가장 중요한 데이터를 입력받아 힙메모리 공간에 쌓아주는 함수를 만들어보자.

void Push_back(Holymoly* pHol,int I_data) {
    //힙 영역에 할당한 모든 공간이 다 참
    //모자르니까 늘려주어야 함!
    if (pHol->IMaxCount <= pHol->Icount) 
        Reallocate(pHol);

    //데이터를 채워주는 부분
    pHol->PInt[pHol->Icount++] = I_data;
}

첫번째로, 할당된 크기를 초과하지 않았는지 검색해야 한다.

만일 공간이 부족하다면, 메모리 재할당 함수를 통해 크기를 늘려주어야 할 것이다.

두번째로, 데이터를 채워넣어주어야 한다.

근데, 다음 추가 위치는 어떻게 알아 낼 것인지 의문이 생긴다.

현재 Icount값이 다음 추가 인덱스이지 않은가?

Icount번째에 값을 넣고, 후위 연산자를 통해서 다음 인덱스를 가리키도록 설정하였다.

힙 메모리 영역의 주의점

malloc함수를 생각해보자, 이 함수는 내가 요청한 크기만큼의 메모리를 힙 영역에 할당해주는 함수이다.

여러 malloc함수를 이용해 프로그램을 작성 중이였는데, 사용자의 실수로 인해서, 의도치 않게 다름 힙메모리의 다른 영역을 침범할 수 도 있을 것이다.

C++은 이러한 상황에 대해 안전 장치가 전혀 없다.
심지어 이러한 오류들은 잡기도 함들다.

프로그램이 정상적으로 실행되지만, 언제 오류가 나올지를 모르기 때문이다!!

이러한 오류들을 heap corruption, 힙 손상이라고 한다.

메모리 재할당 함수

만일 우리가 처음에 초기화한 40바이트만큼의 크기를 초과한다면, 메모리 공간을 늘려야 한다.

그럼 이런 동작이 과연 가능 할까?

줄줄 이어 붙이는 형식으로 재할당을 할 것이 아니라,

더 큰 크기의 새로운 크기만큼 할당을 받는 방식으로 가야 할 것이다!

현재 상황은 이렇다.

기존의 배열의 최대치를 초과하였고, 우리는 이의 2배 크기의 4바이크 만큼의 공간을 할당받았다.

그런 다음, PInt를 옮겼는데, 과연 문제가 없을까?

기존의 메모리 영역은.....

int *Pnew=(int*)malloc(pHol->IMaxCount * 2*sizeof(int));

주소값을 저장하지 않고 옮겼다간, 기존의 메모리 영역은 공중으로 사라져버린다. 따라서 일단, 기존의 영역 주소값을 저장해 두어야 한다.

for (int i = 0; i < pHol->Icount; i++)
        Pnew[i] = pHol->PInt[i];

그리고, 기존 공간에서 썼던 데이터들을 새로운 공간으로 복사시켜야 한다.

free(pHol->PInt);

그리고, 기존 배열에서 썻던 공간은 해제시켜 주어야 한다.

pHol->PInt = Pnew;
pHol->IMaxCount *= 2;

마지막으로, PInt가 가리키는 곳을 새로운 메모리 영역으로 바꿔주고, MaxCount를 두배 로 늘려준다!


완성

main.cpp

#include <iostream>
#include "Holymoly.h"

using namespace std;

int main() {
    Holymoly v;
    InitArr(&v);
    Push_back(&v, 1);
    Push_back(&v, 10);
    Push_back(&v, 7);
    Push_back(&v, 6);
    Push_back(&v, 3);
    Push_back(&v, 4);
    Push_back(&v, 8);
    Push_back(&v, 4);
    Push_back(&v, 15);
    Push_back(&v, 45);
    Push_back(&v, 85);
    Push_back(&v, 17);
    show(&v);
    ReleaseArr(&v);
}

Holymoly.cpp

#include <iostream>
#include "Holymoly.h"
using namespace std;

void InitArr(Holymoly* pHol) {
    pHol->PInt = (int*)malloc(sizeof(int) * 5);
    pHol->Icount = 0;
    pHol->IMaxCount = 5;
}

void ReleaseArr(Holymoly* pHol) {
        free(pHol->PInt);
        pHol->Icount = 0;
        pHol->IMaxCount = 0;
    }

void Push_back(Holymoly* pHol,int I_data) {
    //힙 영역에 할당한 모든 공간이 다 참
    //모자르니까 늘려주어야 함!
    if (pHol->IMaxCount <= pHol->Icount) 
        Reallocate(pHol);

    //데이터를 채워주는 부분
    pHol->PInt[pHol->Icount++] = I_data;
}

void Reallocate(Holymoly* pHol) {
    //기존 주소값을 저장할 지역 변수 선언
    int *Pnew=(int*)malloc(pHol->IMaxCount * 2*sizeof(int));

        //그리고, 기존 공간에서 썼던 데이터들을 새로운 공간으로 복사시켜야 한다.
    for (int i = 0; i < pHol->Icount; i++)
        Pnew[i] = pHol->PInt[i];

        //그리고, 기존 배열에서 썻던 공간은 해제시켜 주어야 한다.
    free(pHol->PInt);
        //마지막으로, PInt가 가리키는 곳을 새로운 메모리 영역으로 바꿔주고, MaxCount를 두배 로 늘려준다!
    pHol->PInt = Pnew;
    pHol->IMaxCount *= 2;
}

void show(Holymoly* pHol) {
    for (int i = 0; i < pHol->Icount; i++)
    {
        cout << i << "번째 데이터의 값 : " << pHol->PInt[i] << endl;
    }
}

Holymoly.h

typedef struct Holymoly {

    int* PInt;
    int Icount;
    int IMaxCount;

}Holymoly;

void InitArr(Holymoly* pHol);
void ReleaseArr(Holymoly* pHol);
void Push_back(Holymoly* pHol, int I_data);
void Reallocate(Holymoly* pHol);
void show(Holymoly* pHol);
728x90

'프로그래밍 언어 > C++ [기본]' 카테고리의 다른 글

[C++기본] 29.함수 포인터  (0) 2021.09.26
[C++기본] 28.연결 리스트  (0) 2021.09.26
[C++기본] 26.동적 할당  (0) 2021.09.23
[C++기본] 25.구조체 포인터  (0) 2021.09.23
[C++기본] 24.문자열-5  (0) 2021.09.23