포인터에 대하여 중요하기에 따로 글쓰기

 

포인터는 왜 사용할까?

C++에서 포인터는 메모리 주소를 저장하고 관리하는 강력한 도구입니다. 포인터를 사용하면 데이터를 직접 읽고 수정할 수 있으며, 배열과 함수에 유연하게 접근할 수 있습니다. 하지만 포인터를 잘못 사용하면 프로그램이 비정상적으로 종료되거나 예기치 않은 동작이 발생할 수 있으므로 주의해야 합니다.

 

포인터를 선언할때에는 앞에 *를 붙인다 ex) int타입의 포인터를 선언할때, int *ptr 

 

int a = 100;
int* ptr = &a;

cout << a << endl;     // 100
cout << ptr << endl;    // a의 주소값 출력
cout << *ptr << endl;   // 100 (ptr이 가리키는 값)

 

쉽게 이해하기

 

102동 1201호에는 a가 삽니다.

101동 101호에는 a의 주소가 있습니다.

이때 101동 101호에 찾아가면 a는 없지만 a의 주소를 알아내서 a를 찾아갈 수 있습니다.

 

포인터의 연산

 

포인터는 주소를 가르키므로 주소를 이동하거나 값을 읽고 변경하는것이 가능하다.

ptr + 1은 단순히 1을 더하는것이 아니라 포인터가 가르키는 데이터타입의 크기만큼 주소를 이동한다는 의미이다.

ex) int 타입은 보통 4바이트 이므로 ptr은 현재주소에서 4바이트 이동

 

// 예: 포인터가 0x1000을 가리킬 때
| 0x1000 | 0x1001 | 0x1002 | 0x1003 | 0x1004 |
ptr + 1은 0x1004를 가리키게 됩니다.

 

그렇다면 포인터변수를 배열에 할당하면 어떻게 될까?

 

배열 중 가장 첫번째요소를 가르키는 포인터가 됩니다.

 ex)

int arr[5] = {10, 20, 30, 40, 50};
int* ptr = arr;  // arr은 첫 번째 요소의 주소를 가리킴

 

이때 *ptr은 10을 나타내게 될 것입니다.

 

이때 배열의 다음 주소들은 ptr +1입니다.

ex)

cout << *ptr << endl;        // 10 (arr[0])
cout << *(ptr + 1) << endl;  // 20 (arr[1])
cout << *(ptr + 2) << endl;  // 30 (arr[2])

 

주의해야될 점

 

포인터의 사용은 오류를 일으킬수 있습니다.

예를들어 포인터가 null 즉, 빈 메모리를 가르킨다면 런타임오류( Segmentation Fault )가 발생합니다.

 

int* ptr = nullptr;
*ptr = 10;  // 잘못된 참조, 프로그램 비정상 종료

 

그러므로 널포인터를 사용해야 할때에는 반드시 조건문을 사용해 널여부를 확인합니다.

 

if (ptr != nullptr) {
    *ptr = 10;
}

 

Dangling Pointer 사용이란?

 

Dangling Pointer는 이미 해제된(삭제된) 메모리를 참조하는 포인터입니다. 오류가 일어나지 않을 수도 있지만 예기치 않는 동작이 발생합니다.

 

int* ptr = new int(10);
delete ptr;  // 메모리 해제
*ptr = 20;   // Dangling Pointer로 잘못된 접근

 

그렇다면 어떻게 해야할까?

해제한 이후에는 반드시 포인터를 다시 널로 초기화해야합니다.

ptr = nullptr;

 

초기화 되지않는다면 어떻게 될까?

 

초기화를 하지않는다면 쓰레기 값을 배출하여 의도치않는 동작을 유발합니다.

int* ptr;  // 초기화되지 않음
*ptr = 10;  // 잘못된 접근

 

그렇다면 배열을 가르키는 포인터변수가 배열의 범위를 초과하면 어떻게 될까?

 

배열의 범위를 초과하면 오류를 일어나므로 포인터를 이용하여 배열에 접근할때에는 범위를 벗어나지 않도록 해야합니다.

ex)

int arr[5] = {1, 2, 3, 4, 5};
int* ptr = arr;
for (int i = 0; i <= 5; i++) {
    cout << *(ptr + i) << endl;  // i = 5는 잘못된 접근
}

 

그렇다면 함수도 포인터에 넣을수 있을까?

함수의 메모리 주소를 저장하는 특수한 변수를 함수포인터라 합니다.이를 통해 함수를 변수처럼 다룰 수 있으며,

함수를 매개변수로 전달하거나 반환값으로 사용할 수 있습니다.

함수 포인터를 사용하면 특정 상황에 맞는 함수를 동적으로 선택하여 호출하거나, 함수를 데이터 구조에 저장하여 필요할 때 호출할 수 있습니다.

 

기본구조?

리턴 타입 (*포인터 이름)(매개변수 타입들);

 

ex)

#include <iostream>

// 함수 선언
int add(int a, int b) {
    return a + b;
}

int main() {
    // 함수 포인터 선언 및 초기화
    int (*funcPtr)(int, int) = add;

    // 함수 호출
    int result = funcPtr(10, 20);  // add(10, 20)와 동일
    std::cout << "Result: " << result << std::endl;

    return 0;
}

 

위 예시와 같이 함수포인터의 선언및 초기화(주소할당)을 할때에는 &는 생략가능합니다.

funcPtr = &add;  // 함수 주소를 포인터에 할당
funcPtr = add;   // & 생략 가능 (함수 이름은 함수의 주소를 가리킴)

 

처음에 말했듯 포인터를 사용하여 함수를 변수처럼 사용할 수 있으므로 함수의 매개변수로 전달하는 것도 가능합니다.

void executeOperation(int a, int b, int (*operation)(int, int)) {
    std::cout << "Result: " << operation(a, b) << std::endl;
}

 

ex)

int add(int a, int b) {
    return a + b;
}

executeOperation(10, 20, add);  // Result: 30 출력