C++에서의 배열과 포인터

C++ 책이나 튜토리얼 대부분은 배열과 포인터를 함께 다룹니다. 그 이유는 무엇이며, 둘 사이에는 어떤 연관성이 있을까요? C++에서 배열과 포인터는 밀접하게 연결되어 있으며, 주요 관계는 다음과 같습니다.

1. 배열 이름은 포인터다

대부분의 상황에서 배열 이름은 배열의 첫 번째 요소를 가리키는 포인터로 암시적으로 변환됩니다. 예를 들어, int arr[5];라는 배열이 있을 때, arr&arr[0]과 동일한 주소 값을 가지며, 그 타입은 int*입니다. 따라서 배열 이름을 포인터처럼 사용하여 주소 연산을 할 수 있습니다.

int numbers[5] = {10, 20, 30, 40, 50};
int* ptr = numbers; // ptr은 numbers[0]을 가리킴
for (int idx = 0; idx < 5; ++idx) {
    std::cout << *(ptr + idx) << " "; // 포인터로 배열 요소 접근
}

2. 포인터를 통한 배열 요소 접근

포인터 산술 연산을 통해 메모리 데이터를 배열 인덱스 접근 방식과 유사하게 다룰 수 있습니다. ptr + n 연산은 포인터 타입의 크기만큼 주소를 이동시킵니다. 컴파일러 내부에서 arr[n]*(arr + n)으로 처리됩니다.

포인터 연산 시 컴파일러는 데이터 타입 크기에 따라 오프셋을 자동 조정합니다. 예를 들어 int* 타입 포인터에 ptr + 2를 적용하면, int가 4바이트인 환경에서는 실제 주소가 2 * sizeof(int), 즉 8바이트 증가합니다.

int data[5] = {1, 2, 3, 4, 5};
int* p = data;
// 아래 두 표현은 동일한 결과를 냅니다.
std::cout << data[2] << " ";
std::cout << *(p + 2) << " ";

3. 동적 배열과 포인터

new 연산자로 동적 배열 메모리를 할당하면 할당된 메모리의 첫 번째 주소를 가리키는 포인터가 반환됩니다. (int* dynArr = new int[10];) 해제도 포인터를 통해 이루어지며, delete[] dynArr;를 사용합니다.

int* dynamicArray = new int[10];
for (int idx = 0; idx < 10; ++idx) {
    dynamicArray[idx] = idx * 2; // 일반 배열처럼 접근
}
delete[] dynamicArray;

4. 함수 인자 전달 시 배열의 포인터 변환

배열을 함수 인자로 전달하면 배열은 포인터로 변환(decay)됩니다. void func(int arr[10])void func(int* arr)은 동일하게 취급되며, 함수 내부에서는 배열의 길이를 알 수 없으므로 추가로 길이 정보를 전달해야 합니다.

void showElements(int* arr, int size) {
    for (int idx = 0; idx < size; ++idx) {
        std::cout << arr[idx] << " ";
    }
}
int main() {
    int values[5] = {10, 20, 30, 40, 50};
    showElements(values, 5);
    return 0;
}

5. 메모리 배치

배열은 메모리에 연속적으로 저장되며, 포인터는 이 연속된 메모리 블록을 순회하는 데 사용됩니다. 이 원리를 이해하면 대용량 데이터 처리 시 효율적인 코드를 작성하는 데 도움이 됩니다.

6. 포인터 배열과 배열 포인터

  • 포인터 배열: 각 요소가 포인터인 배열입니다. 예: int* ptrArray[5];는 5개의 int* 포인터를 저장하며, 여러 동적 배열이나 객체를 관리할 때 유용합니다.
  • 배열 포인터: 배열 자체를 가리키는 포인터입니다. 예: int (*arrPtr)[5];는 5개의 int 요소를 가진 배열을 가리키며, 2차원 배열(배열의 배열) 처리 시 유용합니다.
// 포인터 배열 예시
int* ptrArray[5];
for (int idx = 0; idx < 5; ++idx) {
    ptrArray[idx] = new int(idx);
}

// 배열 포인터 예시
int matrix[5] = {1, 2, 3, 4, 5};
int (*matrixPtr)[5] = &matrix;

왜 함께 배워야 할까?

  • 개념적 일관성: 배열과 포인터는 개념적으로 밀접하게 연결되어 있어 함께 학습하면 내재된 관계를 이해하고 혼동을 줄일 수 있습니다.
  • 실용성: 문자열 처리, 동적 메모리 관리 등 실제 프로그래밍에서 배열과 포인터는 자주 결합되어 사용됩니다.
  • 효율성 향상: 둘의 관계를 이해하면 저수준 프로그래밍이나 성능이 중요한 애플리케이션에서 더 유연하고 효율적인 코드를 작성할 수 있습니다.

배열과 포인터를 함께 학습함으로써 C++의 메모리 관리와 데이터 조작 메커니즘을 더 잘 이해하고, 복잡한 프로그래밍 작업을 위한 탄탄한 기초를 다질 수 있습니다.

태그: C++ 배열 포인터 메모리 관리 동적 할당

6월 29일 18:28에 게시됨