A. 포인터
1. 포인터의 개요
a. 주소값의 이해
🌟 데이터의 주소값이란 해당 데이터가 저장된 메모리의 시작 주소를 의미
C언어에서는 주소값을 1바이트 크기의 메모리 공간으로 나누어 표현
b. 포인터란?
🌟 메모리의 주소값을 저장하는 변수, 포인터 변수라고도 부름
포인터와 연관되어 사용되는 연산자는 & 주소연산자와 * 참조 연산자가 있음
2. 포인터 연산자
a. 주소(번지) 연산자
🌟 변수의 이름 앞에 사용하여, 해당 변수의 주소값을 반환
'&'기호는 앰퍼샌드(ampersand)라고 읽음
b. 참조 연산자
🌟 포인터의 이름이나 주소 앞에 사용해 포인터에 저장된 주소에 저장되어 있는 값 반환
'*' 기호는 역참조 연산자로 에스크리터(asterisk operator)라고도 불림
💙 * 연산자의 다양성
이항 연산자에 사용하면 곱셈 연산
포인터의 선언시
메모리 접근시에도 사용
3. 포인터의 선언과 참조
a. 포인터의 선언
포인터를 선언할 때 참조 연산자(*)의 앞과 뒤에 존재하는 공백은 무시
동시 선언 가능하지만 코드에 유의해야 함
📘 문법
타입* 포인터이름; //선언만 하는법
타입 포인터이름* 포인터이름* // 동시 선언하는 법
//타입 : 포인터가 가리키고자 하는 변수의 타입
//포인터 이름 : 포인터가 선언된 후에 포인터에 접근하기 위해 사용
⏳ 예제 : 포인터 동시 선언시 주의사항
int* ptr1, ptr2; // ptr1 : int형 포인터 ptr1 : int형 변수
int *ptr1, *ptr2; // 둘 다 int형 포인터
b. 포인터의 선언과 초기화
포인터를 선언 후 참조 연산자(*)를 사용하기 전에 포인터는 반드시 초기화해야 함
👉 초기화하지 않은 채로 참조 연산자를 사용하게 되면, 어딘지 알 수 없는 메모리 장소에 값을 저장하는 것임
💥 이러한 동작은 위험하고 오류는 디버깅하기도 매우 힘듦
📘 문법
타입* 포인터이름 = &변수이름;
또는
타입* 포인터이름 = &주소값;
c. 포인터의 참조
선언된 포인터는 참조 연산자(*)를 사용하여 참조
⏳ 예제 : 포인터의 주소값과 함께 포인터가 가리키고 있는 주소값의 데이터를 참조
int x = 7; // 변수의 선언
int *ptr = &x; // 포인터의 선언
int **pptr = &ptr; // 포인터의 참조
🎨 도식화
🧡 포인터의 크기와 워드(word)
워드(Word)란 컴퓨터(CPU)가 한 번에 처리 할 수 있는 정보의 양 = 데이터의 크기
포인터변수는 메모리에서 변수의 위치를 나타내는 주소를 다루는 변수
👉 크기는 CPU나 컴파일러의 정책에 의해 달라짐
⦁ 32비트 CPU에서는 1word = 32bit = 4byte => 포인터 변수의 크기 또한 4byte
⦁ 64비트 CPU에서는 2배라서 포인터의 크기가 8
⏳ 예제
#include <iostream>
using namespace std;
int main(void)
{
int num1 = 1234;
double num2 = 3.14;
int* ptr_num1 = &num1;
double* ptr_num2 = &num2;
cout << "포인터의 크기는 " << sizeof(ptr_num1) << "입니다." << endl;
cout << "포인터 ptr_num1가 가리키고 있는 주소값은 " << ptr_num1 << "입니다." << endl;
cout << "포인터 ptr_num1가 가리키고 있는 주소에 저장된 값은 " << *ptr_num1 << "입니다." << endl;
cout << "포인터 ptr_num2가 가리키고 있는 주소값은 " << ptr_num2 << "입니다." << endl;
cout << "포인터 ptr_num2가 가리키고 있는 주소에 저장된 값은 " << *ptr_num2 << "입니다.";
return 0;
}
✨ 실행결과
포인터의 크기는 8입니다.
포인터 ptr_num1가 가리키고 있는 주소값은 0x7fffe2bdbacc입니다.
포인터 ptr_num1가 가리키고 있는 주소에 저장된 값은 1234입니다.
포인터 ptr_num2가 가리키고 있는 주소값은 0x7fffe2bdbad0입니다.
포인터 ptr_num2가 가리키고 있는 주소에 저장된 값은 3.14입니다.
🎨 도식화
2. 포인터 연산
a. 포인터 연산의 의미
포인터는 정수 연산만이 의미가 있음 (나머지 다 x)
- 포인터끼리의 덧셈, 곱셈, 나눗셈 : 아무런 의미 X
- 포인터끼리의 뺄셈 : 두 포인터 사이의 상대적 거리
- 포인터에 정수를 더하거나 뺄 수는 있지만, 실수와의 연산은 허용 X
- 포인터끼리 대입하거나 비교할 수 있음
b. 타입별 포인터 연산
포인터 연산에서 포인터 연산 후 각각의 포인터가 가리키고 있는 주소는 포인터의 타입에 따라 달라짐
증가 폭은 포인터가 가리키는 변수의 타입의 크기와 같음 (뺄셈도 동일)
c. 포인터와 배열의 관계
배열의 이름은 그 값을 변경할 수 없는 상수라는 점을 제외하면 포인터와 같음
👉 즉, 배열의 이름을 포인터처럼 사용하거나 포인터를 배열의 이름처럼 사용할 수 있음
배열의 이름 = 첫 번째 요소의 주소
⏳ 예제 : 포인터를 배열의 이름처럼 사용
#include <iostream>
using namespace std;
int main(void)
{
int arr[3] = {10, 20, 30}; // 배열 선언
int* ptr_arr = arr; // 포인터에 배열의 이름을 대입함.
cout << "배열의 이름을 이용하여 배열 요소에 접근 : " << arr[0] << ", " << arr[1] << ", " << arr[2] << endl;
cout << " 포인터를 이용하여 배열 요소에 접근 : " << ptr_arr[0] << ", " << ptr_arr[1] << ", " << ptr_arr[2] << endl;
cout << "배열의 이름을 이용한 배열의 크기 계산 : " << sizeof(arr) << endl;
cout << " 포인터를 이용한 배열의 크기 계산 : " << sizeof(ptr_arr);
return 0;
}
✨ 실행결과
배열의 이름을 이용하여 배열 요소에 접근 : 10, 20, 30
포인터를 이용하여 배열 요소에 접근 : 10, 20, 30
배열의 이름을 이용한 배열의 크기 계산 : 12
포인터를 이용한 배열의 크기 계산 : 8
하지만 배열의 이름을 이요한 크기 계산시에는 다르게 출력됨
📌 배열은 배열의 크기만큼, 포인터는 포인터의 크기만큼
d. 배열의 포인터 연산
⏳ 예제 : 배열의 이름을 포인터처럼 사용
#include <iostream>
using namespace std;
int main(void)
{
int arr[3] = {10, 20, 30}; // 배열 선언
cout << " 배열의 이름을 이용하여 배열 요소에 접근 : " << arr[0] << ", " << arr[1] << ", " << arr[2] << endl;
cout << "배열의 이름으로 포인터 연산을 해 배열 요소에 접근 : " << *(arr+0) << ", " << *(arr+1) << ", " << *(arr+2);
return 0;
}
✨ 실행결과
배열의 이름을 이용하여 배열 요소에 접근 : 10, 20, 30
배열의 이름으로 포인터 연산을 해 배열 요소에 접근 : 10, 20, 30
🎨 도식화
✒ 따라서 배열 이름과 포인터 사이에는 다음과 같은 공식이 성립
📘 공식
arr이 배열의 이름이거나 포인터이고 n이 정수일 때,
arr[n] == *(arr + n)
👉 위의 공식은 1차원 배열뿐만 아니라 다차원 배열에서도 언제나 성립함
💥 배열의 크기에 주의
배열에 관계된 연산을 할 때는 언제나 배열의 크기를 넘어서는 접근을 하지 않도록 주의
포인터 연산을 이용하여 계산하다가 배열의 크기를 넘어서는 접근을 하는 경우, C++ 컴파일러는 어떠한 오류도 발생시키지 않고 잘못된 결과만을 반환하므로 C++로 프로그래밍할 때에는 언제나 배열의 크기에 주의!!
3. 메모리의 동적 할당
a. 메모리의 동적 할당이란?
🌟 런 타임에 메모리를 할당받는 것
데이터 영역과 스택 영역에 할당되는 메모리의 크기는 컴파일 타임(compile time)에 미리 결정됨
하지만 힙 영역의 크기는 프로그램이 실행되는 도중인 런 타임(run time)에 사용자가 직접 결정 = 동적 할당
📌 포인터의 가장 큰 목적
런 타임에 이름 없는 메모리를 할당받아 포인터에 할당하여, 할당받은 메모리에 접근하는 것
C언어에서는 malloc() 함수 등의 라이브러리 함수를 제공하여 이러한 작업을 수행(C++에서도 가능)
C++에서는 메모리의 동적 할당 및 해제를 위한 더욱 효과적인 방법을 new 연산자와 delete 연산자를 통해 제공
b. new 연산자
🌟자유 기억 공간(free store)이라고 불리는 메모리 공간(memory pool)에 객체를 위한 메모리를 할당
- new 연산자를 통해 할당받은 메모리는 따로 이름이 없으므로 해당 포인터로만 접근
- 메모리가 부족해 못만들면 널 포인터를 반환
- C의 malloc()이나 calloc() 함수와 동일
📘 문법
타입* 포인터이름 = new 타입;
//첫 번째 타입: 데이터에 맞는 포인터를 선언
//두 번째 타입: 메모리의 종류를 지정
c. delete 연산자
🌟 동적으로 할당받은 메모리를 다시 운영체제로 반환
- new 연산자를 통해 생성한 메모리가 아닌 변수를 선언하여 생성한 메모리는 delete 연산자로 해제할 수 없음
- delete 연산자는 반드시 new 연산자를 통해 할당된 메모리를 해제할 때에만 사용
- 한 번 해제한 메모리를 다시 해제하려고 시도하면 오류가 발생
- C의 free()함수와 동일
📘 문법
delete 포인터이름;
⏳ 예제 : 메모리를 할당받고 반환
#include <iostream>
using namespace std;
int main(void)
{
int* ptr_int = new int;
*ptr_int = 100;
double* ptr_double = new double;
*ptr_double = 100.123;
cout << "int형 숫자의 값은 " << *ptr_int << "입니다." << endl;
cout << "int형 숫자의 메모리 주소는 " << ptr_int << "입니다." << endl;
cout << "double형 숫자의 값은 " << *ptr_double << "입니다." << endl;
cout << "double형 숫자의 메모리 주소는 " << ptr_double << "입니다." << endl;
delete ptr_int;
delete ptr_double;
return 0;
}
✨ 실행결과
int형 숫자의 값은 100입니다.
int형 숫자의 메모리 주소는 0x55fa5bb1feb0입니다.
double형 숫자의 값은 100.123입니다.
double형 숫자의 메모리 주소는 0x55fa5bb1fed0입니다.
출처 : http://www.tcpschool.com/cpp/cpp_arrayPointer_pointerIntro
'프로그래밍 언어 > C++' 카테고리의 다른 글
[게임 프로그래머 입문 올인원] 객체지향 : 객체지향 개론 (27강) (2) | 2023.09.11 |
---|---|
C++ 문자열 총정리 (0) | 2023.09.09 |
[자료형(data type)] C++ 파생형 배열 총정리 (0) | 2023.09.08 |
[게임 프로그래머 입문 올인원] 포인터와 배열 : 문자열, 참조 (22, 23강) (0) | 2023.09.07 |
[게임 프로그래머 입문 올인원] 포인터와 배열 : 포인터 (20, 21강) (0) | 2023.09.06 |