"배열은 '집(House)' 그 자체이고, 포인터는 '주소가 적힌 쪽지(Note)'입니다."
가장 결정적인 차이는 CPU가 데이터를 가져오는 과정(Instruction Cycle)에 있습니다. int a[100];과 int *p;가 있다고 가정합시다.
x = a[0]) : Direct Addressing배열의 이름 a는 컴파일러에게 "아, 그 주소 0x1000번지?"라고 이미 알려져 있습니다. 심볼 테이블(Symbol Table)에 고정되어 있죠.
[ CPU ] --(1. 야, 0x1000번지 내용 내놔)--> [ Memory (0x1000) ]
<--(2. 여기 값 50 있다)----------
MOV EAX, [0x1000]x = p[0]) : Indirect Addressing포인터 p는 변수이므로, 메모리 어딘가(0x2000)에 따로 살고 있습니다.
[ CPU ] --(1. 야, p 변수(0x2000)에 뭐 적혀있냐?)--> [ Memory (0x2000) ]
<--(2. 0x1000번지로 가라고 적혀있는데?)---- [ p에 저장된 값: 0x1000 ]
----(잠깐, 한번 더 가야해?)----
[ CPU ] --(3. 그럼 0x1000번지 내용 내놔)--------> [ Memory (0x1000) ]
<--(4. 여기 값 50 있다)------------------
MOV EDX, [0x2000] ; 포인터 변수 값 로드
MOV EAX, [EDX] ; 그 주소의 값 로드Q: 왜 둘이 똑같다고 착각하게 만들었을까요?
A: "함수 파라미터로 넘길 때의 'Decay(퇴화)' 현상 때문입니다."
C언어 설계자는 효율성을 중시했습니다. 만약 int arr[1000000]이라는 거대한 배열을 함수에 전달할 때, 배열 전체를 복사(Call by Value)한다면 메모리가 터져버릴 겁니다.
그래서 C언어는 규칙을 정했습니다: "배열의 이름을 함수에게 줄 때는, 배열 전체가 아니라 '첫 번째 요소의 주소(포인터)'만 던져준다."
이것을 Array Decay(배열의 포인터 퇴화)라고 합니다. 이 순간부터 함수 내부에서는 배열이 아니라 포인터로 취급됩니다.
이 차이를 모르면 발생하는 치명적인 버그들을 보여드리겠습니다.
void func(int param_arr[]) { // 말이 배열이지, 사실 int *param_arr와 똑같음 (Decay 발생)
printf("%lu\n", sizeof(param_arr));
// 결과: 8 (64bit 시스템에서 포인터 크기) -> 배열 크기(40)가 아님!
}
int main() {
int my_arr[10] = {0};
printf("%lu\n", sizeof(my_arr));
// 결과: 40 (int 4바이트 * 10개) -> 여기선 진짜 배열 크기
func(my_arr);
return 0;
}
Best Practice: 배열을 함수로 넘길 때는 반드시 배열의 크기(length)도 인자로 같이 넘겨야 합니다. 함수 안에서는
sizeof로 원본 크기를 알 방법이 없습니다.
int arr[10];
int *ptr;
ptr = arr; // 가능 (포인터라는 쪽지에 배열 주소를 적음)
arr = ptr; // 에러! (집을 들어서 다른 땅으로 옮길 수 없음)
배열 이름은 L-value(수정 가능한 왼쪽 값)가 아닙니다. 하드웨어적으로 고정된 주소(Label)이기 때문입니다.
int a[5];
int *p = a;
// &a 와 &p는 다릅니다.
// &a : 배열의 시작 주소 그 자체 (값: 0x1000)
// &p : 포인터 변수가 저장된 주소 (값: 0x2000)
하드웨어 레벨을 넘어, 프로그램이 실행될 때 OS와 링커(Linker)가 이 둘을 어떻게 다루는지 조금 더 깊이 들어가 봅시다.
실행 파일(ELF/PE)이 메모리에 로드될 때, 주소 결정 방식이 다릅니다.
전역 배열 (int global_arr[100])
.bss 또는 .data 섹션에 자리를 잡습니다.전역 포인터 (int *global_ptr)
.data 섹션에 위치하지만, 그 안에 들어갈 주소 값은 런타임에 결정됩니다(예: malloc 호출 시).공유 라이브러리(.so/.dll)에서 전역 배열과 포인터를 다룰 때 차이가 극명해집니다.