[C/C++] 가변 배열 - 구조체/분할 구현

2024. 3. 28. 19:21Programming Language/C++

아래 링크 클릭 시 해당 본문으로 이동

가변 배열 - C

가변 배열 - C++


가변 배열 - C

구조체동적 할당을 이용하여 가변 배열 자료형 만들기

Arr.h

#pragma once

// 가변 배열 자료형 tArr (int)
typedef struct _tagArr
{
	int* pInt;
	int iCount;
	int iMaxCount;
} tArr;

// 배열 초기화 함수
    // 반환 타입이 필요없기 때문에 void
    // 주소를 넘겨줘야 되기 때문에 인자 타입은 배열의 주소인 tArr*
void InitArr(tArr* _pArr);

// 데이터 추가 함수
    // 인자 타입은 배열의 주소 tArr*
    // int 데이터를 추가해야 되므로 int형 인자 _iData 추가
void PushBack(tArr* _pArr, int _iData);

// 공간 추가 확장(재할당) 함수
    // 메모리 공간이 꽉 차면 확장시키며, 데이터를 넣는 과정에서 이 함수를 호출한다.
    // main()에서 이 함수를 호출할 수 있는 이유는 참조한 현재 Arr.h에 함수를 선언했기 때문이다.
    // 하지만 링크 단계에서 연결이 되므로 내가 원하지 않은 결과가 나올 수 있는데,
    // main()에는 이 함수의 존재를 알 수 없게 하기 위해 헤더 파일에 Reallocate()를 명시하지 않으면 이를 방지할 수 있다.
    // 가변 배열에서도 Arr.cpp에 정의를 했기 때문에 문제가 되지 않는다.
void Reallocate(tArr* _pArr);

// 배열 메모리 해제 함수
void ReleaseArr(tArr* _pArr);

 

Arr.cpp

#include <cstdlib>
#include "Arr.h"

// 배열 초기화
void InitArr(tArr* _pArr)
{
	// -> (화살표 연산자) : 역참조 후 멤버 접근
	_pArr->pInt = (int*)malloc(sizeof(int) * 2);  // 힙 영역의 시작 주소값
	_pArr->iCount = 0;  // 현재 들어와있는 데이터 개수
	_pArr->iMaxCount = 2; // 최대치
}

// 공간 추가 확장(재할당)
void Reallocate(tArr* _pArr)
{
	// 1. 2배 더 큰 공간을 동적할당한다.
		// 2칸이면 4칸, 4칸이면 8칸, 8칸이면 16칸, ...
	int* pNew = (int*)malloc(_pArr->iMaxCount * 2 * sizeof(int));
	// malloc()으로 할당받은 주소를 pInt로 받으면 발생하는 문제
		// 시작 주소를 가리키는 pInt는 새로 할당받은 메모리 공간의 시작 주소를 받게 되어서 원래 시작 주소가 아니게 된다.
		// 그래서 위의 경우 주소는 지역변수로 받아야 한다.

	// 2. 기존 공간에 있던 데이터들을 새로 할당한 공간으로 복사시킨다.
	for (int i = 0; i < _pArr->iCount; ++i)
	{
		pNew[i] = _pArr->pInt[i];
	}

	// 3. 기존 공간은 메모리 해제
	free(_pArr->pInt);

	// 4. 배열이 새로 할당된 공간을 가리키게 한다.
	_pArr->pInt = pNew;

	// 5. iMaxCount 변경점 적용
	_pArr->iMaxCount *= 2;
}

// 데이터 추가
void PushBack(tArr* _pArr, int _iData)
{
	// 힙 영역에 할당한 공간이 다 찬다면
	if (_pArr->iMaxCount <= _pArr->iCount)  // 만에 하나 잘못된 값이 들어와서 개수를 초과한 경우, 재 할당이 안 일어날 가능성이 있다.
	{
		// 공간이 모자를 경우 재할당 (공간 확장 함수 Reallocate())
		Reallocate(_pArr);
	}

	// 데이터 추가
        // 다음 데이터를 추가할 인덱스값 = iCount
        // 가변 배열이 멤버로 들고 있는 pInt의 주소 변수로 가서 인덱스 접근을 할건데 그때 사용할 인덱스가 본인이 사용하는 iCount값이다.
        // 접근해서 그 위치에 수정하고나서 iCount값이 증가해야하므로 후위연산자 사용
        // 입력까지 다 끝나고 나서 iCount값 증가시키기
	_pArr->pInt[_pArr->iCount++] = _iData;
}

// 배열 메모리 해제
void ReleaseArr(tArr* _pArr)
{
	free(_pArr->pInt);
	_pArr->iCount = 0;
	_pArr->iMaxCount = 0;
}

 

main.cpp

#include <cstdlib>
#include <stdio.h>
#include "Arr.h"

// 가변 배열 : 데이터를 계속 확장해가면서 넣는 형태의 자료구조
int main()
{
    tArr s1 = {};

    // 배열 초기화
    InitArr(&s1);

    // malloc()으로 *내가 원하는 주소*에 메모리를 할당할 수는 없다.
    // 동적할당으로 되돌려준 주소에는 요청한 만큼의 메모리가 할당되어있다.
    // 할당해준 공간만 사용해야되는데, 만약 포인터로 해당 공간이 아닌 다른 공간으로 넘어가면 다른 객체가 사용하고 있는 공간을 침범하고 훼손시킬 수 있어 문제가 발생한다.
    	// ex. 할당한 공간은 10칸인데 실수로 100칸을 넘어간 경우
    	// c++은 안전 장치가 없다.
    	// 실수로 조작한 순간에 문제가 발생하는 것이 아니라 뒤늦게 그 값을 사용할 때 문제가 발생하므로 어디서 문제가 발생했는지 찾기가 어렵다.
    // 또한 할당하지 않은 곳의 메모리를 사용하는 경우도 문제이다.
    // 운영체제는 쓰이지 않은 공간으로 인식하고 있는 그곳을 사용했을 때 발생하는 문제도 나중에서야 발견하게 된다.
    // 이런 오류를 *힙 손상(Heap Corruption)*이라고 하며, 한 박자 늦게 또는 한참 뒤에 뜬금없이 문제가 발생한다.
    	// 비슷한 경우 : 배열 스택
    	// 지역 변수인데도 배열(배열도 사실은 포인터이다.) 인덱스를 잘못 접근하면 다른 변수의 스택 메모리 공간에 침범한다.
    
    // 재할당
    	// 처음에 동적할당 받을 때 충분히 넉넉한 공간을 잡아줘야 한다.
    	// 2칸을 사용하다가 2칸을 더 할당받고 싶을 때 2칸 뒤에 2칸을 이어붙이는 것이 아니라 4칸을 새로 할당받고 원래 2칸을 복붙한다.

    // 데이터 추가
    for (int i = 0; i < 10; ++i)
    {
        PushBack(&s1, i);  // s1에 0~9까지 넣어라.
    }
    
    for (int i = 0; i < s1.iCount; ++i)
    {
        printf("%d\n", s1.pInt[i]);  // i번째 데이터 출력
    }

    // 배열 메모리 해제
    ReleaseArr(&s1);

    return 0;
}

분할 구현 이용해서 가변 배열 자료형 만들기

Arr.h

#pragma once

// 동적 배열을 정의하는 구조체
typedef struct _tagArr {
    int* pInt;  // 가변 배열을 저장할 포인터
    int iCount;  // 배열에 현재 저장된 데이터 개수
    int iMaxCount;  // 배열의 최대 용량
} tArr;

// 배열 초기화
void InitArr(tArr* _pArr);

// 재할당
void Reallocate(tArr* _pArr, int newMax);

// 데이터 추가
void PushBack(tArr* _pArr, int value);

// 메모리 해제
void ReleaseArr(tArr* _pArr);

 

Arr.cpp

#include "Arr.h"
#include <cstdlib>

// 배열 초기화
void InitArr(tArr* _pArr) {
    _pArr->pInt = (int*)malloc(sizeof(int) * 2);   // 2개의 정수를 저장할 메모리 할당
    _pArr->iCount = 0;  // 처음에는 데이터가 없으므로 iCount는 0
    _pArr->iMaxCount = 2;  // 배열의 용량은 2로 초기화
}

// 재할당
void Reallocate(tArr* _pArr, int newMax) {
    _pArr->pInt = (int*)realloc(_pArr->pInt, sizeof(int) * newMax);  // 메모리 재할당
    _pArr->iMaxCount = newMax;  // 최대 용량 업데이트
}

// 데이터 추가
void PushBack(tArr* _pArr, int value) {
    if (_pArr->iCount == _pArr->iMaxCount) {  // 배열이 가득 찼으면
        Reallocate(_pArr, _pArr->iMaxCount * 2);  // 배열 크기를 두 배로 늘린다.
    }
    _pArr->pInt[_pArr->iCount] = value;  // 새로운 값 추가
    _pArr->iCount++;  // 데이터 개수 증가
}

// 메모리 해제
void ReleaseArr(tArr* _pArr) {
    free(_pArr->pInt);  // 동적 메모리 해제
    _pArr->pInt = nullptr;  // 포인터를 nullptr로 설정하여 안전하게 만든다.
    _pArr->iCount = 0;  // 데이터 개수 초기화
    _pArr->iMaxCount = 0;  // 최대 용량 초기화
}

 

main.cpp

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

using namespace std;

int main() {
    tArr s;

    // 배열 초기화
    InitArr(&s);

    // 배열에 데이터 추가
    PushBack(&s, 10);
    PushBack(&s, 20);
    PushBack(&s, 30);

    // 배열의 내용 출력
    for (int i = 0; i < s.iCount; i++) {
        cout << s.pInt[i] << endl;
    }

    // 메모리 해제
    ReleaseArr(&s);

    return 0;
}

▽ 출력 결과

10
20
30


가변 배열 - C++

구조체동적 할당을 이용하여 가변 배열 자료형 만들기

Arr.h

#pragma once

// 가변 배열 자료형 tArr (int)
typedef struct _tagArr
{
	int* pInt;
	int iCount;
	int iMaxCount;
} tArr;

// 배열 초기화 함수
void InitArr(tArr* _pArr);

// 데이터 추가 함수
void PushBack(tArr* _pArr, int _iData);

// 공간 추가 확장(재할당) 함수
void Reallocate(tArr* _pArr);

// 배열 메모리 해제 함수
void ReleaseArr(tArr* _pArr);

 

Arr.cpp

#include "Arr.h"

// 배열 초기화
void InitArr(tArr* _pArr) {
    _pArr->pInt = new int[2];  // new[]를 사용하여 메모리 할당
    _pArr->iCount = 0;  // 현재 입력된 데이터 개수
    _pArr->iMaxCount = 2;  // 최대 크기
}

// 공간 추가 확장 (재할당)
void Reallocate(tArr* _pArr) {
    // 1. 두 배 크기로 새로운 공간을 동적으로 할당
    int* pNew = new int[_pArr->iMaxCount * 2];  // new[]를 사용하여 메모리 할당

    // 2. 기존 공간의 데이터를 새로 할당한 공간으로 복사
    for (int i = 0; i < _pArr->iCount; ++i) {
        pNew[i] = _pArr->pInt[i];
    }

    // 3. 기존 공간의 메모리를 delete[]로 해제
    delete[] _pArr->pInt;

    // 4. 배열을 새로 할당된 공간을 가리키도록 변경
    _pArr->pInt = pNew;

    // 5. iMaxCount에 변경 사항 적용
    _pArr->iMaxCount *= 2;
}

// 데이터 추가
void PushBack(tArr* _pArr, int _iData) {
    // 힙 영역에 할당된 공간이 꽉 찼다면,
    if (_pArr->iMaxCount <= _pArr->iCount) {
        // 공간이 부족하면 재할당 (공간 확장 함수 Reallocate() 호출)
        Reallocate(_pArr);
    }

    // 데이터 추가
    _pArr->pInt[_pArr->iCount++] = _iData;
}

// 메모리 해제
void ReleaseArr(tArr* _pArr) {
    // delete[]로 메모리 해제
    delete[] _pArr->pInt;
    _pArr->iCount = 0;
    _pArr->iMaxCount = 0;
}

 

main.cpp

#include "Arr.h"

#include <cstdlib>
#include <stdio.h>

// 가변 배열 : 데이터를 계속 확장해가면서 넣는 형태의 자료구조
int main()
{
    tArr s1 = {};

    // 배열 초기화
    InitArr(&s1);

    // 재할당
    Reallocate(&s1);
    
    // 데이터 추가
    for (int i = 0; i < 10; ++i)
    {
        PushBack(&s1, i);  // s1에 0~9까지 넣어라.
    }
    
    for (int i = 0; i < s1.iCount; ++i)
    {
        printf("%d\n", s1.pInt[i]);  // i번째 데이터 출력
    }

    // 배열 메모리 해제
    ReleaseArr(&s1);

    return 0;
}

분할 구현 이용해서 가변 배열 자료형 만들기

Arr.h

#pragma once

// 동적 배열을 정의하는 구조체
typedef struct _tagArr {
    int* pInt;  // 동적 배열을 저장할 포인터
    int iCount;  // 배열에 현재 저장된 데이터 개수
    int iMaxCount;  // 배열의 최대 용량
} tArr;

// 배열 초기화
void InitArr(tArr* _pArr);

// 재할당
void Reallocate(tArr* _pArr, int newMax);

// 데이터 추가
void PushBack(tArr* _pArr, int value);

// 메모리 해제
void ReleaseArr(tArr* _pArr);

 

Arr.cpp

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

// 배열 초기화
void InitArr(tArr* _pArr) {
    _pArr->pInt = new int[2];  // 2개의 정수를 저장할 메모리 할당 (new를 사용)
    _pArr->iCount = 0;  // 처음에는 데이터가 없으므로 iCount는 0
    _pArr->iMaxCount = 2;      // 배열의 용량은 2로 초기화
}

// 재할당
void Reallocate(tArr* _pArr, int newMax) {
    int* newArray = new int[newMax];  // 새로운 배열을 할당
    for (int i = 0; i < _pArr->iCount; i++) {
        newArray[i] = _pArr->pInt[i];  // 기존 데이터를 새로운 배열로 복사
    }
    delete[] _pArr->pInt;  // 기존 배열의 메모리 해제
    _pArr->pInt = newArray;  // 새로운 배열로 포인터 변경
    _pArr->iMaxCount = newMax;  // 최대 용량 업데이트
}

// 데이터 추가
void PushBack(tArr* _pArr, int value) {
    if (_pArr->iCount == _pArr->iMaxCount) {  // 배열이 가득 찼으면
        Reallocate(_pArr, _pArr->iMaxCount * 2);  // 배열 크기를 두 배로 늘린다.
    }
    _pArr->pInt[_pArr->iCount] = value;  // 새로운 값 추가
    _pArr->iCount++;  // 데이터 개수 증가
}

// 메모리 해제
void ReleaseArr(tArr* _pArr) {
    delete[] _pArr->pInt;  // 동적 메모리 해제 (delete[] 사용)
    _pArr->pInt = nullptr;  // 포인터를 nullptr로 설정하여 안전하게 만든다.
    _pArr->iCount = 0;  // 데이터 개수 초기화
    _pArr->iMaxCount = 0;  // 최대 용량 초기화
}

 

main.cpp

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

using namespace std;

int main() {
    tArr s;

    // 배열 초기화
    InitArr(&s);

    // 배열에 데이터 추가
    PushBack(&s, 10);
    PushBack(&s, 20);
    PushBack(&s, 30);

    // 배열의 내용 출력
    for (int i = 0; i < s.iCount; i++) {
        cout << s.pInt[i] << endl;
    }

    // 메모리 해제
    ReleaseArr(&s);

    return 0;
}

▽ 출력 결과

10
20
30