
💡 1분 핵심 요약 (TL;DR)
- ✅ 배열은 같은 타입의 데이터를 여러 개 나열해 저장하는 선형 데이터 구조다
- ✅ 인덱스는 0부터 시작한다 — 크기가 4면 인덱스는 0, 1, 2, 3까지만 유효하다
- ✅ 배열 이름 자체는 배열 첫 번째 요소의 주소(포인터)다
- ✅ 범위를 벗어난 인덱스에 접근하면 다른 메모리를 침범할 수 있어 위험하다
- ✅ sizeof(배열) / sizeof(배열[0])로 배열의 길이(원소 개수)를 계산할 수 있다
01 배열이란 무엇인가
같은 타입의 데이터를 나란히 이어 붙인 선형 구조
배열(array)은 동일한 데이터 타입을 여러 개 저장할 수 있는 자료구조입니다. 데이터가 연속으로 나열되어 있다고 해서 선형 데이터 구조라고 부릅니다. 배열의 각 자리에는 번호가 매겨져 있는데, 이 번호를 인덱스(index)라고 합니다.
⚠️ 인덱스는 0부터 시작한다
컴퓨터는 숫자를 0부터 셉니다. 배열의 크기가 4라면, 사용 가능한 인덱스는 0, 1, 2, 3까지입니다. 4는 존재하지 않습니다. 이 부분에서 초보자들이 가장 많이 실수합니다.
| 인덱스 0 |
인덱스 1 |
인덱스 2 |
인덱스 3 |
| 10 | 20 | 30 | 40 |

각 칸은 데이터 타입에 따라 정해진 바이트 크기(int라면 4바이트)를 차지하며, 메모리 상에서 연속된 공간에 나란히 배치됩니다.
02 배열 선언과 초기화, 두 가지 방법
한 번에 채우거나, 나중에 하나씩 채우거나
📖 배열 선언 기본 구조
타입 배열이름[크기]; 형태로 선언합니다. 초깃값을 함께 넣으면 { }로 감싸서 나열하고, 이때는 [크기]를 생략해도 초깃값 개수만큼 자동으로 크기가 정해집니다.
① 선언과 동시에 초기화
#include <stdio.h>
int main() {
int score[4] = {10, 20, 30, 40};
printf("score[0] = %d\n", score[0]);
printf("score[3] = %d\n", score[3]);
return 0;
}
▶ 실행 결과
score[0] = 10
score[3] = 40
하지만 배열 크기가 100개, 1000개처럼 크다면 초깃값을 전부 나열하기 어렵겠죠. 이럴 땐 선언만 먼저 해두고, 나중에 인덱스로 하나씩 값을 넣을 수 있습니다.
② 선언 후 인덱스로 값 채우기
✅ 직접 실습해본 코드
#include <stdio.h>
int main() {
int array[4];
array[0] = 100;
array[1] = 90;
array[2] = 80;
printf("array 0번 값은 %d 입니다.\n", array[0]);
printf("array 1번 값은 %d 입니다.\n", array[1]);
return 0;
}
▶ 실행 결과
array 0번 값은 100 입니다.
array 1번 값은 90 입니다.
여기서 array[3]에는 값을 넣지 않았습니다. 이 배열을 가지고 바로 다음 섹션에서 "범위를 벗어나면 어떻게 될까"를 직접 실험해보겠습니다.
✓ 모든 원소를 0으로 초기화하려면
배열이 매우 클 때는 {0} 하나만 적어도 나머지 모든 원소가 자동으로 0이 됩니다.
int arr[100] = {0};
반대로 { 1 }처럼 0이 아닌 값을 넣으면, 첫 번째 원소만 1이 되고 나머지는 여전히 0이 됩니다. 0 이외의 값으로는 전체를 한 번에 채울 수 없습니다. {0, } 처럼 콤마를 하나 더 붙이는 표기도 종종 보게 되는데, 동작은 완전히 동일합니다 — 취향의 차이일 뿐입니다.
int multi_array[100] = {0, };
03 배열 이름은 사실 주소다
scanf에서 배열에 &를 안 붙였던 이유가 바로 이거였다
배열 이름 자체는 배열의 첫 번째 요소를 가리키는 포인터(주소)입니다. arr[2]처럼 인덱스를 붙이면, "배열의 시작 주소에서 2칸을 이동한 곳의 값"을 의미하게 됩니다.

| arr · 배열의 첫 번째 요소 주소 · 포인터값 그 자체 · 그대로 쓰면 "주소"를 의미 |
arr[2] · 주소에서 2칸 이동한 위치 · 그 위치에 저장된 값 · 데이터 내용물을 의미 |
📖 그래서 scanf("%s", name)에 &가 없었다
문자 하나를 입력받을 땐 &변수로 직접 주소를 만들어 넘겼지만, 문자열(배열)을 입력받을 땐 배열 이름만 그대로 썼습니다. 배열 이름 자체가 이미 주소값이기 때문에 &가 필요 없었던 겁니다. 이제 그 이유가 명확해졌습니다.
그래서 scanf로 배열의 특정 인덱스에 값을 입력받을 때는, 그 인덱스가 가리키는 값이 아니라 그 위치의 주소가 필요하므로 &를 붙여줘야 합니다.
특정 인덱스에 scanf로 값 저장하기
#include <stdio.h>
int main() {
int score[10] = {0};
printf("6번째 칸에 넣을 점수를 입력하세요: ");
scanf("%d", &score[5]); // 5번 인덱스의 "주소"에 값을 저장
printf("입력된 값: %d\n", score[5]);
return 0;
}
▶ 실행 결과
6번째 칸에 넣을 점수를 입력하세요: 99
입력된 값: 99
⚠️ 자주 하는 실수 — printf에 넣은 인자가 조용히 무시된다
배열을 순회하며 출력할 때 이런 코드를 실수로 쓰기 쉽습니다.
printf("%d번째 점수 : \n", index, score[index]);
포맷 스트링에 %d가 하나뿐인데 인자는 두 개(index, score[index])를 넘겼습니다. C는 이런 경우 에러를 내지 않고, 자리가 없는 나머지 인자를 그냥 무시합니다. 그 결과 index만 출력되고 score[index] 값은 영영 화면에 나오지 않는, 발견하기 까다로운 버그가 됩니다. 값을 출력하려면 포맷 스트링의 %d 개수와 인자 개수를 항상 맞춰야 합니다.
04 위험한 실수 — 범위를 벗어난 인덱스
"되긴 되는데" 절대 하면 안 되는 이유
크기 4인 배열에 인덱스 4로 접근하면 어떻게 될까요? 코드는 문법적으로 컴파일되고 실행도 됩니다. 하지만 이건 잘못된 코드입니다. 앞서 만든 array[4] 배열을 그대로 이어서 실험해보겠습니다.
❌ 직접 실습해본 코드 — 하면 안 되는 두 가지
#include <stdio.h>
int main() {
int array[4];
array[0] = 100;
array[1] = 90;
array[2] = 80; // array[3]은 아무 값도 넣지 않았다
// Q1. 없는 인덱스에 값을 저장하면?
array[4] = 55;
printf("array 4번 값은 %d 입니다.\n", array[4]);
// Q2. 초기화하지 않은 인덱스를 출력하면?
printf("array 3번 값은 %d 입니다.\n", array[3]);
return 0;
}
▶ 실행 결과 (정의되지 않은 동작 — 실행할 때마다 달라질 수 있다)
array 4번 값은 55 입니다. (하필 이번엔 넣은 값이 그대로 나왔을 뿐, 보장된 동작이 아니다)
array 3번 값은 예측 불가능한 값 입니다. (우연히 0이 나올 수도, 전혀 다른 값이 나올 수도 있다)

배열의 크기를 4로 정한다는 건, 컴퓨터에게 "0~3번 칸만 쓸게"라고 약속하는 것과 같습니다. 그 범위를 벗어나면 다른 변수가 이미 사용 중인 메모리를 덮어쓸 수 있고, 운이 나쁘면 프로그램에 아예 접근이 허용되지 않은 영역을 건드려 세그멘테이션 폴트(Segmentation Fault)로 프로그램이 강제 종료될 수도 있습니다. "되니까 괜찮다"가 아니라, 겉보기엔 동작해도 절대 하면 안 되는 코드라는 걸 꼭 기억해야 합니다.
⚠️ 초기화하지 않은 값을 읽는 것도 위험하다
값을 넣지 않은 인덱스를 출력하면 우연히 0이 나올 때가 있지만, 항상 0이라는 보장은 없습니다. 초기화되지 않은 메모리에는 이전에 다른 데이터가 사용하던 쓰레기값이 남아있을 수 있습니다. 반드시 사용 전에 명시적으로 초기화하는 습관을 들이는 게 안전합니다.
05 sizeof로 배열의 크기 구하기
sizeof는 원소 개수가 아니라 '바이트 크기'를 알려준다
배열의 원소 개수가 많거나, 나중에 데이터가 추가될 수도 있는 상황이라면 배열의 크기를 자동으로 계산하는 방법이 필요합니다. 이때 쓰는 게 sizeof입니다. 단, sizeof는 원소 개수가 아니라 전체 바이트 크기를 돌려준다는 점을 주의해야 합니다.
배열의 길이(원소 개수) 계산하기
✅ 직접 실습해본 코드 — 원소가 많은 배열
#include <stdio.h>
int main() {
int array[] = {1, 2, 3, 4, 5, 7, 8, 1, 32, 451, 4, 24, 1, 5, 2, 43, 6, 121, 4, 1};
int arrayLength = sizeof(array) / sizeof(int);
printf("배열의 크기는: %d\n", arrayLength);
return 0;
}
▶ 실행 결과
배열의 크기는: 20

📖 sizeof(int) vs sizeof(array[0])
위 코드는 array가 int형이라는 걸 알고 있으니 sizeof(int)로 나눴습니다. 결과는 동일하지만, sizeof(array[0])으로 나누는 방식이 조금 더 안전합니다. 나중에 배열의 타입을 double이나 char로 바꿔도 코드를 수정할 필요가 없기 때문입니다.
int는 4바이트이고 원소가 20개이므로 sizeof(array)는 80바이트를 돌려줍니다. 여기서 int 하나의 크기(4바이트)로 나누면 실제 원소 개수 20이 정확히 나옵니다. 데이터가 아무리 많아도, 나중에 배열에 값이 추가되어도 이 계산식은 그대로 동작합니다.
📖 sizeof 값을 화면에 직접 출력하고 싶다면
sizeof 연산자가 돌려주는 값의 타입은 int가 아니라 size_t입니다. 그래서 sizeof(array) 자체를 printf로 바로 출력하고 싶다면 %d 대신 %zu를 쓰는 것이 표준적이고 안전한 방법입니다. 위 코드처럼 나눗셈까지 끝내서 int 변수(arrayLength)에 담아 출력할 때는 %d를 그대로 써도 괜찮습니다.
06 실전 예제 — 점수 총합과 평균 구하기
데이터가 늘어나도 코드는 그대로여야 좋은 코드다
배열의 길이가 정해져 있는 상황은 for문으로 순회하기에 적합합니다. 점수 배열을 순회하며 총합과 평균을 구해보겠습니다. 평균을 구할 때 4로 직접 나누는 대신, sizeof로 계산한 길이를 사용하면 데이터 개수가 바뀌어도 코드를 수정할 필요가 없습니다.
점수 총합·평균 구하기
#include <stdio.h>
int main() {
int score[] = {100, 90, 50, 60};
int len = sizeof(score) / sizeof(score[0]);
int total = 0;
for (int i = 0; i < len; i++) {
printf("%d번째 점수: %d\n", i, score[i]);
total += score[i];
}
printf("종합 점수: %d\n", total);
printf("평균 점수: %.2f\n", (double)total / len);
return 0;
}
▶ 실행 결과
0번째 점수: 100
1번째 점수: 90
2번째 점수: 50
3번째 점수: 60
종합 점수: 300
평균 점수: 75.00

📖 (double)total / len 에서 괄호는 왜 필요할까
total과 len이 둘 다 int면 나눗셈 결과도 정수로 잘려서 계산됩니다(300 / 4는 나머지 없이 딱 떨어지지만, 나누어떨어지지 않는 경우엔 소수점이 사라집니다). (double)로 미리 형변환해두면 소수점까지 정확한 평균을 구할 수 있습니다.
07 응용 예제 — 비어있는 값 찾아서 채우기
0으로 남아있는 자리만 골라서 입력받기
일부 값이 아직 입력되지 않아 0으로 남아있는 배열이 있다고 해보겠습니다. 배열을 순회하다가 값이 0인 자리만 골라서 사용자에게 입력을 받아보겠습니다.
0인 자리만 찾아서 입력받기
#include <stdio.h>
int main() {
int score[] = {0, 90, 60, 0, 40, 0, 80};
int len = sizeof(score) / sizeof(score[0]);
for (int i = 0; i < len; i++) {
if (score[i] == 0) {
printf("%d번째 점수를 입력하세요: ", i);
scanf("%d", &score[i]);
}
}
for (int i = 0; i < len; i++) {
printf("%d ", score[i]);
}
printf("\n");
return 0;
}
▶ 실행 결과
0번째 점수를 입력하세요: 100
3번째 점수를 입력하세요: 44
5번째 점수를 입력하세요: 66
100 90 60 44 40 66 80
이미 값이 채워진 90, 60, 40, 80 자리는 건너뛰고, 0인 자리(0, 3, 5번 인덱스)에서만 입력을 받는 것을 확인할 수 있습니다. 이때도 scanf에는 &score[i]처럼 주소를 넘겨야 한다는 점을 잊지 않아야 합니다.
08 2차원 배열 이해하기
좌표처럼 값 두 개가 필요할 때는 2차원 배열
일직선 위의 데이터는 1차원 배열로 충분하지만, (x, y) 좌표처럼 값 두 개가 한 쌍으로 필요한 경우엔 2차원 배열이 필요합니다. (x, y, z)처럼 세 개가 필요하면 3차원이 되는 식입니다.

3×3 2차원 배열 순회하기
#include <stdio.h>
int main() {
int arr[3][3] = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
printf("%d ", arr[i][j]);
}
printf("\n");
}
return 0;
}
▶ 실행 결과
1 2 3
4 5 6
7 8 9
✓ 껍질을 벗기는 느낌으로 이해하기
바깥쪽 for문의 i가 "몇 번째 줄"을 고르고, 안쪽 for문의 j가 그 줄 안에서 "몇 번째 칸"을 고릅니다. i가 0일 때 j가 0→1→2로 돌면서 첫 번째 줄 {1,2,3}을 전부 순회하고, i가 1로 넘어가면 두 번째 줄을 순회하는 식입니다. 게임 좌표, 데이터 분석의 표(table) 구조 등에서 2차원 배열이 자주 쓰입니다.
09 배열 복사하기 — for문 vs memcpy
arr2 = arr1; 은 왜 안 될까
다른 언어에 익숙하다면 arr2 = arr1;처럼 배열 전체를 통째로 대입하면 될 것 같지만, C언어에서는 이렇게 하면 안 됩니다. 배열 이름은 주소값 하나일 뿐이라, 배열 전체가 아니라 주소만 복사하려는 시도로 처리되어 컴파일 에러가 납니다.
❌ 이렇게 배열을 통째로 대입할 수는 없다
int arr1[5] = {1, 2, 3, 4, 5};
int arr2[5];
arr2 = arr1; // 컴파일 에러!
▶ 실행 결과 (컴파일 자체가 되지 않음)
error: assignment to expression with array type
대신 for문으로 한 칸씩 옮기거나, string.h의 memcpy 함수를 사용해야 합니다.
① for문으로 한 칸씩 복사
#include <stdio.h>
int main() {
int arr1[5] = {1, 2, 3, 4, 5};
int arr2[5];
for (int i = 0; i < 5; i++) {
arr2[i] = arr1[i];
}
for (int i = 0; i < 5; i++) {
printf("%d ", arr2[i]);
}
printf("\n");
return 0;
}
▶ 실행 결과
1 2 3 4 5
② memcpy로 한 번에 복사
✅ 직접 실습해본 코드
#include <stdio.h>
#include <string.h>
int main() {
int arr1[5] = {1, 2, 3, 4, 5};
int arr2[5];
memcpy(arr2, arr1, sizeof(arr1));
for (int i = 0; i < 5; i++) {
printf("%d ", arr2[i]);
}
printf("\n");
return 0;
}
▶ 실행 결과
1 2 3 4 5

📖 memcpy의 세 번째 인자는 "바이트 크기"다
memcpy는 배열 전용 함수가 아니라 메모리 자체를 복사하는 함수입니다. 그래서 원소 개수(5)가 아니라 복사할 바이트 수를 넘겨야 합니다. 위 코드처럼 sizeof(arr1)을 그대로 넘기면, arr1의 전체 바이트 크기가 자동으로 계산되기 때문에 배열 크기나 원소 개수를 직접 계산해서 넣을 필요가 없습니다. 원소 개수를 하드코딩(예: sizeof(int) * 5)하면 나중에 배열 크기가 바뀔 때 함께 수정해야 하지만, sizeof(arr1)은 배열이 바뀌어도 코드를 그대로 유지할 수 있어 더 안전합니다.
📌 핵심 정리
· 배열 : 같은 타입의 데이터를 나열해 저장하는 선형 데이터 구조
· 인덱스 : 0부터 시작하며, 크기가 n이면 유효 인덱스는 0~(n-1)까지
· 배열 이름 : 배열 첫 번째 요소의 주소(포인터) — 그래서 scanf에 &가 필요 없었다
· 범위 밖 접근 : 겉보기엔 동작해도 다른 메모리를 침범할 수 있는 위험한 코드다
· sizeof(arr) / sizeof(arr[0]) : 데이터 타입에 상관없이 배열의 길이를 구하는 공식
· 2차원 배열 : 좌표처럼 값 두 개가 필요할 때 사용하며, 이중 for문으로 순회한다
· 배열 복사 : arr2 = arr1;은 불가능하다 — for문으로 하나씩 옮기거나 memcpy를 사용한다
· 다음 강의인 포인터를 배우고 나면, 오늘 배운 배열의 원리가 훨씬 명확해진다
태그 : C언어 배열 사용법배열 인덱스 개념C언어 sizeof 사용법배열 이름 포인터배열 범위 초과 오류2차원 배열 C언어배열 복사 memcpyC언어 정적 배열배열 평균 구하기C언어 코딩 기초 강의
'학과 수업 노트 > C언어 기초 배우기' 카테고리의 다른 글
| 13. 문자열과 string.h (0) | 2026.07.18 |
|---|---|
| 12. 포인터 (0) | 2026.07.14 |
| 10. 함수 (0) | 2026.07.05 |
| 9. Switch Case문 (0) | 2026.07.03 |
| 8. 반복문 while문과 무한반복 (0) | 2026.07.02 |

