본문 바로가기
Programming

C 다차원 배열과 포인터

by 나무수피아는 지식의 가지를 뻗어가는 공간입니다. 2025. 8. 13.
반응형

다차원 배열과 포인터

C언어에서 2차원 배열은 배열 안에 배열이 들어있는 구조로, 행(row)과 열(column)로 구성된 표 형태의 데이터를 저장하고 처리하는 데 매우 유용합니다. 이는 마치 엑셀의 셀처럼 가로와 세로로 구분된 데이터 집합을 나타내며, 예를 들어 3행 4열의 2차원 배열은 3개의 행에 각각 4개의 열이 존재합니다.

2차원 배열은 주로 행렬 연산, 이미지 처리, 게임 보드 구현 등 다양한 분야에서 활용됩니다. 메모리상에서는 1차원 배열처럼 연속된 공간에 저장되지만, 프로그래밍 시에는 두 개의 인덱스로 접근합니다.

2차원 배열 선언과 초기화

2차원 배열을 선언할 때는 다음과 같이 행과 열의 크기를 지정합니다. 예를 들어 int arr [3][4]는 3개의 행과 4개의 열을 가진 정수형 배열을 의미합니다.

#include <stdio.h>

int main() {
    int arr[3][4] = {
        {1, 2, 3, 4},      // 1행
        {5, 6, 7, 8},      // 2행
        {9, 10, 11, 12}    // 3행
    };

    // 2차원 배열 출력
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 4; j++) {
            printf("%d ", arr[i][j]);
        }
        printf("\n");
    }
    return 0;
}

위 예시에서 각 행은 중괄호 `{}` 로 묶여 있으며, 배열의 각 요소를 초기화했습니다. 이중 반복문을 사용하여 행과 열 인덱스를 순회하며 요소를 출력했습니다.

메모리 구조와 배열 요소 접근

C에서 2차원 배열은 연속된 1차원 메모리 공간에 저장됩니다. 예를 들어 int arr [3][4]는 총 12개의 정수(int) 공간이 연속적으로 할당됩니다.
따라서 2차원 배열의 요소 arr [i][j]는 메모리 주소 상에서 *(arr + i*4 + j)와 동일하게 접근 가능합니다. 여기서 4는 각 행의 열 개수입니다.

이러한 구조를 이해하면 포인터를 이용한 2차원 배열 접근이 가능해지고, 메모리 최적화 및 고급 프로그래밍 기술에도 활용할 수 있습니다.

2차원 배열 활용 예시: 행렬 덧셈

다음은 두 개의 3x3 행렬을 더하는 간단한 예제입니다. 2차원 배열과 반복문을 활용해 행렬 덧셈을 구현합니다.

#include <stdio.h>

int main() {
    int A[3][3] = {
        {1, 2, 3},
        {4, 5, 6},
        {7, 8, 9}
    };
    int B[3][3] = {
        {9, 8, 7},
        {6, 5, 4},
        {3, 2, 1}
    };
    int C[3][3];  // 결과 행렬

    // 행렬 덧셈 수행
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            C[i][j] = A[i][j] + B[i][j];
        }
    }

    // 결과 출력
    printf("행렬 A + B = \n");
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            printf("%d ", C[i][j]);
        }
        printf("\n");
    }

    return 0;
}

이 예시를 통해 2차원 배열의 실용적인 활용법을 체험할 수 있습니다.

포인터 배열과 배열 포인터

C언어에서 포인터 배열배열 포인터는 혼동하기 쉬운 개념이지만, 각각 전혀 다른 의미와 용법을 가지고 있습니다. 두 개념 모두 포인터와 배열을 다루지만, 선언과 사용법에 따라 동작이 크게 달라집니다.

포인터 배열 (Array of Pointers)

포인터 배열은 배열의 각 요소가 포인터인 배열입니다. 즉, 배열 자체는 메모리 상에서 연속된 공간을 가지지만, 각 요소는 변수, 문자열, 다른 데이터가 저장된 주소를 가리키는 포인터입니다.
예를 들어 문자열을 저장하는 포인터 배열은 각각의 포인터가 다른 문자열을 가리키는 형태입니다.

포인터 배열 예시 코드

#include <stdio.h>

int main() {
    char *fruits[] = {"Apple", "Banana", "Cherry"};

    // 포인터 배열을 이용해 문자열 출력
    for (int i = 0; i < 3; i++) {
        printf("%s\n", fruits[i]);
    }

    return 0;
}

위 코드에서 fruits는 3개의 char* 포인터를 요소로 가진 배열입니다. 각 포인터는 각각의 문자열 리터럴의 시작 주소를 가리킵니다. 포인터 배열은 가변 길이 문자열 리스트를 처리할 때 유용합니다.

배열 포인터 (Pointer to Array)

배열 포인터는 배열 전체를 가리키는 포인터입니다. 즉, 배열의 첫 번째 요소 주소를 저장하는 포인터로서, 배열의 크기 정보도 함께 포함하여 처리하는 것이 특징입니다.
배열 포인터는 일반 포인터와 선언 문법이 다르며, 특정 크기의 배열 주소를 가리킨다는 의미에서 사용됩니다.

배열 포인터 예시 코드

#include <stdio.h>

int main() {
    int arr[3] = {10, 20, 30};
    int (*ptr)[3] = &arr;  // 배열 포인터 선언 및 초기화

    // 배열 포인터를 사용해 배열 요소 출력
    for (int i = 0; i < 3; i++) {
        printf("%d\n", (*ptr)[i]);
    }

    return 0;
}

위 코드에서 ptrint [3] 배열을 가리키는 포인터입니다. (*ptr)[i] 구문을 통해 배열의 i번째 요소에 접근할 수 있습니다.
배열 포인터는 주로 함수에 2차원 배열을 전달하거나, 복잡한 데이터 구조를 다룰 때 유용합니다.

포인터 배열과 배열 포인터의 차이점 정리

  • 포인터 배열은 포인터를 요소로 가진 배열입니다. 즉, 여러 개의 포인터가 모여 하나의 배열을 이룹니다.
  • 배열 포인터는 배열 전체를 가리키는 포인터로, 배열의 주소와 크기를 포함합니다.
  • 선언 방식: char *arr[]는 포인터 배열, int (*ptr)[3]는 배열 포인터입니다.
  • 용도: 포인터 배열은 여러 개의 개별 주소를 관리할 때, 배열 포인터는 다차원 배열 또는 고정 크기 배열을 포인터로 다룰 때 주로 사용됩니다.

2차원 배열과 포인터 배열, 배열 포인터의 관계

2차원 배열 int arr [3][4]는 메모리에 연속된 12개의 int 공간을 가지며, 각 행은 1차원 배열입니다. 이를 다룰 때 다음과 같은 포인터 개념이 활용됩니다:

  • 배열 포인터: int (*ptr)[4] 는 4개의 int를 가진 1차원 배열(한 행)을 가리키는 포인터입니다. 반복문에서 행 단위로 접근할 때 쓰입니다.
  • 포인터 배열: 각 포인터가 별도의 배열(또는 행)을 가리키는 배열입니다. 동적 할당이나 비정형 행렬 처리에 적합합니다.

예시: 2차원 배열을 배열 포인터로 다루기

#include <stdio.h>

int main() {
    int arr[3][4] = {
        {1, 2, 3, 4},
        {5, 6, 7, 8},
        {9, 10, 11, 12}
    };

    int (*ptr)[4] = arr;  // 배열 포인터 선언: 4개의 int를 가진 배열을 가리킴

    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 4; j++) {
            printf("%d ", ptr[i][j]);
        }
        printf("\n");
    }
    return 0;
}

여기서 ptrarr의 각 행을 가리키며, 포인터 산술 연산을 통해 2차원 배열 요소에 접근할 수 있습니다.

포인터 배열과 동적 2차원 배열

정적 배열과 달리, 동적 메모리 할당을 통해 크기가 가변적인 2차원 배열을 만들 때 포인터 배열을 활용할 수 있습니다.
예를 들어, 행마다 크기가 다른 문자열 배열을 저장하거나, 동적으로 생성되는 행렬 데이터를 처리할 때 유용합니다.

#include <stdio.h>
#include <stdlib.h>

int main() {
    int rows = 3;
    int cols = 4;

    // 포인터 배열로 2차원 배열 동적 할당
    int **arr = (int **)malloc(rows * sizeof(int *));
    for (int i = 0; i < rows; i++) {
        arr[i] = (int *)malloc(cols * sizeof(int));
    }

    // 초기화
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            arr[i][j] = i * cols + j + 1;
        }
    }

    // 출력
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            printf("%d ", arr[i][j]);
        }
        printf("\n");
    }

    // 메모리 해제
    for (int i = 0; i < rows; i++) {
        free(arr[i]);
    }
    free(arr);

    return 0;
}

이처럼 포인터 배열을 활용하면 런타임에 원하는 크기로 2차원 배열을 생성하고 해제할 수 있습니다. 다만 메모리 관리를 신중히 해야 합니다.

마무리 및 참고 사항

지금까지 2차원 배열, 포인터 배열, 배열 포인터의 개념과 활용법을 살펴보았습니다. 각각의 차이점과 특징을 명확히 이해하는 것은 C언어로 고급 데이터 구조를 설계하고 효율적인 메모리 관리를 하는 데 필수적입니다.

실습을 통해 2차원 배열을 포인터로 접근해 보고, 포인터 배열과 배열 포인터가 어떻게 다르게 동작하는지 직접 코드를 작성해 보시길 추천합니다. 이를 통해 포인터 산술, 메모리 구조, 동적 메모리 관리에 대한 감각을 키울 수 있습니다.

특히 동적 2차원 배열을 구현할 때는 메모리 누수 방지를 위해 mallocfree를 올바르게 사용해야 하며, 복잡한 포인터 사용 시에는 디버깅에 신경 써야 합니다.

반응형

'Programming' 카테고리의 다른 글

C 배열과 문자열  (36) 2025.08.15
C 함수  (63) 2025.08.14
C 고급 포인터  (75) 2025.08.12
C 동적 메모리 할당  (80) 2025.08.11
C 제어문  (67) 2025.08.10