숏컷 코드
배열 이름은 많은 문맥에서 첫 원소 포인터처럼 바뀝니다. 다만 배열 전체가 사라지는 것은 아니고, 문맥에 따라 배열 그대로 남을 때도 있습니다.
int arr[5] = {10, 20, 30, 40, 50};
int *p = arr;
printf("%d\n", p[2]);
printf("%d\n", *(p + 2));문법
배열은 같은 타입 원소가 연속된 메모리에 놓인 고정 길이 블록입니다. 그런데 배열 이름은 식(expression)으로 쓰일 때 배열 전체가 아니라 첫 원소 주소처럼 변환(decay) 되는 경우가 많습니다.
int arr[4] = {1, 2, 3, 4};
int *p1 = arr;
int *p2 = &arr[0];
printf("%d\n", arr[2]); // 3
printf("%d\n", *(arr + 2)); // 3이 감각이 잡히면 arr[i]와 *(arr + i)가 왜 같은지도 같이 이해됩니다. 배열 이름이 “첫 원소 주소로 바뀐 상태”이기 때문입니다.
전달 규칙
배열을 함수에 넘기는 순간 매개변수는 사실상 포인터로 읽어야 합니다. 그래서 함수 안에서는 길이 정보가 자동으로 따라가지 않습니다.
void print_all(const int *arr, size_t n) {
for (int i = 0; i < n; i++) {
printf("%d\n", arr[i]);
}
}
int data[] = {10, 20, 30, 40};
size_t len = sizeof(data) / sizeof(data[0]);
print_all(data, len);같은 스코프에서는 sizeof(arr) / sizeof(arr[0])로 길이를 계산할 수 있지만, 함수 안으로 들어간 뒤에는 이 계산을 믿으면 안 됩니다.
void broken(int arr[]) {
size_t wrong_len = sizeof(arr) / sizeof(arr[0]); // 포인터 크기 기준
}예외 상황
sizeof(arr)와 &arr, 배열 선언 초기화 같은 경우에는 배열 전체가 유지됩니다.
int arr[4] = {1, 2, 3, 4};
size_t bytes = sizeof(arr);
int (*whole)[4] = &arr;
char s[] = "hello";arr와 &arr는 주소값이 비슷해 보여도 타입이 다릅니다. arr는 보통 첫 원소 포인터처럼 읽고, &arr는 배열 전체를 가리키는 포인터로 읽어야 합니다.
경계 이해
- 배열 이름을 식으로 넘기면 보통 decay가 일어난다.
- 함수 안 길이는 자동으로 따라가지 않으니 항상 별도 인자를 같이 넘긴다.
arr[i]는*(arr + i)와 같은 의미로 읽는다.sizeof(arr)는 같은 스코프 배열에서만 믿고, 매개변수 안에서는 믿지 않는다.&arr는 첫 원소 포인터가 아니라 배열 전체 포인터다.
주의할 점
decay 때문에 함수 안에서 배열 길이를 계산하면 포인터 크기만 보게 됩니다.
void broken(int arr[]) {
size_t wrong_len = sizeof(arr) / sizeof(arr[0]);
}
int data[10] = {0};
// ✅ 길이는 호출하는 쪽에서 계산해 전달참고 링크
1 sources