HOWTO · C++
C++에서 new를 사용하여 2D 배열을 선언하는 방법
이 기사에서는 new in C++를 사용하여 2D 배열을 선언하는 방법을 설명합니다.
이 페이지의 내용
C++에서 실행 중에 크기를 정하는 행렬은 여러 방식으로 표현할 수 있지만, new를 사용해도 내장 다차원 배열이 만들어지는 것은 아닙니다. 요소의 배치 방식과 저장 공간의 소유권을 직접 선택해야 합니다. 아래 예제는 두 행과 세 열을 사용해 인덱스가 저장된 값에 어떻게 대응하는지 보여 줍니다. 행 할당 예제는 제공된 실행 환경에서 정상적으로 실행되었습니다.
실행 중 크기가 정해지는 2차원 배열의 표현 방식
int data[2][3]처럼 내장 배열을 선언하면 크기는 고정됩니다. 실행 중에 차원을 정하는 방법으로는 행을 각각 할당하는 int **, 인덱스를 계산해 접근하는 단일 연속 할당, 중첩 std::vector 객체가 있습니다. 이 방식들은 모두 행과 열로 요소에 접근할 수 있지만, 메모리 배치와 소유권 관리 방식은 서로 다릅니다. C++은 행이나 열 인덱스가 유효한 범위에 있는지 확인하지 않습니다.
arr[i][j]로 접근하도록 행을 따로 할당하기
int **는 행 포인터 배열을 가리키며, 요소가 들어 있는 하나의 직사각형 메모리 블록을 가리키지는 않습니다. 먼저 포인터 배열을 할당한 다음 각 행을 할당합니다. 이 방식은 익숙한 matrix[i][j] 표기법을 유지하면서, 각 행의 길이를 직접 기록하고 관리하면 행마다 길이가 다른 데이터도 다룰 수 있습니다.
아래 예제를 실행해 보세요. 코드와 화면에 표시되는 문구는 영어로 되어 있습니다.
이 섹션의 코드와 표시되는 레이블 또는 출력은 영어로 유지됩니다.
#include <iostream>
int main() {
const int rows = 2;
const int columns = 3;
int **matrix = new int*[rows];
for (int i = 0; i < rows; ++i) {
matrix[i] = new int[columns]{};
}
for (int i = 0; i < rows; ++i) {
for (int j = 0; j < columns; ++j) {
matrix[i][j] = i * columns + j + 1;
std::cout << matrix[i][j] << (j + 1 == columns ? '\n' : ' ');
}
}
for (int i = 0; i < rows; ++i) {
delete[] matrix[i];
}
delete[] matrix;
}
각 new[] 할당에는 delete[]를 짝지어야 합니다. 먼저 행을 해제하고, 그다음 포인터 배열을 해제하세요. 생성 도중 할당에 실패할 수 있는 원시 포인터 구현에는 별도의 정리 로직이 필요합니다. 실제 코드에서는 객체 수명을 직접 관리하지 않도록 소유 컨테이너나 스마트 포인터를 사용하는 편이 좋습니다.
요소를 하나의 연속된 메모리 할당에 저장하기
new[]를 한 번 할당하면 모든 요소가 함께 저장됩니다. 행 우선 배치에서는 행 i, 열 j에 있는 요소의 오프셋이 i * columns + j입니다. 포인터만으로는 행과 열의 개수를 알 수 없으므로 차원 정보도 보관해야 합니다. 할당 크기를 계산하는 곱셈 결과가 프로그램에서 사용하는 형식과 차원으로 표현 가능한지 확인하세요.
예를 들어 int *data에 rows * columns개의 요소를 할당한 뒤 data[i * columns + j]로 요소에 접근할 수 있습니다. delete[] data로 할당을 한 번 해제합니다. 이 방식은 메모리를 연속으로 배치하지만 data[i][j] 표기법으로 바로 접근할 수는 없습니다. 보조 함수나 컨테이너 추상화를 사용하면 오프셋 계산을 감출 수 있습니다. 메모리 배치만으로 성능의 우열을 판단할 수는 없으므로 속도가 중요하다면 전체 작업을 측정하세요.
자동 메모리 관리를 위해 std::vector 사용하기
일반적인 애플리케이션 코드에서는 중첩 벡터가 소유권을 명확하게 표현하는 경우가 많습니다. 내부 벡터가 각 행을 소유하고, 바깥 벡터가 범위를 벗어나면 소멸자가 요소를 자동으로 해제합니다. 다음 초기화는 행마다 요소 세 개가 들어 있는 두 행을 만듭니다.
std::vector<std::vector<int>> matrix(2, std::vector<int>(3, 0));
matrix[i][j]로 요소에 접근하거나 범위 기반 반복문으로 행과 값을 순회할 수 있습니다. 각 행의 크기를 따로 조정할 수 있어 행마다 길이가 다른 데이터에 유용합니다. 원시 배열과 마찬가지로 벡터의 operator[]는 범위를 검사하지 않으므로, 범위 검사가 필요하면 at()을 사용하세요. 중첩 벡터는 모든 행이 하나의 연속된 메모리 블록에 저장된다고 보장하지 않습니다.
방식을 선택하고 한계를 관리하기
행 길이가 달라질 수 있거나 포인터 대 포인터 형식이 필요하다면 행을 따로 할당하세요. 하나의 소유 메모리 블록과 명시적인 행 우선 인덱싱이 인터페이스에 맞다면 단일 연속 할당을 선택하세요. 자동 정리와 크기 조정, 간단한 소유권 관리가 중요하다면 std::vector를 우선 고려하세요. 직접 소유하는 원시 new 할당보다 일반적으로 더 안전한 출발점입니다.
컴파일 시점에 차원을 알고 있다면 int matrix[2][3] 같은 고정 크기 내장 배열이 간단합니다. 수동 할당으로 행마다 길이가 다른 데이터를 다룬다면 각 행의 길이를 저장해 접근과 정리에 사용하세요. 어떤 방식을 쓰든 인덱싱 전에 차원을 확인해야 합니다. 원시 포인터나 operator[]로 범위를 벗어난 요소에 접근하면 동작이 정의되지 않습니다. new[]로 할당한 배열마다 짝이 되는 delete[]를 사용하세요. 소유권을 의도적으로 이전하거나 공유하는 경우가 아니라면 소유 원시 포인터를 복사하지 마세요.