전체 코드

1. 포인터 개념 이해

1.1 포인터란?

포인터(pointer)란 메모리 주소를 저장하는 변수입니다. 일반 변수는 데이터 자체를 저장하지만, 포인터는 데이터를 저장하는 메모리의 "위치"를 저장하는 것입니다.

int number = 10;   // 일반 변수 선언 및 초기화
int* ptr = &number; // number의 주소를 ptr에 저장

위 코드에서 ptrnumber의 메모리 주소를 가리킵니다. 즉, ptr을 이용하면 number가 저장된 메모리 공간에 접근할 수 있습니다.

1.2 포인터의 역할

포인터는 일종의 "포탈"처럼 작동하여 특정 메모리 위치로 이동하여 값을 읽거나 수정할 수 있습니다.
이를 그림으로 표현하면 다음과 같습니다.

메모리 주소변수명
0x100010number
0x20000x1000ptr
  • number는 메모리 0x1000에 저장된 변수이며, 값은 10입니다.
  • ptr은 0x1000이라는 주소를 가리킵니다. 즉, ptr을 통해 number 변수의 값에 접근할 수 있습니다.

2. 포인터를 이용한 변수 값 변경

포인터를 활용하면 변수를 직접 수정하지 않고도 값을 변경할 수 있습니다.

#include <iostream>
using namespace std;

void SetHp(int* hp) 
{
    *hp = 100; // 포인터가 가리키는 주소의 값을 100으로 변경
}

int main()
{
    int hp = 1;   // 'hp'라는 정수형 변수 선언 및 초기화
    SetHp(&hp);   // 'hp'의 주소를 전달하여 함수 내에서 값을 수정

    cout << "현재 HP: " << hp << endl; // 출력: 현재 HP: 100
    return 0;
}

분석:

  1. SetHp(int* hp) 함수는 매개변수로 포인터를 전달받아 *hp = 100;을 실행하여 해당 주소의 값을 100으로 변경합니다.
  2. main()에서 hp 변수의 주소 &hpSetHp() 함수에 전달하면 hp 값이 함수 내에서 직접 변경됩니다.

3. 포인터의 기본 연산

3.1 포인터 선언과 주소 할당

int number = 1;
int* ptr = &number; // number의 주소를 ptr에 저장
  • ptrnumber의 주소를 가리키며, 이를 통해 number 값을 읽거나 수정할 수 있습니다.

3.2 값 가져오기 (역참조)

int value1 = *ptr; // ptr이 가리키는 주소의 값을 가져와 value1에 저장
  • *ptr을 사용하면 ptr이 가리키는 변수의 값을 읽을 수 있습니다.

3.3 값 변경

*ptr = 2; // ptr이 가리키는 주소의 값을 2로 변경 (즉, number의 값이 2로 변경됨)
  • ptr을 통해 number 값이 1 → 2로 변경됩니다.

4. 포인터의 크기와 타입의 역할

4.1 포인터의 크기

포인터의 크기는 운영체제와 컴파일러 아키텍처에 따라 달라집니다.

  • 32비트 시스템에서는 포인터 크기가 4바이트
  • 64비트 시스템에서는 포인터 크기가 8바이트

즉, 포인터 자체는 데이터 타입과 무관하게 고정된 크기를 가집니다.

4.2 포인터 타입의 의미

int* ptr;
double* dptr;
char* cptr;
  • 포인터의 타입(int*, double*, char*)은 해당 포인터가 가리키는 데이터의 타입을 의미합니다.
  • 타입을 통해 포인터 연산(예: ptr + 1)이 수행될 때 몇 바이트씩 이동해야 하는지 결정됩니다.

예제:

int number = 1;
__int64* ptr2 = (__int64*)&number; // number 주소를 강제로 8바이트 포인터로 변환
*ptr2 = 0xAABBCCDDEEFF;
  • 원래 number4바이트(int)이지만, 8바이트(__int64*) 포인터를 사용하여 값이 덮어씌워집니다.
  • 이로 인해 메모리 오버플로우가 발생할 수 있으며, 예상치 못한 동작을 초래할 수 있습니다.

5. 포인터와 어셈블리 분석

어셈블리 코드를 통해 포인터의 동작을 확인할 수 있습니다.

예제 코드

#include <iostream>
using namespace std;

int main()
{
    int number = 1;
    int* ptr = &number;
    int value1 = *ptr;
    *ptr = 2;

    return 0;
}

어셈블리 코드 분석:

lea    eax, [number]           ; number의 주소를 eax 레지스터에 저장
mov    dword ptr [ptr], eax    ; eax 값을 ptr에 저장
mov    eax, dword ptr [ptr]    ; ptr이 가리키는 값을 eax에 저장
mov    dword ptr [value1], eax ; eax 값을 value1에 저장
mov    eax, dword ptr [ptr]    ; ptr이 가리키는 값을 eax에 저장
mov    dword ptr [eax], 2      ; eax가 가리키는 주소의 값을 2로 변경
  • 포인터를 이용해 변수를 수정할 때, 두 번 이동 (포인터 → 포인터의 주소)이 발생함을 확인할 수 있습니다.

6. 포인터의 위험성과 주의할 점

  1. 잘못된 메모리 접근 (Segmentation Fault)

    • 초기화되지 않은 포인터를 사용하면 런타임 오류가 발생할 수 있습니다.
    int* ptr;
    *ptr = 100; // 오류 발생 (ptr이 유효한 주소를 가리키지 않음)
  2. Dangling Pointer (삭제된 메모리 접근)

    int* ptr = new int(10);
    delete ptr;
    *ptr = 20; // 오류 발생 (할당 해제된 메모리에 접근)
  3. 메모리 누수 (Memory Leak)

    • new로 동적 할당한 메모리를 delete로 해제하지 않으면 메모리 누수가 발생합니다.
    int* ptr = new int(10);
    // delete ptr; // 해제를 잊으면 누수가 발생

profile
李家네_공부방

0개의 댓글