C++

12. Pointer And Reference, Memory Allocate (1)

Kelvin의 게임개발 2024. 1. 30. 00:01
728x90
반응형

1. What is a Pointer?

 

포인터 타입 변수는 다른 변수의 메모리 주소를 저장한다

 

포인터는 주소 전달 방식으로서 함수의 매개변수에 사용되고 배열에 아주 효과적으로 사용되며 힙 영역에 메모리를 동적으로 할당할 때 사용한다 또한 임베디드 시스템에서 특정 메모리 주소 액세스 권한이 필요할 때 사용된다

 

타입* 변수명;으로 포인터 변수를 선언할 수 있다

이때 *는 수학적의미가 아닌 포인터 변수를 의미한다

 

포인터 변수도 일반 변수와 마찬가지로 초기화 하지 않으면 쓰레기값(Garbage Data)이 들어가게 된다 꼭 사용하기 전에 초기화를 하고 사용해야 한다

 

포인터 변수의 쓰레기값은 결국 주소이다 만약 이 주소가 프로그래머가 알지 못하는 유효한 곳이라면 접근 시 상당히 위험한 결과를 나타낼 수 있다

 

포인터 변수는 일반 변수와 같은 방식으로 초기화가 가능하다

 

nullptr은 말 그대로 가리키는 주소가 없다는 의미이다 (Null), nullptr인 포인터 변수를 참조하게 되면 크래쉬가 발생한다 (거의 대부분의 크래쉬 원인)

 

nullptr은 0으로 출력된다

int main()
{
	int* Int_Ptr; //int타입 포인터 변수
	double* Double_Ptr; //double타입 포인터 변수


	//일반 변수와 같은 방식으로 초기화 가능
	int* IntPtr1{};
	int* IntPtr2{ nullptr };
	int* IntPtr3 = nullptr;

	return 0;
}

 

 

2. Access And Store Address in a Pointer Variable

 

C++에는 주소연산자 &가 존재한다 이는 변수의 메모리주소를 나타낸다 (lvalue여야 한다, 상수에 사용은 불가능하다)

 

따라서 포인터 변수에는 &변수;로 특정 변수의 메모리 주소값을 넣어줄 수 있다

 

포인터 변수의 크기는 전부 동일하다 (변수 자체의 크기가 아닌 주소를 담고 있기 때문에), 32bit설정이면 4byte, 64bit설정이면 8byte이다
 
포인터 변수에 주소를 넣어줄 때 포인터 변수의 타입과 주소를 타고 갔을때 나오는 변수의 타입을 항상 일치시켜야 한다 (다르면 컴파일 에러 발생)

int main()
{
	int num{ 10 };

	# //num변수의 메모리 주소를 나타낸다 ex) 0x61ff18

	int* temp{ nullptr };
	temp = #

	cout << sizeof temp << endl; //설정에 따라 4, 8byte를 출력한다 (int*, double*, string* 등 타입에 상관없이 전부 크기가 동일함)


	int IntScore{ 10 };
	double DoubleScore{ 10.7 };

	int* Score_Ptr;

	Score_Ptr = &IntScore; //가능
	Score_Ptr = &DoubleScore; //컴파일 에러 발생 -> 포인터 변수 타입과 주소를 타고 갔을 때의 변수 타입이 다르기 때문 (int*, double)

	return 0;
}

 

 

3. Dereferencing a Pointer

 

포인터 변수가 가리키는 데이터에 접근하기 위해서는 포인터가 가리키는 주소를 타고 가야 한다 (Dereferencing Pointer)

 

*포인터변수;로 포인터가 가리키는 데이터에 접근이 가능하다 (이떄 *는 간접연산자라고 한다)

 

이렇게 접근한 데이터는 수정도 가능하다

int main()
{
	int score{ 100 };
	int* ScorePtr = &score; //score변수의 주소로 ScorePtr이라는 포인터 변수 초기화

	*ScorePtr = 300; //ScorePtr 포인터 변수가 가리키고 있는 데이터에 접근해서 값 수정

	cout << score << endl; //score값이 300으로 변경됨


	//같은 방식의 string예제
	string Name{ "Kelvin" };
	string* NamePtr = &Name;

	*NamePtr = "Jacob";

	cout << Name << endl; //Jacob출력

	return 0;
}

 

 

4. Dynamic Memory Allocation

 

동적 메모리 할당은 대표적인 포인터 사용 예시이다

 

우리는 프로젝트를 개발할 때 어느정도의 메모리가 필요한지 정확히 알 수 없다 따라서 런타임에 메모리를 할당할 수 있어야 한다 (Array, Vector의 차이와 같음)

 

Vector가 Array와 다르게 동적으로 크기를 조절할 수 있는 이유는 Vector는 메모리를 heap에서 할당/해제하기 때문이다

 

new라는 키워드로 heap에 동적메모리 할당이 가능하다 new 타입;으로 동적할당 한다 (타입의 크기에 맞는 동적 메모리를 할당하고 주소로 반환한다 따라서 포인터 변수에 할당이 가능하다)
 
배열타입은 다음과 같이 할당한다 new 타입[개수];
일반 배열과 마찬가지로 배열의 시작 주소가 반환된다
 
일반 변수, 포인터 변수와 마찬가지로 초기화 하지 않으면 쓰레기값이 들어가게 된다
 
메모리를 할당하고 다 사용했으면 항상 해제를 해주어야 한다 (동적 메모리 할당만 하고 해제하지 않으면 메모리 누수가 발생하여 메모리가 부족해진다 )

 

delete라는 키워드로 heap에 할당된 동적메모리를 해제할 수 있다 delete 포인터변수;로 해제한다 이때 포인터 변수는 동적 할당 한 메모리 주소를 갖고있는 변수이다
 
배열타입은 다음과 같이 해제한다 delete[] 포인터변수;
 
delete를 하는 순가 해당 포인터 변수는 nullptr이 되기 때문에 이미 delete된 메모리 주소를 참조하려 하면 크래쉬가 발생한다
 
동적할당은 stack에서는 불가능하고 오직 heap영역에서만 가능하다

int main()
{
	int* Int_Ptr{ nullptr };

	Int_Ptr = new int; //heap에 int크기에 맞는 동적 메모리 할당 -> 주소로 반환하기 때문에 포인터 변수에 할당

	*Int_Ptr; //Garbage Data

	*Int_Ptr = 10; //포인터 변수가 가리키는 값을 10으로 변경

	delete Int_Ptr; //Int_Ptr이 가리키고 있는 메모리 해제


	int* ArrayPtr{ nullptr };
	int Size{};

	cin >> Size;

	ArrayPtr = new int[Size]; //배열 타입 동적 메모리 할당

	delete[] ArrayPtr;

	return 0;
}

 

 

5. The Relationship Between Arrays and Pointers

 

배열의 이름은 배열의 시작 주소이고 포인터 변수는 주소를 담는 변수이다 따라서 포인터 변수에 배열 이름을 넣을 수 있다 -> 포인터 변수와 배열 이름은 동일하게 사용될 수 있다

 

그 결과 배열의 이름을 넣은 포인터 변수로도 배열의 인덱스 값에 접근이 가능하다 -> 배열의 이름을 넣은 포인터 변수명[index];로 접근이 가능하다는 의미이다

 

배열의 이름을 넣은 포인터 변수에 간접연산자를 사용하여 배열의 0번째 인덱스 값에 접근도 가능하다 -> *배열의 이름을 넣은 포인터 변수명; 으로 0번째 index값에 접근이 가능하다는 의미이다
 
포인터 변수에 값을 더하게 되면 그 값만큼 증가하는게 아니라 해당 포인터 변수의 타입의 크기 * 값만큼 증가하게 된다
ex) int* 일경우에 3을 더하면 0x61ff10 -> 0x61ff22가 된다 (int는 4byte이기 때문에 12가 늘어남)

 

포인터 변수에 값을 더한다는건 그 만큼 주소를 이동한다는 의미이다 따라서 배열의 원하는 index값에 접근할 때 사용할 수 있다 (배열도 마찬가지로 사용 가능)
ex) *ScoresPtr는 배열의 0번째 값, *(ScoresPtr + 1);은 배열의 1번째 값이 된다

int main()
{
	int scores[]{ 10,20, 30,40, 50 };

	cout << scores << endl; //scores배열의 시작 주소
	cout << *scores << endl; //scores배열의 첫번째 값 10

	int* ScoresPtr{ scores }; //배열의 이름은 배열의 시작 주소이고 포인터 변수는 주소를 담을 수 있기 때문에 할당 가능

	*ScoresPtr; //*scores와 마찬가지로 scores배열의 첫번째 값 10이 나온다

	ScoresPtr[1]; //배열 이름으로 인덱스에 접근하는 방식을 포인터 변수로도 할 수 있다

	cout << scores << endl; //scores배열의 시작 주소

	cout << *(ScoresPtr + 1) << endl; //+1이 되었기 때문에 다음주소로 이동 -> scores배열의 1번째 index값인 20이 나오게 된다

	cout << *(scores + 1) << endl; //+1이 되었기 때문에 다음주소로 이동 -> scores배열의 1번째 index값인 20이 나오게 된다


	int* TestPtr;
	int TestVal{ 100 };

	TestPtr = &TestVal;

	cout << TestPtr << endl; //0x61ff10
	cout << TestPtr + 2 << endl; //0x61ff18; int는 4byte이기 때문에 8이 늘어남 

	return 0;
}

 

 

728x90
반응형

'C++' 카테고리의 다른 글

14. OOP Class & Object (1)  (77) 2024.02.22
13. Pointer And Reference (2)  (63) 2024.02.14
11. Function (2)  (108) 2024.01.26
10. Function (1)  (127) 2024.01.25
9. Character & String  (134) 2024.01.08