[C++] 포인터 붕괴(Pointer Decay)

조재훈·2024년 2월 26일

개요

포인터 붕괴를 처음 들어본 사람이면 이게 뭐지? 할 수 있는데 사실 이미 알고 있는 개발자들이 많을 것이다. 용어를 몰랐을 뿐

흔히 C언어를 배울 때 배열과 포인터는 서로 다른 형식이라고 배웠는데 실습을 하면서 배열을 포인터처럼 쓸 수 있었던 경험이 있을 것인데 바로 그런 개념이 포인터 붕괴이다.

int arr[] = {1, 2, 3, 4, 5};

int* parr = arr;

이와 같은 코드를 어디서 본 적이나 쓴 적이 있을 것이다. 이 경우 parr은 어디를 가리키게 될까??
바로 arr 배열의 첫 번째 원소의 주소를 가리키게 된다!

위와 같이 배열의 이름을 포인터처럼 쓸 수 있는 것은 배열이 포인터로 붕괴(Decay)하기 때문이다
그렇다고 해도 배열은 포인터와 같지 않다를 명심하자!

배열의 포인터로의 붕괴

배열은 메모리에 값이 연속으로 배치된 형태의 자료구조로 타입 정보와 크기 정보를 가지고 있다

그래서 sizeof 함수를 사용하면 배열의 크기 정보를 구할 수 있는데 예시를 보자

// arr의 크기를 구한다
// 타입이 int이고 원소가 3개이니 sizeof(int) * 3이 된다
// 12를 출력
int arr[] = { 1, 2, 3 };
std::cout << "arr size : " << sizeof(arr) << std::endl;

// 반면 포인터는 모든 타입의 포인터 크기가 
// 32비트에서는 4, 64비트에서는 8로 고정되어 있다
// 8 출력
int num = 3;
int *pnum = &num; 
std::cout << "pnum size : " << sizeof(pnum) << std::endl;

이제 auto 변수를 배열의 이름으로 초기화해보자(위의 변수 사용)

// 8을 출력한다 -> auto_arr가 포인터 변수임을 알 수 있음
auto auto_arr = arr;
std::cout << "auto arr size : " << sizeof(auto_arr) << std::endl;

auto_arr는 이미 arr의 크기 정보를 잃어버리고 포인터로 변환되었다

함수로 배열을 넘겨줄 때는 어떨까??

void f(int param[3])
{
	cout << "param size : " << sizeof(param) << endl;
}

int main()
{
	...
	f(arr);		// 8을 출력, 타입과 크기를 알려줬는데도 포인터로 붕괴함
}

함수에서 배열을 인자로 받으려면 배열의 크기를 명시적으로 넘기거나, 참조를 사용해 배열을 넘겨야 한다

// [] <- 크기 안넣으면 컴파일 에러, 정확한 크기 넣어야 됨
void g(int(&param)[3])
{
	cout << "&param size : " << sizeof(param) << endl;
}

int main()
{
	...
	g(arr);		// 12 출력
}

예외

포인터로 붕괴가 항상 일어나는 것은 아니며 예외가 있는데 다음의 세 가지이다
1. sizeof 연산자로 배열 크기를 구할 때 : sizeof(arr)
2. & 연산자로 배열의 주소를 구할 때 : ptr = &(arr[2]);
3. 문자 배열 형식을 문자 리터럴으로 초기화할 때 : char str[6] = "hello";

참고

Base Street님 블로그
박승재님 블로그

profile
나태지옥

0개의 댓글