무엇을 하는 것인가
int 점수[5];는 정수 다섯 개가 들어갈 자리를 메모리에 나란히 잡습니다. 번호는 점수[0]부터 점수[4]까지입니다. 파이썬 리스트와 달리 크기를 나중에 늘릴 수 없습니다.
C에서 가장 위험한 성질이 여기 있습니다. 점수[5]나 점수[100]을 써도 컴파일이 됩니다. 실행도 됩니다. 다만 남의 자리를 읽거나 덮어씁니다. 그래서 프로그램이 엉뚱하게 동작하다가 한참 뒤에 죽고, 원인을 찾기가 매우 어렵습니다. 파이썬은 IndexError로 즉시 알려주지만 C는 알려주지 않습니다.
배열의 크기는 스스로 기억하지 못합니다. 그래서 배열을 함수에 넘길 때는 크기도 같이 넘겨야 합니다. 이건 배열이 함수에 넘어갈 때 그냥 첫 원소의 주소로 바뀌기 때문인데, 자세한 건 포인터 글에서 이어집니다.
sizeof(배열) / sizeof(배열[0])로 원소 개수를 구할 수 있습니다. 다만 이건 배열이 선언된 그 함수 안에서만 통합니다. 함수로 넘어간 뒤에는 주소가 되어 크기 정보가 사라집니다.
예제 코드
아래 코드가 위 버튼으로 받는 파일과 같은 내용입니다. 통째로 복사해 실행해 보고, 값을 바꿔 가며 결과가 어떻게 달라지는지 보는 편이 읽기만 하는 것보다 훨씬 빨리 익습니다.
/* 배열 — 범위를 넘어가도 C 는 막지 않습니다
컴파일: gcc c-array.c -o arr -Wall
범위 검사까지: gcc c-array.c -o arr -Wall -fsanitize=address */
#include <stdio.h>
/* 배열은 크기를 스스로 기억하지 못합니다. 크기를 같이 넘겨야 합니다. */
double 평균(const int 값[], int 개수)
{
if (개수 <= 0) {
return 0.0;
}
int 합 = 0;
for (int i = 0; i < 개수; i++) {
합 += 값[i];
}
return (double)합 / 개수; /* 정수끼리 나누면 잘립니다 */
}
int main(void)
{
int 점수[5] = {90, 85, 77, 68, 94};
/* 선언된 함수 안에서는 이렇게 개수를 구할 수 있습니다 */
int 개수 = (int)(sizeof(점수) / sizeof(점수[0]));
printf("원소 개수: %d\n", 개수);
for (int i = 0; i < 개수; i++) {
printf("점수[%d] = %d\n", i, 점수[i]);
}
printf("평균: %.2f\n", 평균(점수, 개수));
/* 최대 · 최소 찾기 — 첫 원소를 기준으로 두고 훑습니다 */
int 최고 = 점수[0], 최저 = 점수[0];
for (int i = 1; i < 개수; i++) {
if (점수[i] > 최고) { 최고 = 점수[i]; }
if (점수[i] < 최저) { 최저 = 점수[i]; }
}
printf("최고 %d / 최저 %d\n", 최고, 최저);
/* 전부 0 으로 채우기 */
int 빈배열[5] = {0};
printf("빈배열[3] = %d\n", 빈배열[3]);
/* 2차원 배열 — 표[행][열] */
int 표[2][3] = { {1, 2, 3}, {4, 5, 6} };
for (int 행 = 0; 행 < 2; 행++) {
for (int 열 = 0; 열 < 3; 열++) {
printf("%d ", 표[행][열]);
}
printf("\n");
}
/* ── 이 줄의 주석을 풀면 무슨 일이 일어나는지 보세요 ──
컴파일도 되고 실행도 됩니다. 남의 메모리를 읽습니다.
-fsanitize=address 로 컴파일하면 그 자리를 잡아 줍니다.
printf("점수[10] = %d\n", 점수[10]);
*/
return 0;
}
기억할 것
int 점수[5];— 번호는 0부터 4까지.- 범위를 넘어가도 컴파일·실행이 됩니다. 조용히 남의 메모리를 건드립니다.
int a[5] = {0};— 이렇게 하면 전부 0으로 채워집니다.- 개수는
sizeof(a) / sizeof(a[0]). 함수 안으로 넘어가면 안 통합니다. - 배열을 함수에 넘길 때는 크기도 같이 넘기세요.
자주 묻는 것
이상한 값이 나오거나 갑자기 죽습니다
배열 범위를 넘어갔을 확률이 높습니다. 반복문의 조건식(<인지 <=인지)과 인덱스 계산을 확인하세요. gcc -fsanitize=address로 컴파일하면 범위를 넘는 순간을 잡아 줍니다. 배우는 동안 아주 쓸모 있는 옵션입니다.
배열 크기를 변수로 정해도 되나요
C99부터는 int n = 10; int a[n];이 됩니다(가변 길이 배열). 다만 크기가 아주 크면 위험하고 지원이 들쭉날쭉해서, 실무에서는 malloc으로 잡는 편입니다. 배우는 단계에서는 #define MAX 100처럼 상수로 두는 것이 무난합니다.
배열을 통째로 복사하려면요
b = a;는 안 됩니다. 반복문으로 하나씩 옮기거나 memcpy(b, a, sizeof(a));를 씁니다(<string.h> 필요).
2차원 배열은 어떻게 쓰나요
int 표[3][4];처럼 선언하고 표[행][열]로 접근합니다. 메모리에는 행 단위로 쭉 이어져 있습니다. 반복문도 바깥이 행, 안쪽이 열 순서로 도는 편이 빠릅니다.