프로그래밍 언어/C++

[게임 프로그래머 입문 올인원] 포인터와 배열 : 포인터 (20, 21강)

순정법사 2023.09.06

B. 포인터

1. 포인터 기초

a. 포인터의 정의

🌟 주소값는 담는 바구니

 

 

타입 : 주소값을 타고 가면 무엇이 있는지 명시함

 

원본 데이터를 수정할 수 있다

 

b. 포인터의 크기

프로그램 실행 환경과 관련이 있음

64bit에선 무조건 8byte

 

c. 잘못 작성된 타입의 값은?

아래 이미지와 같이 int 타입의 데이터 주소를 float 형의 포인터로 받게 된다면 오류가 난다

 

결과값으로 이상한 데이터가 출력

 

데이터 처리 시 int 타입 데이터 + 주소 + 100 (2진수로 표현)으로 설정되어있던 hp 변수의 데이터가

float 타입으로 변환되면서 float 타입 데이터로 치환되며 2진수로 표현되어있던 100의 값에 손상이 가게 된다

 

위의 예제는 int와 float 타입의 데이터방 크기가 같아 큰 문제는 일어나지 않지만

 

 

만일 이와같이 데이터 범위가 더 큰 곳에서 잘못된 타입의 치환이 이루어 진다면... 😫

 

d. 포인터의 연산

포인터의 연산은 일반 연산과 다름

ptr += n의 의미는

포인터가 저장되어 있는 주소값 + 주어진 값 n번째의 위치로 이동(데이터 타입 * n) 하겠다는 의미가 됨

 

int 타입의 크기(4byte)만큼 이동되어 104가 된 ptr

 

❔ 예제

#include <iostream>
using namespace std;

int main()
{
    int hp = 100;
    
    int* ptr = (int*)hp; //hp 변수를 포인터로 강제 형변환한 값을 포인터에 대입
    ptr += 1;

    cout << "변수 hp의 주소 : " << & hp << endl;
    cout << "포인터로 강제 형변환한 hp의 값: " << (int*)hp << endl;
    cout << "포인터 ptr의 주소(int형식)" << ptr << endl;
    cout << "포인터 ptr의 int 값" << (int)ptr << endl;

}

✨ 실행결과

변수 hp의 주소 : 000000CDC04FF854
포인터로 강제 형변환한 hp의 값: 0000000000000064
포인터 ptr의 주소(int형식)0000000000000068
포인터 ptr의 int 값104

 

💥 포인터에 정수를 넣은 경우

위 코드에서는 ptr 포인터에 주소값을 넣지 않고 정수를 강제로 넣었는데
이런 경우 prt의 값을 출력하려고 하면, 주소값이 잘못되어있고 값을 찾을 수 없어서
코드가 빵 터져버리니 조심해야 함 

 

e. 포인터의 포인터

말 그대로 포인터의 포인터라는 이야기

 

 

이중 포인터도 잘 사용하지 않음, 포인터를 무한대로 사용할 수 있음

 

 

2. 포인터 연산자

a. 포인터의 기초적인 모습

변수의 주소값을 입력받아 그 값 자체를 수정한다

 

int Swap(int* a, int* b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

int main()
{
    int a = 10;
    int b = 20;
    Swap(&a, &b);

    cout << a << endl;
    cout << b << endl;

}

 

포인터 연산에는 주소, 산술, 간접, 간접 멤버 연산자가 있음

 

b. 주소 연산자(&)

🌟 변수의 주소를 가지고 오는 연산자

 

c. 산술 연산자 (+-)

🌟 포인터 변수를 사용하여 메모리 위치를 조작하는 데 사용하는 연산자

 

위에서 데이터의 연산 할때도 증명했지만,

산술 연산을 할 때에는 타입의 데이터 크기 만큼 넘어감

 

4바이트 만큼 넘어간다는걸 표현하신 강사님

 

💙 사용되는 곳 

 

주로 배열과 같이 사용되는데

배열의 첫번째 주소에 산술 연산으로 배열 내부의 데이터에 접근할 수 있음!

 

 

또한 배열 이름은 배열의 주소값과 동일하다

따라서 위 코드는 int* ptr = numbers;로도 작성 가능함

 

d. 포인터(간접) 연산자 (*)

🌟 포인터 변수 주소 값에 있는 데이터를 출력하는 연산자

 

e. 간접 멤버 연산자 (->)

🌟 포인터를 사용하여 객체나 구조체의 멤버에 접근하는 데 사용

 

포인터 변수와 멤버 변수 또는 멤버 함수를 연결하는 역할

 

📘 문법

포인터->멤버
(*포인터).멤버  // 위와 정확하게 동일한 행동

 

❔ 예제

#include <iostream>
using namespace std;

struct StatInfo {
    int hp;
    int attack;
    int defence;
};

int main()
{
    StatInfo monster;
    monster.hp = 100;
    monster.attack = 10;
    monster.defence = 1;

    StatInfo* ptr = &monster;

    cout << (*ptr).hp << endl;
    cout << ptr->hp << endl;
}

 

 

3. 포인터 연산 예제

a. 플레이어 생성

🐲 예제 : struct를 사용해 복사를 하는 형태로 생성

struct StatInfo {
    int hp;
    int attack;
    int defence;
};

//스텍에서 데이터 복사가 이루어짐
//아쉬운 점은 데이터가 많아지면 StatInfo를 자주 호출해야 해 성능 저하가 있다
StatInfo CreatePlayer() {
    StatInfo info;

    cout << "플레이어 생성" << endl;

    info.hp = 100;
    info.attack = 10;
    info.defence = 2;

    return info;
}

int main()
{
    StatInfo player;
    player = CreatePlayer();    

}

 

b. 몬스터 생성

🐲 예제 : struct를 사용해 복사를 하는 형태로 생성

//먼저 메모리를 할당한 다음 채우는 형태에 가까움
//원본 데이터가 4000바이트라도 포인터라서 8바이트밖에 해당이 안됨 
void CreateMonster(StatInfo* info) {

    cout << "몬스터 생성" << endl;

    info->hp = 40;
    info->attack = 8;
    info->defence = 1;
}

...

int main()
{
	...

    StatInfo monster;
    CreateMonster(&monster);

}

 

c. 플레이어 vs 몬스터

🐲 예제 : 간접 멤버 연산자를 사용해 객체에 접근

...

void Battle(StatInfo* player, StatInfo* monster) {
    while (true) {
        //공격
        int damage = player->attack - monster->defence;
        if (damage < 0)
            damage = 0;

        monster->hp -= damage;
        if (monster->hp < 0)
            monster->hp = 0;

        cout << "몬스터 HP : " << monster->hp << endl;

        if (monster->hp == 0) {
            return;
        }

        //반격
        damage = monster->attack - player->defence;
        if (damage < 0)
            damage = 0;

        cout << "플레이어 HP : " << player->hp << endl;

        player->hp -= damage;
        if (player->hp < 0)
            player->hp = 0;

        if (player->hp == 0) {
            return;
        }
    }
}


int main()
{
    ...

    Battle(&player, &monster);
}

 

d. 최종 코드

CodingTest.cpp
0.00MB

 

e. 데이터 위치의 중요성

위 코드에서 예를 들어보자

 

1. 플레이어를 생성하는 객체를 포인터 변수로 선언하고

2. 데이터 자체(info)를 함수 내부에서 생성하면,

3. 당연하게도 함수 호출이 끝나면 스택 메모리가 해제되니

4. 없어진 info 값을 참조하게 된다

 

❔ 잘못된 예제

StatInfo* CreatePlayer2() {
    StatInfo info;

    cout << "플레이어 생성" << endl;

    info.hp = 100;
    info.attack = 10;
    info.defence = 2;

    return &info; //player은 없어진 값의 참조를 하게 됨
}

...

int main(){
    StatInfo* player;
    player = CreatePlayer2();	//즉 player = &info
	cout << player << endl; 
    //00000020E7BFF7A8
    //포인터 변수로 선언한 것 답게 주소값(&info와 같은 값)이 나오긴 함
}

 

 내부를 살펴보면 (player2를 살펴보면 됨)

 

함수가 끝나자마자 이상한 값으로 변경됨

 

이렇게 설정하지 않은 값들로 변경되어있다

 

그 이유는 3, 4번에서 설명했듯,

StatInfo info값이 이미 사라진 뒤 그 주소값을 player가 참조하게되어

이상한 값들로 변경되어 들어가게 되는것!!

 

💥 따라서 무슨 값을 참조하는건지 항상 경계해야 함

 

 

 


출처 : https://www.inflearn.com/course/%EA%B2%8C%EC%9E%84-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8-%EC%9E%85%EB%AC%B8-%EC%98%AC%EC%9D%B8%EC%9B%90-rookiss#curriculum

 

[게임 프로그래머 입문 올인원] C++ & 자료구조/알고리즘 & STL & 게임 수학 & Windows API & 게임 서버 -

어디부터 시작할지 막막한 게임 프로그래밍 입문자를 위한 All-In-One 커리큘럼입니다. C++, 자료구조/알고리즘, STL, 게임 수학, Windows API, 게임 서버 입문으로 이어지는 알찬 커리큘럼으로 게임 프

www.inflearn.com