1. 문제 개요
C 언어에서 다차원 배열을 동적으로 할당할 때, 많은 프로그래머가 포인터 배열(pointer-to-pointer
)을 사용하는 방식을 배웁니다. 그러나 이는 실제로 비효율적이며 잘못된 접근입니다. 본 글에서는 포인터 배열 방식의 문제점과 진정한 다차원 배열을 동적으로 할당하는 방법을 소개합니다.
2. 포인터 배열 방식의 문제점
2.1 메모리 비연속성
포인터 배열 방식은 각 차원의 메모리를 별도로 할당하므로 메모리가 연속적이지 않습니다. 이는 CPU 캐시 효율성을 떨어뜨리고, 힙(fragmentation)을 심화시켜 메모리 관리가 복잡해집니다.
int** arr = malloc(sizeof(*arr) * x);
for (size_t i = 0; i < x; i++) {
arr[i] = malloc(sizeof(**arr) * y);
}
2.2 성능 저하
- 메모리가 분산되어 있어 반복 작업 시 CPU 캐시를 효과적으로 사용할 수 없습니다.
malloc
과free
를 여러 번 호출해야 하므로 성능이 저하됩니다.
2.3 제한된 표준 라이브러리 함수 활용
포인터 배열 방식은 연속 메모리를 요구하는 memcpy
, qsort
등의 함수에서 사용할 수 없습니다.
3. 진정한 다차원 배열 할당 방법
3.1 연속 메모리 할당
다차원 배열을 연속된 메모리로 한 번에 할당하면 효율성과 코드 가독성이 크게 향상됩니다.
int (*arr)[y] = malloc(sizeof(int[x][y]));
3.2 구현 예제
아래는 연속 메모리를 사용하는 다차원 배열의 동적 할당, 초기화, 출력 예제입니다:
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
// 다차원 배열 동적 할당
void arr_alloc(size_t x, size_t y, int (**arr)[x][y]) {
*arr = malloc(sizeof(int[x][y]));
assert(*arr != NULL);
}
// 배열 초기화
void arr_fill(size_t x, size_t y, int arr[x][y]) {
for (size_t i = 0; i < x; i++) {
for (size_t j = 0; j < y; j++) {
arr[i][j] = (int)j + 1;
}
}
}
// 배열 출력
void arr_print(size_t x, size_t y, int arr[x][y]) {
for (size_t i = 0; i < x; i++) {
for (size_t j = 0; j < y; j++) {
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
// 메인 함수
int main(void) {
size_t x = 2, y = 3;
int (*arr)[x][y];
arr_alloc(x, y, &arr);
arr_fill(x, y, *arr);
arr_print(x, y, *arr);
free(arr); // 메모리 해제
return 0;
}
4. 왜 이 방법이 더 나은가?
4.1 성능
- 메모리가 연속적으로 할당되므로 CPU 캐시 효율성이 높아집니다.
malloc
호출이 한 번만 이루어져 성능 저하를 방지합니다.
4.2 라이브러리 함수 호환성
연속 메모리 덕분에 memcpy
, qsort
등 표준 라이브러리 함수를 사용할 수 있습니다.
4.3 코드 간결성
포인터 배열 방식의 복잡한 메모리 할당 코드가 간단하고 가독성이 높아집니다.
5. 결론
포인터 배열 방식은 다차원 배열 할당을 제대로 처리하지 못하며, 성능과 유지보수 측면에서 비효율적입니다. 대신 연속 메모리를 사용하여 진정한 다차원 배열을 할당하면 성능과 코드 품질을 모두 향상시킬 수 있습니다. 이러한 접근 방식을 활용해 더 나은 C 프로그래밍을 경험하세요!