3단원 — 배열
같은 자료형의 값을 **여러 개 한 묶음**으로 다루는 도구가 배열입니다. 변수 100개를 따로 만드는 대신, 100칸짜리 배열 하나로 처리할 수 있습니다.
이 강의에서 배우는 것
- 11차원/2차원 배열을 선언·초기화·인덱싱한다.
- 2`sizeof` 트릭으로 배열 길이를 구한다.
- 3메모리상 배열이 어떻게 놓이는지 그림으로 이해한다.
- 4인덱스 범위 오류의 위험성을 안다.
왜 배열이 필요한가?
학생 5명의 점수를 변수 5개로 처리하면:
int s1=90, s2=85, s3=78, s4=92, s5=88; // 동일한 처리를 5번 반복해야 함배열로 바꾸면:
int score[5] = {90, 85, 78, 92, 88};
for (int i = 0; i < 5; i++) printf("%d\n", score[i]);학생 수가 100명이 되어도 코드는 사실상 그대로입니다.
핵심 개념
1) 선언과 초기화
int a[5]; // 쓰레기 값
int b[5] = {1, 2, 3}; // 나머지는 0으로 채움
int c[] = {10, 20, 30}; // 크기를 컴파일러가 결정 (3)2) 메모리상 배열은 "연속된 칸"
int arr[5] = {10, 20, 30, 40, 50};
주소: 0x100 0x104 0x108 0x10c 0x110
┌─────┬─────┬─────┬─────┬─────┐
│ 10 │ 20 │ 30 │ 40 │ 50 │
└─────┴─────┴─────┴─────┴─────┘
인덱스: [0] [1] [2] [3] [4]각 칸은 **`sizeof(int)`바이트** 만큼씩 떨어져 있습니다. **인덱스는 0부터 시작**합니다. 마지막 인덱스는 `길이 - 1`.
3) 길이 계산 — `sizeof` 트릭
int arr[10];
size_t n = sizeof(arr) / sizeof(arr[0]); // 10전체 바이트 수를 한 칸 바이트 수로 나눕니다. **주의**: 함수 매개변수로 받은 배열에는 안 됩니다(포인터로 변환되어 크기 정보가 사라짐).
4) 2차원 배열 (행렬)
int grid[3][4]; // 3행 4열
grid[i][j] = i * 4 + j;grid[0]: ┌────┬────┬────┬────┐
│ 0 │ 1 │ 2 │ 3 │
grid[1]: ├────┼────┼────┼────┤
│ 4 │ 5 │ 6 │ 7 │
grid[2]: ├────┼────┼────┼────┤
│ 8 │ 9 │ 10 │ 11 │
└────┴────┴────┴────┘C는 **행 우선(row-major)** 으로 메모리에 저장합니다 (한 행이 끝나야 다음 행).
예제로 보기
예제 1 — `ex01_basic.c` : 입력과 출력
int arr[5];
for (int i = 0; i < 5; i++) scanf("%d", &arr[i]);
for (int i = 0; i < 5; i++) printf("%d ", arr[i]);**입력**: `10 20 30 40 50`
arr[0] 입력: arr[1] 입력: arr[2] 입력: arr[3] 입력: arr[4] 입력: 입력값: 10 20 30 40 50핵심: `&arr[i]`처럼 **개별 원소의 주소**를 `scanf`에 전달합니다.
예제 2 — `ex02_max.c` : 최댓값 찾기
int max = arr[0];
for (size_t i = 1; i < n; i++)
if (arr[i] > max) max = arr[i];**실행 결과**
최댓값: 56 (인덱스 5)핵심: "맨 처음 원소를 임시 답으로 잡고, 나머지를 비교" 가 정석 패턴.
예제 3 — `ex03_reverse.c` : 배열 뒤집기
for (size_t i = 0; i < n / 2; i++) {
int t = arr[i];
arr[i] = arr[n - 1 - i];
arr[n - 1 - i] = t;
}**실행 결과**
뒤집힌 결과: 7 6 5 4 3 2 1핵심: **양 끝에서 안쪽으로 swap**. 반복은 `n/2`번이면 충분합니다.
예제 4 — `ex04_2d.c` : 2차원 배열 합
int A[2][3] = {{1, 2, 3}, {4, 5, 6}};
int B[2][3] = {{6, 5, 4}, {3, 2, 1}};
for (i...) for (j...) C[i][j] = A[i][j] + B[i][j];**실행 결과**
A + B =
7 7 7
7 7 7핵심: 2중 for로 행렬을 순회. 출력 시 행마다 `\n`을 잊지 말 것.
다른 시각으로 보기 — `arr[i]`는 사실 `*(arr + i)`
C 문법에서 `arr[i]`는 컴파일러가 자동으로 `*(arr + i)`로 변환합니다. 즉 배열 이름은 **첫 원소의 주소**처럼 동작합니다(자세한 내용은 [포인터](../../03_고급/01_포인터/) 단원).
arr ─► ┌────┬────┬────┬────┬────┐
│ 10 │ 20 │ 30 │ 40 │ 50 │
└────┴────┴────┴────┴────┘
i=0 i=1 i=2 ...
arr + 2 ──────────► (이 주소가 arr[2]의 주소)자주 하는 실수
- **인덱스 오버런**: `int a[5]; a[5] = 0;` → 정의되지 않은 동작.
- **루프 조건 부등호 실수**: `for (i = 0; i <= n; i++)` 로 한 칸 더 접근.
- **함수에서 `sizeof(arr) / sizeof(arr[0])`**: 매개변수에서는 항상 `포인터 / 원소`가 되어 깨짐.
- **2D 배열 행/열 혼동**: `arr[i][j]`의 `i`가 행, `j`가 열.
- **VLA(가변 길이 배열) 의존**: `int a[n];` 은 C99 가변 배열로 가능하지만, 큰 n은 스택 오버플로 위험.
정리
- 배열은 같은 자료형의 값을 메모리에 연속으로 놓은 묶음.
- 인덱스는 **0부터** 시작하며, 마지막은 `길이 - 1`.
- `sizeof(arr) / sizeof(arr[0])`은 지역 배열에서만 유효.
- 2차원 배열은 `[행][열]` 순서로 접근하며 행 우선으로 저장된다.
- 인덱스 범위 검사는 컴파일러가 안 해주므로 **개발자가 책임**진다.
직접 해 보기
cd src
gcc -std=c11 -Wall -o ex01 ex01_basic.c && echo "10 20 30 40 50" | ./ex01
gcc -std=c11 -Wall -o ex02 ex02_max.c && ./ex02
gcc -std=c11 -Wall -o ex03 ex03_reverse.c && ./ex03
gcc -std=c11 -Wall -o ex04 ex04_2d.c && ./ex04응용:
- `ex02`에서 **최솟값**도 같이 찾도록 확장해 보세요.
- `ex04`를 5x5 행렬로 바꿔 단위 행렬(대각선만 1)을 출력해 보세요.
💻 예제 (examples)
실제로 컴파일·실행해 결과를 확인할 수 있는 예제입니다.
#include <stdio.h>
int main(void) {
int arr[5];
for (int i = 0; i < 5; i++) {
printf("arr[%d] 입력: ", i);
scanf("%d", &arr[i]);
}
printf("입력값: ");
for (int i = 0; i < 5; i++) {
printf("%d ", arr[i]);
}
putchar('\n');
return 0;
}
#include <stdio.h>
int main(void) {
int arr[] = {12, 7, 38, 21, 9, 56, 3, 44};
size_t n = sizeof(arr) / sizeof(arr[0]);
int max = arr[0];
int idx = 0;
for (size_t i = 1; i < n; i++) {
if (arr[i] > max) { max = arr[i]; idx = (int)i; }
}
printf("최댓값: %d (인덱스 %d)\n", max, idx);
return 0;
}
최댓값: 56 (인덱스 5)#include <stdio.h>
int main(void) {
int arr[] = {1, 2, 3, 4, 5, 6, 7};
size_t n = sizeof(arr) / sizeof(arr[0]);
for (size_t i = 0; i < n / 2; i++) {
int t = arr[i];
arr[i] = arr[n - 1 - i];
arr[n - 1 - i] = t;
}
printf("뒤집힌 결과: ");
for (size_t i = 0; i < n; i++) printf("%d ", arr[i]);
putchar('\n');
return 0;
}
뒤집힌 결과: 7 6 5 4 3 2 1#include <stdio.h>
int main(void) {
int A[2][3] = {{1, 2, 3}, {4, 5, 6}};
int B[2][3] = {{6, 5, 4}, {3, 2, 1}};
int C[2][3];
for (int i = 0; i < 2; 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 < 2; i++) {
for (int j = 0; j < 3; j++) printf("%3d ", C[i][j]);
putchar('\n');
}
return 0;
}
A + B =
7 7 7
7 7 7📝 과제 (exercises)
직접 풀어보고, 막힐 때 정답을 펼쳐 비교해보세요.
문제 1 (hw01.c)
목표: 정수 10개를 입력받아 합계와 평균(소수점 둘째 자리)을 출력하세요.
- 파일명: hw01.c
▶정답 코드 펼치기 / 접기
#include <stdio.h>
int main(void) {
int arr[10];
int sum = 0;
printf("정수 10개 입력: ");
for (int i = 0; i < 10; i++) {
scanf("%d", &arr[i]);
sum += arr[i];
}
printf("합계: %d\n", sum);
printf("평균: %.2f\n", sum / 10.0);
return 0;
}
문제 2 (hw02.c)
목표: 배열 `{5, 3, 8, 1, 9, 2}`를 **버블 정렬**로 오름차순 정렬해 출력하세요.
- 파일명: hw02.c
▶정답 코드 펼치기 / 접기
#include <stdio.h>
int main(void) {
int arr[] = {5, 3, 8, 1, 9, 2};
int n = (int)(sizeof(arr) / sizeof(arr[0]));
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
int t = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = t;
}
}
}
printf("정렬 결과: ");
for (int i = 0; i < n; i++) printf("%d ", arr[i]);
putchar('\n');
return 0;
}
문제 3 (hw03.c)
목표: `int A[3][3]`을 입력받아 **전치 행렬**을 출력하세요.
- 파일명: hw03.c
▶정답 코드 펼치기 / 접기
#include <stdio.h>
int main(void) {
int A[3][3];
int T[3][3];
printf("3x3 행렬 입력 (총 9개):\n");
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
scanf("%d", &A[i][j]);
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
T[j][i] = A[i][j];
printf("전치 행렬:\n");
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) printf("%4d", T[i][j]);
putchar('\n');
}
return 0;
}