[C++]배열과 포인터의 관계: 'Array to Pointer Decay

JUNYOUNG·2024년 11월 21일

CS 기초

목록 보기
1/6
post-thumbnail

C++에서 배열은 단순히 여러 데이터를 저장하는 자료구조에 그치지 않고, 포인터처럼 동작하는 경우가 많다. 이런 동작을 가능하게 하는 핵심 개념이 바로 'Array to Pointer Decay'이다. 이번 글에서는 이를 알고리즘 문제 해결에 어떻게 활용할 수 있는지 살펴보겠다.

1. Array to Pointer Decay란?


Array to Pointer Decay는 C++에서 배열 이름이 자동으로 해당 배열의 첫 번째 원소를 가리키는 포인터로 변환되는 과정을 말한다. 이 변환은 함수 호출이나 포인터 연산 등 다양한 상황에서 발생한다. 중요한 점은 배열과 포인터가 동일한 개념이 아니라, 특정 상황에서 배열이 포인터처럼 동작하도록 변환(decay)된다는 것이다.

기본적인 동작 예제

#include <iostream>using namespace std;

int main() {
    int arr[5] = {10, 20, 30, 40, 50};

    int* ptr = arr; // arr은 첫 번째 원소의 주소로 변환
    cout << "첫 번째 원소: " << *ptr << endl; // 출력: 10
    cout << "두 번째 원소: " << *(ptr + 1) << endl; // 출력: 20

    return 0;
}

위 코드에서 arr은 배열의 이름이지만, int* ptr = arr에서 첫 번째 원소의 주소로 변환되었다. 이후 포인터 ptr을 통해 배열의 원소를 순회하거나 접근할 수 있다.


2. Array to Pointer Decay의 주요 특징

1) 배열 이름은 첫 번째 원소의 주소로 변환

배열 이름 자체는 배열 전체를 가리키는 것이 아니라 첫 번째 원소의 주소로 동작한다.

int arr[3] = {1, 2, 3};
cout << arr << endl;    // 출력: 첫 번째 원소의 메모리 주소
cout << &arr[0] << endl; // 출력: 첫 번째 원소의 메모리 주소 (같음)
  • arr&arr[0]와 동일하게 해석된다.
  • 하지만 배열 전체를 가리키는 포인터와는 다르다. 이는 아래에서 설명할 예정이다.

2) 배열의 크기 정보 손실

Array to Pointer Decay가 발생하면, 배열의 크기 정보는 사라진다. 따라서 배열의 크기를 알고 싶다면 별도의 매개변수를 통해 전달해야 한다.

void printArray(int* arr, int size) {
    for (int i = 0; i < size; ++i) {
        cout << arr[i] << " ";
    }
    cout << endl;
}

int main() {
    int arr[5] = {10, 20, 30, 40, 50};
    printArray(arr, 5); // 배열 크기를 함께 전달
    return 0;
}

위 코드에서 printArray 함수의 매개변수 int* arr는 원래 배열이 아니라 포인터로 동작한다. 배열의 크기 정보가 손실되기 때문에, 크기를 추가로 전달해야 한다.


3) 배열은 포인터와 다르다

배열과 포인터는 비슷하게 동작하지만, 메모리 구조와 타입 수준에서 차이가 있다.

int arr[5] = {1, 2, 3, 4, 5};
int* ptr = arr;

cout << sizeof(arr) << endl; // 출력: 20 (int[5] 크기)
cout << sizeof(ptr) << endl; // 출력: 8 (포인터 크기)
  • sizeof(arr)는 배열 전체의 크기를 반환한다. (5개의 int의 크기)
  • sizeof(ptr)는 포인터의 크기(보통 8바이트)를 반환한다.
  • 즉, 배열과 포인터는 메모리 크기와 동작 수준에서 완전히 동일하지 않다.

3. Array to Pointer Decay가 발생하는 상황

Array to Pointer Decay는 다음과 같은 상황에서 발생한다:

1) 함수 호출

배열을 함수 인자로 전달하면, 배열이 포인터로 변환된다.

void modifyArray(int* arr) {
    arr[0] = 100;
}

int main() {
    int arr[3] = {1, 2, 3};
    modifyArray(arr); // 배열이 포인터로 변환되어 전달됨
    cout << arr[0] << endl; // 출력: 100
    return 0;
}

2) 포인터 연산

배열 이름은 포인터로 변환되며, 포인터 연산을 통해 배열 요소에 접근할 수 있다.

int arr[3] = {1, 2, 3};
int* ptr = arr;

cout << *(ptr + 1) << endl; // 출력: 2

3) 배열 이름을 직접 포인터로 사용

배열 이름을 명시적으로 포인터에 할당하거나, 함수 매개변수로 전달할 때 Decay가 발생한다.


4. Array to Pointer Decay가 발생하지 않는 경우

다음과 같은 경우에는 Decay가 발생하지 않는다:

1) & 연산자 사용

배열의 이름에 & 연산자를 사용하면, 배열 전체를 가리키는 포인터가 된다.

int arr[3] = {1, 2, 3};
cout << &arr << endl;  // 배열 전체의 주소
cout << &arr[0] << endl; // 첫 번째 원소의 주소

2) decltype 또는 template 사용

배열의 타입 정보를 유지하기 위해 decltype이나 템플릿을 사용할 경우 Decay가 발생하지 않는다.

template <typename T, size_t N>
void printArray(T (&arr)[N]) {
    for (size_t i = 0; i < N; ++i) {
        cout << arr[i] << " ";
    }
    cout << endl;
}

int main() {
    int arr[3] = {1, 2, 3};
    printArray(arr); // 배열 크기를 알 수 있음
    return 0;
}

5. Array to Pointer Decay의 실제 활용

1) 알고리즘 문제에서의 활용

많은 알고리즘 문제에서 배열을 함수로 전달해야 할 때, 배열 이름이 포인터로 변환되므로 이를 활용할 수 있다.

void sumArray(int* arr, int size, int& sum) {
    sum = 0;
    for (int i = 0; i < size; ++i) {
        sum += arr[i];
    }
}

int main() {
    int arr[5] = {1, 2, 3, 4, 5};
    int sum = 0;
    sumArray(arr, 5, sum);
    cout << "합계: " << sum << endl; // 출력: 15
    return 0;
}

2) 동적 메모리와의 조합

new와 같은 동적 메모리 할당과 결합하면 배열처럼 포인터를 사용할 수 있다.


6. 내가 겪은 실수와 문제점

문제: 배열 전체를 가리키는 포인터를 잘못 사용

int arr[3] = {1, 2, 3};
int *c = &arr; // 컴파일 에러: 배열 전체를 가리키는 포인터 타입이 아님

7. 마무리하며

C++에서 배열과 포인터는 긴밀하게 연결되어 있지만, 동일하지는 않다. Array to Pointer Decay는 배열이 포인터처럼 동작할 수 있게 해주는 중요한 메커니즘으로, 알고리즘 구현과 효율적인 메모리 관리에 필수적인 역할을 한다.

profile
Onward, Always Upward - 기록은 성장의 증거

0개의 댓글