오늘 나눠볼 얘기는 C++에서 가장 악랄하고 많은 사람들을 괴롭게 만든 포인터에 대해 알아볼 것이다.
포인터 기초 1
포인터는 많은 프로그래머들이 위기를 느끼는 구간이다. 그러나 원리를 알고 이해하면 충분히 쉬운 개념이다.
그렇기 위해서는 거꾸로 생각할 필요가 있다.
포인터는 왜 필요한가??
지금까지 우리가 코딩을 한 방식을 생각해보자.
int number = 1; 라는 문장에서 number라는 이름의 4바이트 정수 타입의 바구니를 만든거이고, 이는 스택 메모리에 할당한다. 라는걸 의미한다.
따라서 스택 메모리에 있는 특정 주소(number 바구니)에 우리가 원하는 값을 넣은 셈이다.
number는 오직 메모리에 이름을 우리가 붙인 것이다.(컴파일러가 찰떡같이 알아들어서)
이러한 방식은 나쁘지 않고 편리하긴 하지만 함수 내부가 아닌 다른 함수에서 원본을 수정하기 어렵다는 것이다.
포인터를 선언하려면 다음과 같이 하면 된다.
#include <iostream>
using namespace std;
int main()
{
// type* 변수 이름;
// 일단 2가지 요소가 있다.
// - type -> int, float, char.....
// - *
// 포인터는 바구니는 바구니인데...
// [주소를 저장하는 바구니다!!!]
// 변수를 선언할 때 * 이 등장했다 -> 포인터 = 주소
int number = 1;
int* ptr = &number;
return 0;
}
이 코드를 디버깅 해봤을 때, number는 0073FD08 이라는 주소에 00000001 이란 값으로 저장이 되어있다.(이는 스택 메모리이다.) 그리고 그 위에 포인터 ptr이 0073FCFC라는 주소에 저장되어 있는데 그 값은 0073FD08로 어디서 많이 본 숫자이다. 바로 number의 주소를 의미하는 것이다.
포인터라는 바구니는 32비트 체계에서 실행할 땐 4바이트 64비트 체계에선 8바이트로 고정 크기가 할당된다.
그렇다면 남의 주소를 저장해서 뭘 할 수 있는데??? 라는 생각이 든다.
주소를 저장하는 바구니가 가리키는 주소로 가서 무엇인가를 해라! 라는 문법이 있다.
#include <iostream>
using namespace std;
int main()
{
// type* 변수 이름;
// 일단 2가지 요소가 있다.
// - type -> int, float, char.....
// - *
// 포인터는 바구니는 바구니인데...
// [주소를 저장하는 바구니다!!!]
// 변수를 선언할 때 * 이 등장했다 -> 포인터 = 주소
int number = 1;
int* ptr = &number;
// *이 여러번 등장하니 헷갈리는데, 사용 시점에 다라서 구분해서 기억하자
// - 변수 선언 (주소를 저장하는 바구니다!)
// - 사용할 때(포탈을 타고 그 위치로 순간이동)
// *변수이름 = 값;
int value1 = *ptr;
*ptr = 2;
return 0;
}
포인터 기초 2
그렇다면 type은 왜 붙여주는걸까?? *는 포인터의 의미이고 이는 주소를 저장하는 바구니이다. 그리고 4 또는 8바이트로 고정 크기를 갖게 된다.
그렇다면 type의 역할은 무엇을 할까??
바로 주소에 가면 뭐가 있는지 명시적으로 알려주는 것이다.
결혼식 청접장에 있는 주소는 그냥 주소가 아닌 예식장 주소인 것과 동일하다.
만약 타입이 불일치 하면 어떻게 될까?
타입의 크기가 안맞거나 이러면 메모리의 데이터가 덮여쓰이는 경우가 발생 할 수 있다.
#include <iostream>
using namespace std;
int main()
{
int number = 1;
__int64* ptr = (__int64*)&number;
*ptr = 0xAABBCCDDEEFF;
return 0;
}
이런식으로 00A0FA34에 00000001 이 저장되어 있었는데 4바이트가 밀려서 다른 데이터를 덮어쓴 모습이다. 실제 디버깅을 해서 확인해 보길 바란다. 되게 중요한 개념이다.
포인터 연산
#include <iostream>
using namespace std;
struct Player
{
int hp;
int damage;
};
// 1. 주소 연산자 (&)
// 2. 산술 연산자
// 3. 간접 연산자
// 4. 간접 멤버 연산자
int main()
{
int number = 1;
// 1) 주소 연산자(&)
// - 해당 변수의 주소를 알려주세요.
// - 더 정확히 말하면 해당 변수 타입에 따라서 type*를 반환
int* ptr = &number;
// 2) 산술 연산자 (+ -)
number += 1; // 1 증가했다 (!)
ptr += 1; // 4 증가했다 (?)
// => 포인터에서 +나 -등 산술 연산으로 1을 더하거나 빼면,
// 정말 그 숫자를 더하고 빼라는 의미가 아니다.
// 한번에 type의 크기만큼을 이동하라!
// 다음/이전 바구니로 이동하고 싶다 << [바구니 단위]의 이동으로
// 즉 , 1을 더하면 = 바구니 1개를 이동시켜라
// 3을 더하면 바구니 3개를 이동시켜라.
// 3) 간섭 연산자 (*)
// - 포탈을 타고 해당 주소로 슝~ 이동
number = 3;
*ptr = 3;
// 4) 간점 멤버 연산자 (->)
Player player;
player.hp = 100;
player.damage = 10;
Player* playerptr = &player;
(*playerptr).hp = 200;
(*playerptr).damage = 20;
// *과 .을 하나로 합쳐진 문법
// * 간섭 연산자(포탈을 타고 해당 주소로 가자)
// . 구조체의 특정 멤버를 다룰 때 사용(어셈블리 언어로 까보면 .는 단순히 바이트 덧셈)
// -> *과.을 한 방에!
playerptr->hp = 200;
playerptr->damage = 20;
return 0;
}
실습
#include <iostream>
using namespace std;
struct StatInfo
{
int hp; // +0
int attack; // +4
int defence; // +8
};
void EnterLobby();
StatInfo CreatePlayer();
void CreateMonster(StatInfo* info);
int main()
{
EnterLobby();
return 0;
}
void EnterLobby()
{
cout << "로비에 입장했습니다" << endl;
StatInfo player;
player = CreatePlayer();
StatInfo Monster;
CreateMonster(&Monster);
}
StatInfo CreatePlayer()
{
cout << "플레이어 생성" << endl;
StatInfo temp;
temp.hp = 100;
temp.attack = 10;
temp.defence = 5;
return temp;
}
void CreateMonster(StatInfo* info)
{
cout << "몬스터 생성" << endl;
info->hp = 80;
info->attack = 8;
info->defence = 4;
}
포인터를 배우기 전(플레이어 생성), 포인터를 배운 후(몬스터 생성)를 잘 비교해보자 어셈블리 언어를 확인해보는것이 좋을 것이다.
같은 결과를 얻을 수 있지만 포인터를 사용하는 것이 훨신 효율이 좋을 것이다.
'C++ > 기초' 카테고리의 다른 글
[C++] 포인터 - 3 (배열) (1) | 2023.11.25 |
---|---|
[C++] 포인터 - 2 (참조) (1) | 2023.11.25 |
[C++] 함수 (1) | 2023.11.24 |
[C++] 코드의 흐름 제어 - 2 (가위-바위-보, 열거형) (1) | 2023.11.23 |
[C++] 코드의 흐름 제어 - 1 (분기문, 반복문) (1) | 2023.11.23 |