숏컷 코드
배열 길이는 같은 스코프에서만 sizeof(arr) / sizeof(arr[0])로 안전하게 구하고, 함수로 넘길 때는 길이를 같이 전달합니다.
int scores[5] = {90, 85, 100, 70, 88};
int len = (int)(sizeof(scores) / sizeof(scores[0]));
for (int i = 0; i < len; i++) {
printf("%d\n", scores[i]);
}문법
배열은 같은 타입 원소가 연속해서 놓인 고정 길이 메모리 블록입니다. arr[i]는 시작 주소에서 i * sizeof(T)만큼 이동한 위치를 읽는 표현이고, 그래서 배열은 문법과 메모리 경계를 같이 봐야 이해가 쉽습니다. 길이를 어디서 안전하게 구할 수 있는지, 함수 경계를 넘기면 무엇이 사라지는지도 같이 보는 편이 좋습니다.
기본 문법은 이렇게 읽습니다.
int scores[5];
scores[0] = 90;
scores[4] = 88;scores는 원소 5개를 가진int배열입니다.- 유효한 인덱스는
0부터4까지입니다. - 배열 이름은 첫 원소를 가리키는 식처럼 쓰일 수 있지만, 선언 시점의 배열 전체 정보가 언제나 같이 따라가지는 않습니다.
같은 스코프에서 길이를 계산하는 기본 패턴
int scores[5] = {90, 85, 100, 70, 88};
int len = (int)(sizeof(scores) / sizeof(scores[0]));
for (int i = 0; i < len; i++) {
printf("%d\n", scores[i]);
}이 패턴이 안전한 이유는 아직 컴파일러가 scores를 "진짜 배열"로 보고 있기 때문입니다. sizeof(scores)는 배열 전체 바이트 수를, sizeof(scores[0])는 원소 하나의 바이트 수를 뜻합니다.
함수로 넘길 때 길이를 같이 전달하는 패턴
void print_scores(const int *arr, int n) {
for (int i = 0; i < n; i++) {
printf("%d\n", arr[i]);
}
}함수 인자로 넘어가면 int arr[]나 int *arr는 사실상 같은 의미가 됩니다. 이 시점부터는 호출자가 넘긴 배열의 전체 길이를 잃어버리므로, 길이를 따로 같이 넘기는 구조가 기본입니다.
print_scores(scores, len); /* 맞는 호출 */반대로 이런 코드는 틀립니다.
print_scores(scores, sizeof(scores)); /* 바이트 수를 길이처럼 넘김 */함수는 원소 개수를 기대하는데 호출자가 배열 전체 바이트 수를 넘기면 경계가 바로 틀어집니다.
선언 시점에 길이를 추론하는 패턴
int primes[] = {2, 3, 5, 7, 11};
int count = (int)(sizeof(primes) / sizeof(primes[0]));배열 길이 추론은 선언 시점에서 끝납니다. 이 정보가 함수 호출 경계까지 자동으로 따라가지는 않습니다.
경계 이해
- 같은 스코프에서는
sizeof(arr) / sizeof(arr[0])로 길이를 구할 수 있습니다. - 함수 인자로 넘어가면 배열은 포인터처럼 decay되어 길이 정보가 사라집니다.
- 함수에서는 길이를 별도 인자로 같이 넘기는 구조가 기본입니다.
- 배열 문제의 핵심은 경계 초과와 길이 전달 누락입니다.
arr[i] == *(arr + i);arr[i]는 결국 시작 주소에서 i * sizeof(T)만큼 이동한 위치를 읽는 것과 같습니다. 그래서 배열 순회와 포인터 산술이 연결됩니다.
길이 계산이 안전한 위치와 위험한 위치를 같이 보면 차이가 더 분명합니다.
int scores[5] = {90, 85, 100, 70, 88};
int len = (int)(sizeof(scores) / sizeof(scores[0])); /* 안전 */
void print_scores(const int *arr) {
size_t wrong = sizeof(arr) / sizeof(arr[0]); /* 잘못된 길이 */
}- 위
len은 배열 선언이 보이는 같은 스코프에서 계산하므로 안전합니다. - 아래
wrong은 함수 안에서arr가 포인터가 된 뒤에 계산하므로, 배열 길이가 아니라 포인터 크기를 기준으로 계산됩니다. sizeof(scores)는 바이트 수이고,len은 원소 개수라는 점도 같이 구분해야 합니다.
주의할 점
C는 배열 경계를 자동으로 검사하지 않고, 함수 안에서 배열 길이도 자동으로 보존하지 않습니다.
int arr[5] = {1, 2, 3, 4, 5};
arr[5] = 99;- 이런 코드는 즉시 크래시가 안 나더라도 정의되지 않은 동작입니다.
- 함수 안에서
sizeof(arr)를 다시 구하면 배열 전체 크기가 아니라 포인터 크기만 나옵니다. - 배열 길이는 호출자 쪽에서 계산하고, 함수에는 원소 포인터와 길이를 같이 넘기는 편이 맞습니다.
void fill(int arr[]) {
size_t count = sizeof(arr) / sizeof(arr[0]); /* 포인터 크기 기준 */
}arr[]로 적어도 함수 매개변수에서는 배열이 아니라 포인터처럼 동작합니다.- 그래서 배열을 함수로 넘길 때는
pointer + length를 같이 설계하는 습관이 중요합니다.
참고 링크
1 sources