[C++] C++에서 사용되는 개념 12탄(포인터 기능)

Patrick!·2023년 2월 26일
0
post-thumbnail

1. 포인터의 다양한 연산에 대하여

포인터를 사용하는데 있어 기본적으로 사용되는 연산자는 다양한 종류가 있지만
그 중에서 대표적으로 다음의 연산자를 알아보도록 하자.

a. 주소 연산자(&)

해당 변수의 주소를 가리킨다.
이를 더 정확히 말하면 해당 변수 타입(type)에 따라서 type* 반환하는 개념이라고 볼 수 있다.

int* pointer = &number;

b. 산술 연산자 (+ -)

일반적인 산술 연산자와 사용법은 같다. 하지만 이를 포인터에 적용한다면 이야기는 달라진다.
포인터에 산술연산자를 사용하게 되면, 일반적인 연산이 아니라 포인터에 담긴 주소의 위치를 변경하는데 사용된다.

int number = 1;
int* pointer = &number; -> &number [ 0x02ff98d0 ]
*pointer += 1; -> &number [ 0x02ff98d4 ]

*pointer += 1; -> &number [ 0x02ff98d8 ]

주소값의 '값'을 변경하는게 아닌 int(4byte 정수형 바구니)의 위치를 이동하는 개념이라는 것이다.
즉, *number의 type만큼의 메모리 이동이 이루어지는 것이다.

c. 간접 연산자( * )

주소값에 접근하여 해당 값을 조작하는 연산자이다.
하지만 선언받은 값이 주소값을 가지고 있어야 하며 이를 선언한 변수가 포인터형으로 존재해야 한다.

#include <iostream>
using namespace std;

struct Player {
    int hp;
    int damege;
};

int main() {

    Player player; => 구조체로 선언해둔 Player가 일반적으로 int main() 에서 사용될 때의 모습이다.
    player.hp = 100;
    player.damage = 10;

위의 코드는 구조체로 선언해둔 Player를 다루는 모습을 보여주고 있다.

이후 player에 접근할 포인터(playerPtr)를 선언하고
playerPtr에서는 .를 통해 playerdml 내부 값에 접근할 수 있다.

    Player* playerPtr = &player;
    (*playerPtr).hp = 200;
    (*playerPtr).damage = 20;
}

d. 간접 맴버 연산자 (->)

(*) 와 . 의 개념을 동시에 실행할 수 있는 개념
구조체의 특정 맴버를 다룰 때 사용(어셈블리 언어로 까보면 . 사실상 그냥 덧셈의 개념)

   playerPtr->hp = 200;
   playerPtr->damege = 200;

2. 포인터가 활용될 때의 차이점

포인터를 사용하는데 있어 일반적인 변수들과 차이가 존재할까?

다음 코드를 보면서 살펴보도록 하자.

#include <iostream>
using namespace std;

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

void EnterLobby();
StatInfo CreatePlayer();
void CreateMonster(StatInfo* info);

bool StartBattle(StatInfo* player, StatInfo* monster);


int main() {
    EnterLobby();
}

void EnterLobby() {
    cout << "로비에 입장했습니다." << endl;

    StatInfo player;
    player.hp = 0xbbbbbbbb;
    player.atteck = 0xbbbbbbbb;
    player.defence = 0xbbbbbbbb;

    player = CreatePlayer();

    StatInfo monster;
    monster.hp = 0xbbbbbbbb;
    monster.atteck = 0xbbbbbbbb;
    monster.defence = 0xbbbbbbbb;
    CreateMonster(&monster);

    bool victory = StartBattle(&player, &monster);

    if (victory) cout << "victory!" << endl;
    else cout << "lose!" << endl;

}

StatInfo CreatePlayer() {
    StatInfo ret;

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

    ret.hp = 100;
    ret.atteck = 10;
    ret.defence = 2;

    return ret;
}

void CreateMonster(StatInfo* info) {

    cout << "몬스터 생성" << endl;
    info->hp = 40;
    info->atteck = 8;
    info->defence = 1;
}

bool StartBattle(StatInfo* player, StatInfo* monster) {
    while (true) {
        int damage = player->atteck - 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 true;

        damage = monster->atteck - 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 false;
    }
}

위의 코드에서 유심히 봐야하는 것은 기존에 생성했던 방법과는 달리 포인터를 사용해서 생성하는 부분이 추가되었고 이를 사용하고 있다는 것이다.

포인터가 무언가를 생성하는 부분에서는 성능적으로 좋다 ?!

StatInfo CreatePlayer() { // 일반적인 생성
    StatInfo ret;

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

    ret.hp = 100;
    ret.atteck = 10;
    ret.defence = 2;

    return ret;
}

void CreateMonster(StatInfo* info) { // 포인터를 활용한 생성

    cout << "몬스터 생성" << endl;
    info->hp = 40;
    info->atteck = 8;
    info->defence = 1;
}

이 둘의 차이는 무엇일까? 어떤 차이가 있기에 이렇게 나누어서 사용을 한 것일까?

우선적으로 CreatePlayer() 함수를 살펴보면
[매개변수(&temp)][RET][지역변수(ret(100, 10, 2))]
CreatePlayer() 함수 안의 지역변수로 생성된 ret에 담긴 return하는 원리고 작동된다.

위의 방법은 어셈블리 언어로 살펴볼 때 상당히 많은 복사과정을 거쳐서 진행된다.
이는 현제 player에 많은 정보가 없기에 문제가 되지 않을 수 있다. 하지만 MMORPG의 경우,
player의 정보는 방대하기 때문에 이 방법으로 진행하게 된다면 부하를 일으킬 가능성이 농후하다.

다음으로 CreateMonster(&monster) 함수를 살펴보면
[매개변수][RET][지역변수(monster(40, 8, 1))]
인자값으로 StatInfo타입의 포인터를 받고 있다. 포인터를 받았다면 이는 간접 맴버 연산자(->) 접근할 수 있다.

    info->hp = 40;
    info->atteck = 8;
    info->defence = 1;

어셈블리 코드가 CreatePlayer();에 비해 적다는 것과 동시에 지역변수를 사용하지 않음으로써 메모리적인 부분에서도 효율적이며 무엇보다 메모리 주소값에 직접넣기에 유용하다.(하지만 잘못될 경우 이만큼 위험한 것도 없다는 점이다.)

profile
C++와 Unreal Engine / C#과 Unity / Katalon Studio를 통한 자동화 테스트 등을 하루하루 공부한 기록

0개의 댓글