Menu

C++ 배열: 선언, 인덱싱, 그리고 흔한 함정

C++ 원시 배열 설명: 선언과 초기화 방법, 안전한 인덱싱, 크기를 사용한 반복, 배열에서 포인터로의 감쇠 함정, 그리고 std::array와 vector가 보통 더 나은 이유.

이 페이지에는 실행 가능한 에디터가 있습니다 - 편집하고 실행하면 결과를 바로 볼 수 있습니다.

고정 크기의 값들의 한 줄

배열은 같은 타입의 값을 고정된 개수만큼 담는 연속된 메모리 블록입니다. 하나의 int가 숫자 하나를 저장한다면, int 배열은 그것들을 줄지어 여럿 저장하며, 각각은 정수 인덱스로 접근할 수 있습니다.

배열을 선언하려면 요소 타입, 이름, 그리고 대괄호 안의 크기를 지정합니다. 내용을 채우려면 중괄호로 둘러싼 목록을 추가하세요:

인덱싱은 0부터 시작합니다. 첫 번째 요소는 scores[0]이고, 크기가 4인 배열의 유효한 인덱스는 0부터 3까지입니다. 크기는 컴파일 타임 상수여야 합니다. 표준 C++에서는 int n = readInput(); int a[n];처럼 쓸 수 없습니다(그것은 이식성 없는 확장입니다). 크기를 런타임에 결정해야 한다면 대신 vector를 사용하세요.

배열 초기화하기

배열을 채우는 방법은 몇 가지가 있고, 알아 둘 만한 단축 표기도 몇 가지 있습니다:

꼭 익혀 둘 핵심: 크기보다 적은 수의 초기화 값을 주면 남는 자리는 쓰레기로 남는 게 아니라 값 초기화됩니다(수치 타입은 0). 그러나 초기화 값이 전혀 없는 배열, 즉 지역 변수로 선언한 int e[4];는 불확정 값을 담고 있으며, 대입하기 전에 읽는 것은 정의되지 않은 동작입니다.

배열 반복하기

요소들이 연속으로 저장되어 있으므로 배열은 단순한 인덱스 루프로 순회합니다. 범위 안에 머물려면 하드코딩한 숫자가 아니라 배열의 실제 길이로 루프를 구동하세요:

sizeof(scores)는 배열 전체의 총 바이트 수이며, 이를 sizeof(scores[0])(요소 하나의 크기)로 나누면 요소 개수가 나옵니다. C++17부터는 더 깔끔한 표기인 std::size(scores)가 있는데, 더 읽기 좋고 실수로 포인터를 넘기면 컴파일을 거부합니다. 값만 필요할 때는 더 간단합니다. 범위 기반 for를 쓰면 인덱스 계산을 통째로 건너뛸 수 있습니다.

범위 초과 함정

C++는 arr[i]에 대해 경계 검사를 전혀 하지 않습니다. 마지막 요소를 넘어 인덱싱해도 예외를 던지거나 경고하지 않고, 그 자리에 우연히 있는 메모리를 읽거나 씁니다. 이것은 단연 가장 흔한 배열 버그이며 전형적인 정의되지 않은 동작입니다:

int a[3] = {1, 2, 3};
a[3] = 99;          // OOPS - valid indices are 0..2, not 3
cout << a[5];       // garbage, crash, or corruption - undefined behavior

하나 차이(off-by-one) 오류는 보통 루프 조건에 숨어 있습니다. i < n 대신 i <= n이라고 쓰면 한 걸음 더 나아가 존재하지 않는 arr[n]을 건드립니다:

for (int i = 0; i <= n; i++)   // BUG: when i == n, arr[i] is out of bounds
    cout << arr[i];

해결책은 앞 절의 규율입니다. 루프는 <=가 아니라 항상 i < size로 돌리고, 요소를 추가할 때 어긋나는 리터럴을 다시 타이핑하는 대신 크기를 배열에서 계산하세요.

배열 감쇠: 숨어 있는 포인터

C++에서 가장 까다로운 배열 동작은 **감쇠(decay)**입니다. 배열을 함수에 넘기면 그것은 조용히 첫 요소를 가리키는 포인터로 변환됩니다. 크기 정보가 사라지므로, 함수 내부의 sizeof는 배열이 아니라 포인터를 측정합니다.

함수 매개변수로서 int arr[]int* arr는 동일하다는 점에 유의하세요. 대괄호는 보기 좋게 만드는 장식일 뿐입니다. 개수가 사라지므로 길이는 배열과 함께 직접 넘겨야 합니다:

int sum(const int* arr, int n) {
    int total = 0;
    for (int i = 0; i < n; i++) total += arr[i];
    return total;
}

이 "포인터와 길이를 넘기고 둘이 맞기를 기도하라"는 패턴이 바로 대부분의 C++ 코드를 std::arraystd::vector 쪽으로 밀어붙이는 마찰입니다. 이들은 자신의 크기를 지니며 결코 감쇠하지 않습니다.

다차원 배열에 대한 짧은 한마디

대괄호를 중첩하면 격자를 만들 수 있습니다. 2차원 배열은 사실 배열의 배열이며, 메모리에 행 단위로 차례로 놓입니다:

grid[row][col] 형태로 인덱싱합니다. 경계와 감쇠에 관한 동일한 주의 사항이 적용되며, 더 심해집니다. 2차원 배열을 함수에 넘기려면 첫 차원을 제외한 모든 차원을 명시해야 하기 때문입니다(void f(int g[][3])). 작은 고정 격자를 넘어서는 무엇이든, vectorvector가 오류를 훨씬 덜 일으킵니다.

다음: Vector

원시 배열은 빠르고 예측 가능하지만, 고정 크기, 경계 안전성의 부재, 포인터로의 감쇠 동작 때문에 일상 코드에서는 다루기 불편합니다. 다음으로 std::vector를 만나 봅니다. 필요에 따라 늘어나는 크기 조정 가능한 배열로, 자신의 크기를 기억하고 STL 알고리즘에 곧바로 연결되며, 배열이 제공하는 거의 모든 것을 자기 발에 총 쏠 방법은 훨씬 줄인 채로 안겨 줍니다.

자주 묻는 질문

C++에서 배열을 어떻게 선언하고 초기화하나요?

요소 타입, 이름, 그리고 대괄호 안의 크기를 적고, 필요하면 중괄호 목록을 덧붙입니다: int scores[4] = {90, 75, 100, 60};. 초기화 값을 제공하면 크기를 생략할 수 있습니다. int scores[] = {90, 75, 100, 60};처럼 쓰면 컴파일러가 대신 개수를 세어 줍니다.

C++에서 배열의 길이를 어떻게 구하나요?

아직 스코프 안에 있는 진짜 배열에 대해서는 std::size(arr)(C++17)나 sizeof(arr) / sizeof(arr[0])을 사용하세요. 배열이 포인터로 감쇠한 뒤(예: int arr[]를 받은 함수 내부)에는 이것이 동작하지 않습니다. 그곳에서 sizeof는 배열이 아니라 포인터의 크기를 돌려줍니다.

C++에서 배열의 범위를 벗어나 접근하면 어떻게 되나요?

정의되지 않은 동작입니다. C++는 arr[i]에 대해 경계 검사를 하지 않으므로, 끝을 넘어 읽거나 쓰면 충돌하거나 쓰레기 값을 돌려주거나 인접한 메모리를 조용히 손상시킬 수 있습니다. 인덱스는 항상 0부터 size - 1 범위 안에 두세요.

Coddy programming languages illustration

Coddy로 코딩 배우기

시작하기