근래 C++에 대한 감각이 무뎌지는 듯해서 내가 복습하는 내용을 정리할 예정.
포인터란?
포인터(Pointer)는 단적으로 말해 주소를 가리키는(Point) 변수라 할 수 있다.
우리가 늘 보는 마우스 포인터를 생각할 수 있겠다.
마우스 포인터가 아이콘이나 버튼을 가리키듯이 포인터는 주소를 가리킨다.
처음 프로그래밍을 배울 때 포인터가 어려운 이유는 포인터가 완전히 다른 개념이라고 생각하기 때문이다.
포인터를 늘 보던 변수라고 생각하자
포인터에 사용되는 뭔가 특별한 장치가 있는 것이 아니다. 이는 그저 int형 변수가 정수를 갖고 있듯이 주소를 갖고 있다.
단순히 우리가 배울 때 '포인터는 가리키는 것' 이라는 개념에 정신이 팔려서 이해가 잘 되지 않았던 것이다.
포인터가 단순히 변수와 다르지 않다는 것을 메모리를 통해 알 수 있다.
이를 직접 코드를 작성 후 실행하여 알아보자.
아래와 같이 포인터는 단순히 변수 선언 시 *을 붙이는 것으로 선언할 수 있다.
int main()
{
// 변수 생성
int num = 1;
int* p;
// 포인터 p에 num의 주소 전달
p = #
return 0;
}
값만 출력할 뿐인 간단한 코드로 디버깅 해 보자.
p = &num 까지 실행되게 중단점을 걸었다.
먼저 메모리에서 &num을 입력해 num의 메모리 주소와 그 값을 찾았다.
주소는 0x00DAF870이고 값은 아까 초기화한 대로 1이다.
이제 포인터 p를 찾아보자.
&p를 입력해 p의 메모리 주소를 찾았다.
p의 주소는 0xDAF864이고 값은 00DAF870이다. 어? 아까 본 num의 주소와 동일하다.
int형 변수인 num이 정수 1을 갖고 있듯이, int형 포인터인 p는 num의 주소값(0x00DAF870)을 갖고 있다.
포인터는 그저 주소값을 갖고 있을 뿐이고, 그 주소를 가리킨다고(Point) 생각하기 때문에 포인터(Pointer)라고 부른다.
포인터의 출력
프로그램을 만들다 보면 분명 포인터를 통해 출력해야 할 일이 생기기 마련.
포인터를 선언할 때 *라는 기호를 사용했는데, 이는 포인터임을 나타내는 것일 뿐만 아니라 포인터가 가리키는 주소가 갖고 있는 값을 나타내는 것 이이도 하다.
위의 코드에 아래와 같은 코드를 추가한다.
// 변수 생성
int num = 1;
int* p;
// 포인터 p에 num의 주소 전달
p = #
// num 출력
std::cout << "값: " << num << " / 주소: " << &num << "\n";
// p 출력
std::cout<< "값: " << *p << " / 주소: " << p << " / 포인터의 주소 : " << &p << "\n";
빌드 후 표준 출력으로 결과를 출력한다.
포인터 p는 num을 가리키기 때문에 num과 같은 값과 주소를 가진다.
포인터 자체는 별도로 존재하기 때문에 포인터의 주소는 다르다.
포인터의 용도
그럼 포인터를 어디에 쓸 수 있을까? 우리는 이전까지 포인터 없이 잘 해쳐왔기 때문에 그 용도를 쉬이 떠올리기 힘들다.
이 예시로 임의의 함수 int SetHp(int curHp)를 생각해 보자.
int SetHp(int curHp)
{
curHp = 100;
return curHp;
}
이 함수는 int형 값을 매개변수로 활용한다. 매개변수 curHp를 받고 100으로 값을 설정 후 리턴한다.
포인터 없이 이렇게 사용해도 괜찮은 것 같지만 포인터를 사용하는 것에 비해서 디메리트가 존재한다.
- 인자로 받은 값을 별도의 장소에 복사해서 보관한다.
원래의 변수에 직접 반영이 안 된다. - 반영이 안 되는 만큼 리턴을 해야 한다.
포인터를 사용하면 리턴을 할 필요가 없다.
포인터는 주소만 넘겨주면 끝이기 때문에 명령어 사용을 줄일 수 있다.
포인터를 사용한 아래의 예시를 보자.
// 포인터를 인자로 받지 않음
int SetHp(int curHp)
{
curHp = 100;
return curHp;
}
// 포인터를 인자로 받음
int SetHpPtr(int* curHp)
{
*curHp = 50;
return *curHp;
}
// 포인터 p에 num의 주소 전달
p = #
int val = 0;
// 포인터 미사용
val = SetHp(num);
std::cout << "num의 값: " << num << " / " << "val의 값: " << val << "\n";
// 포인터 사용
val = SetHpPtr(p);
std::cout << "num의 값: " << num << " / " << "val의 값: " << val << "\n";
포인터를 사용하는 새로운 함수인 SetHpPtr()을 만들었다. 실행결과는 아래와 같다.
포인터를 사용하지 않은 상태에선 num의 값이 변하지 않는다. 그냥 값을 복사해서 넘겨준 것이기 때문에 num과는 아무런 상관없는 연산이 함수에서 이루어졌다.
반면 포인터를 사용한 상태에선 num의 값이 변했고 바뀐 curHp의 값도 정상적으로 리턴됐다.
리턴하기 전부터 curHp에 값이 제대로 반영이 됐기 때문에 리턴의 필요성도 사라졌다.
이를 어셈블리로 보면 아래와 같다.
포인터를 사용하지 않은 쪽은 매개변수 curHp에 직접 복사하기 때문에 원래의 값과는 상관이 없어진다.
별도의 레지스터를 사용하기 않았기에 함수가 종료되면 값도 사라진다.
포인터를 사용한 쪽은 eax 레지스터에 주소를 복사하고, 복사한 eax 레지스터의 값에 50을 복사한다.
주소를 그대로 활용하기 때문에 인수로 넘겨받은 변수에 제대로 값이 반영된다.
이렇게 포인터를 활용해 경우에 따라 별도의 변수를 생성하지 않고도 원하는 값을 얻을 수 있다.
이후엔 스마트 포인터에 대해 정리해 볼 예정.
'Study > C++ & C#' 카테고리의 다른 글
[C++] 스마트 포인터 (0) | 2023.02.04 |
---|---|
[C++] 다중 포인터 (0) | 2023.01.27 |
[C#, Python] C# 라이브러리를 이용한 discord.py 봇 개발 (2/2) (0) | 2023.01.15 |
[C#, Python] C# 라이브러리를 이용한 discord.py 봇 개발 (1/2) (0) | 2023.01.15 |
[C#] WPF로 만들어 본 한>중>한 번역기 (0) | 2022.12.31 |