C++에서 배열은 단순히 여러 데이터를 저장하는 자료구조에 그치지 않고, 포인터처럼 동작하는 경우가 많다. 이런 동작을 가능하게 하는 핵심 개념이 바로 '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을 통해 배열의 원소를 순회하거나 접근할 수 있다.
배열 이름 자체는 배열 전체를 가리키는 것이 아니라 첫 번째 원소의 주소로 동작한다.
int arr[3] = {1, 2, 3};
cout << arr << endl; // 출력: 첫 번째 원소의 메모리 주소
cout << &arr[0] << endl; // 출력: 첫 번째 원소의 메모리 주소 (같음)
arr은 &arr[0]와 동일하게 해석된다.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는 원래 배열이 아니라 포인터로 동작한다. 배열의 크기 정보가 손실되기 때문에, 크기를 추가로 전달해야 한다.
배열과 포인터는 비슷하게 동작하지만, 메모리 구조와 타입 수준에서 차이가 있다.
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바이트)를 반환한다.Array to Pointer Decay는 다음과 같은 상황에서 발생한다:
배열을 함수 인자로 전달하면, 배열이 포인터로 변환된다.
void modifyArray(int* arr) {
arr[0] = 100;
}
int main() {
int arr[3] = {1, 2, 3};
modifyArray(arr); // 배열이 포인터로 변환되어 전달됨
cout << arr[0] << endl; // 출력: 100
return 0;
}
배열 이름은 포인터로 변환되며, 포인터 연산을 통해 배열 요소에 접근할 수 있다.
int arr[3] = {1, 2, 3};
int* ptr = arr;
cout << *(ptr + 1) << endl; // 출력: 2
배열 이름을 명시적으로 포인터에 할당하거나, 함수 매개변수로 전달할 때 Decay가 발생한다.
다음과 같은 경우에는 Decay가 발생하지 않는다:
& 연산자 사용배열의 이름에 & 연산자를 사용하면, 배열 전체를 가리키는 포인터가 된다.
int arr[3] = {1, 2, 3};
cout << &arr << endl; // 배열 전체의 주소
cout << &arr[0] << endl; // 첫 번째 원소의 주소
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;
}
많은 알고리즘 문제에서 배열을 함수로 전달해야 할 때, 배열 이름이 포인터로 변환되므로 이를 활용할 수 있다.
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;
}
new와 같은 동적 메모리 할당과 결합하면 배열처럼 포인터를 사용할 수 있다.
int arr[3] = {1, 2, 3};
int *c = &arr; // 컴파일 에러: 배열 전체를 가리키는 포인터 타입이 아님
C++에서 배열과 포인터는 긴밀하게 연결되어 있지만, 동일하지는 않다. Array to Pointer Decay는 배열이 포인터처럼 동작할 수 있게 해주는 중요한 메커니즘으로, 알고리즘 구현과 효율적인 메모리 관리에 필수적인 역할을 한다.