본문 바로가기

C++

[TIL 13강] 열거체, C언어 동적할당(feat. Heap)

오늘의 TIL 목차 (22.08. 11)

 

  • 열거체 ( enum )
  • 동적 할당 ( C언어 : malloc(), calloc(), free() )
  • 동적 할당 ( C++ : new , delete() )

열거체


[ 열거체 ( enum ) ]

: 상수를 사용자가 정의한 이름으로 열거하는 것

enum 열거형이름
{
열거체0
열거체1
...
};

■ 기능 / 주의

  • 열거체는 원소의 개수와 상관없이 4byte 크기를 갖는다.
  • 열거체는 초깃값을 할당할 수 있다.
  • 열거체는 상수로, 사용자 정의한 이름의 상수이다.
  • 열거체는 0부터 순서대로 +1씩 증가하며, 초기화 시 초기화된 값에 이어서 +1이 된다.
  • switch문 등에서 상수를 사용할 때 무엇을 뜻하는지 명확히 표기하고 싶을 때 주로 사용된다.
  • 오류를 표기하기 위해서도 주로 사용된다.
  • 열거체는 사용할 함수 안에 따로 정의하지 않고 바로 사용 가능하다.
    - 열거형이 여러 개일 경우 스코프연산자로 지정해서 사용

■ 예시

enum STATE
{
	IDLE,	// 열거체는 초기화를 안한다면 0부터 시작하여 차례로 1씩 증가한다.
	WALK = 10, // 선언과 동시에 초기화가 가능
	ATTACK,
	DEAD = 20,
	RUN,
	END
};

void main(void)
{

	cout << sizeof(STATE) << endl; // 열거체는 원소의 개수와 무관하게 무조건 4byte의 크기를 갖는다.
	STATE	eState = END; // eState == 22
	cout << eState << endl;
	cout << STATE::IDLE << endl; 

	cout << ATTACK << endl;
	cout << RUN << endl;

	cout << ATTACK + RUN << endl; // 11 + 21 상수끼리 연산 가능하므로 32
	int iResult = ATTACK + RUN;
}
출력 결과:
4
22
0
11
21
32

※ 스코프연산자 ( :: ) 로 소속 위치를 지정하여 가져올 수 있음

 

C언어 동적할당 ( feat. Heap )


[ Heap ]

: 메모리 저장 장치 ( Code(ROM), Data, Stack, Heap ) 중 하나로 동적할당에 사용되는 메모리 공간

  • 정적변수로 선언 시 프로그램 종료 시까지 해당 변수를 사용하지 않아도 컴파일 때 이미 정의되어 메모리 공간 사용
    이로 인한 메모리 낭비를 줄이기 위해 동적할당을 통해 원할 때 해당 메모리 공간을 사용하고 해제
  • 힙 메모리는 정의한 byte만큼 메모리 공간을 할당해줄뿐 기능 X
  • 포인터를 이용하여 사용자가 원하는 타입으로 힙 메모리 주소에 접근 및 기록
  • 메모리 공간 해제 필수 ( 힙 메모리는 재부팅 전까지 해당 메모리를 계속 할당해놓기 때문에 낭비 발생 )

C언어 | 1. malloc()     2. calloc()     3. free()

[ malloc ]

: 메모리 할당 함수, 사용자가 프로그램이 실행 도중(런타임) 원하는 메모리의 크기를 할당하여 사용하는 것

포인터타입* 포인터명 = (void*)malloc(메모리크기);
// void*인 이유는 포인터 타입에 따라서 할당된 메모리 크기를 몇byte 단위로 잘라 사용할 것인지 정해지기 때문

 

■ 기능 / 주의

  • free(힙메모리주소 = 포인터)를 통해 메모리 공간 해제 필수 ( 미해제 시 메모리 누수 발생 )
  • malloc의 초기화 값은 쓰레기 값
  • 힙 메모리 공간 부족으로 할당 실패 시 NULL 반환

 

■ 예시

- 같은 힙 메모리 공간을 여러 타입의 포인터가 같이 쓰는 경우

int* pI = (int*)malloc(sizeof(int));
// 힙 메모리에 4byte만큼 할당, int형 포인터 pA는 힙 메모리의 주소를 저장하여 int형으로 접근 및 기록
// (int*)malloc(sizeof(int)) 자체가 힙 메모리의 시작 주소를 가짐

float* pF = (float*)pA;
// pA는 4byte만큼 할당된 힙 메모리 주소, 이를 fA포인터는 float형 단위로 접근 및 기록

*pF = 2.4f; // 4바이트 할당된 힙 메모리 주소에 2.4f 저장 (부동소수점 형태로 저장됨)
cout << *pI << endl; // pF가 가리키는 힙 메모리 주소와 같은 주소를 가리키므로 값 2.4f를 int형으로 받아옴

출력 결과:
1075419546 // 2.4f가 부동소수점 형태로 저장됐기 때문에 int로 받아왔을 때 저 값이 됨

- 동적 배열 할당

// 동적 배열 할당
unsigned int* pUi = (unsigned int*)malloc(sizeof(unsigned int)*3); // 4byte 크기만큼 3개 할당됨
cin >> pUi[1] >> pUi[0]; // 입력은 2번 받지만 pszName[0]이 [1]에 넘겨지고 cin에 넘겨지면서 0것만 입력됨
cin >> pUi[2];
cout << "0번째 동적할당과 2번째 동적할당 출력" << endl;
cout << *pUi << "\t" << pUi[2] << endl; // [인덱스]를 지정하지 않으면 [0] 번째꺼 출력

//cout << sizeof(325425) << endl << sizeof("안녕") << endl << sizeof("dainn") << endl;
// 숫자는 42543가 한묶음 4byte, 문자열 상수 dainn은 영어 한 글자당 1byte + '\0' 1byte = 6byte
// "안녕"은 한글 한 글자당 2byte + '\0' 1byte = 5byte

출력 결과:
입력받은 pUi[0]		입력받은 pUi[2]

 

[ calloc ]

: 메모리 할당 함수, malloc과 거의 동일하며 매개변수가 하나 추가되어 배열처럼 개수를 지정할 수 있는 함수

포인터타입* 포인터명 = (void*)calloc(할당할 메모리 개수, 메모리크기);

/* malloc은 배열로 사용하고 싶으면 (메모리크기 * n) 해줬어야했는데
calloc은 (메모리 개수, 메모리크기) 로 지정 */

 

■ 기능 / 주의

  • calloc은 동적할당 시 heap 공간을 자동 0으로 초기화
  • 힙 메모리 공간 부족으로 할당 실패 시 NULL 반환

 

■ 예시

int* iC = (int*)calloc(2, sizeof(int)); // 4byte 크기의 힙 메모리 공간을 2개 할당
cin >> *iC; // [0]번째 iC에 값 입력
cin >> iC[1];
cout << endl << iC[0] << endl << iC[1] << endl << iC[4]; // iC[4]는 쓰레기값 출력

 

[ free ]

: 메모리 해제(반환) 함수, 힙 메모리 할당 시 해제 필수 ( 미해제시 메모리 누수 (=메모리 릭) 발생 )

free(힙메모리주소); // 힙 메모리 주소를 담고 있는 포인터를 주로 매개변수

 

■ 예시

char* pName = (char*)calloc(3,sizeof(char));

if(nullptr == pName) // nullptr 반환 시 ( 메모리 할당 실패 시 nullptr 반환함 )
	cout << "힙 메모리 공간이 부족합니다. 할당 불가 " << endl;

free(pName); // 메모리 해제
pName = nullptr; // 메모리 해제해준 뒤 nullptr 넣어주는 것이 관습

 

C++ 동적할당


C++  |  1. new       2. delete()

[ new ] & [ delete ]

: 메모리 할당 함수, C++에서만 사용 가능하며 malloc, calloc, realloc의 기능을 합친 버전

자료형* 변수 = new 자료형; // 동적할당
자료형* 변수 = new 자료형(초기값); // 동적할당 및 초기화

delete 포인터명; // 메모리 해제(반환)
포인터명 = nullptr;

// 동적 배열 할당
자료형* 변수 = new 자료형[배열의 개수]; // 배열 동적 할당
자료형* 변수 = new 자료형[배열의 개수]{초기화값}; // 배열 동적 할당 및 초기화

delete []포인터명; // 동적 배열 메모리 해제(반환)
포인터명 = nullptr;

→ new 뒤의 자료형은 메모리 크기를 의미

■ 예시

char* szA = new char[3]{ "" }; // 길이 3인 문자열 동적 할당

cin >> szA; // imhome 길이 3 넘어가도 입력 잘되지만 X 
cin >> szA[1]; // dainn 입력 시 2번째 원소에 단문자 d만 입력됨
cout << szA << endl; // idhome 출력
cout << szA[1] << endl; // a, 배열 인덱스는 0부터 시작

int* szInt = new int(100);
int* szArray = new int[3]{1, 2, 3};

*szInt = 300; // szInt는 포인터(=주소)이므로 *포인터로 만들어줘야함
*szArray = 300; // szArray는 배열로 주소이기 때문에 *배열명으로 해줘야함 (0번째에 값 들어감)
szArray[1] = 200; // szArray 1번째에 200 입력됨
cout << *szInt << endl;
for(int i=0; 3 > i ; ++i) // 배열의 경우 '\0' != i 불가능
	cout << szArray[i] << "\t";

출력 결과:
300
300 	200 	3

→ char형의 동적 배열은 문자열 상수 배열이 아닌 문자 배열 ( 문자열 하나 )

■ 기능 / 주의

- 문자열 상수 ( 여러 개 ) 배열 

char* szA = new char[3]{ "" }; // 문자 배열
char** szP = new char*[3]{}; // 문자열 배열

- 포인터 배열과 동적 배열 구분

// 포인터 배열과 동적 배열을 구분하여 이해할 것!

int	iA = 0, iB = 10, iC = 20;

int*	p[3] = { new int, new int, new int }; // 포인터 배열

for (int i = 0; i < 3; ++i)
{
	cout << p[i] << endl;

	delete p[i];
	p[i] = nullptr;
}

int*	p = new int[3]; // 동적 배열
	
delete[]p;
p = nullptr;

- 메모리 누수

메모리 누수의 예
int		iA = 100;

int* p = new int;

*p = iA;

p = &iA; // 힙 메모리가 저장된 p에 iA의 주소값을 넣음 - 힙메모리 주소 잃어버림

*p = 200; // iA = 200;

cout << iA << endl;

-> 힙 메모리 주소를 잃어버려 영영 반환할 수 없게 됨 - 메모리 누수