C 언어에서 포인터는 이해하기 쉽지만은 않다. 그 중에서 내가 공부하며 여러 번 찾아봐야했던 내용을 정리하기로 했다. 이 글에서는 1. 상수 포인터 2. 포인터와 배열의 차이 3. *과 []의 실행 순서가 갖는 의미를 알아보자.
상수 포인터를 통해 아래의 효과를 얻을 수 있다.
첫번째 효과를 얻기 위해서는 const <type> *<변수_이름>의 형태로 선언하면 되고
두번째 효과를 얻기 위해서는 <type> *const<변수_이름>의 형태로 선언하면 된다. (주의: <type> const *<변수_이름>과 같이 * 연산자 앞에 const가 위치하면 두번째 효과가 아니라 첫번째 효과를 얻는다.)
두 효과를 다 얻기 위해서는 const <type> *const<변수_이름>와 같이 위의 두 형태를 섞어서 선언하면 된다.
int main(void)
{
const int *p1 = malloc(sizeof (int));
int *const p2 = malloc(sizeof (int));
p1 = malloc(sizeof (int)); // 에러가 발생하지 않는다.
p2 = malloc(sizeof (int)); // 에러가 발생한다. (두 번째 효과)
*p1 = 5; // 에러가 발생한다. (첫 번째 효과)
*p2 = 5; // 에러가 발생하지 않는다.
}
배열은 많은 경우 포인터로 형변환되어 evaluate된다. 하지만 모든 상황에서 포인터와 동일하게 동작하는 것은 아니다.
마치 상수 포인터처럼, 배열 변수가 가리키는 메모리 주소를 바꿀 수 없다.
sizeof와 & 연산자를 사용할 때, 포인터와 배열은 다르게 동작한다.
sizeof(<포인터>)의 경우, 해당 포인터의 크기(64bit 머신의 경우 8바이트다)로 평가되지만 sizeof(<배열>)은 배열에 담긴 아이템의 개수로 평가된다.
&<포인터>는 포인터 변수의 주소로 평가되지만 &<배열>은 <배열>과 동일하게 평가되는 것처럼 동작한다. '평가되는 것처럼' 동작한다고 말한 이유는, &<배열>과 <배열>을 출력해보면 같은 값이 나오지만 두 대상의 타입이 다르기 때문이다. <배열>은 해당 배열이 담고있는 아이템의 타입 포인터가 되고 &<배열>은 배열 전체를 가리키는 타입을 갖는다 (정확히는 <배열>은 그냥 배열 타입인데 위에서 말했듯이 예외적인 경우를 제외하면 포인터로 형변환된다). 그렇기에 <배열> + n은 배열에 담긴 아이템의 크기(단위: byte) * n만큼 더해지지만 &<배열> + n은 배열의 전체 크기 * n만큼 더해진다. 아래의 예시 코드를 통해 살펴보자.
int main(void)
{
int arr[4];
int *num1;
int (*num2)[4]; // 4 개의 int를 갖는 배열을 가리키는 포인터. 즉 num2는 배열 전체를 가리키는 포인터이다.
num1 = arr + 4;
num2 = &arr + 4;
printf("arr + 4 is %p\n", num1);
printf("&arr + 4 is %p\n", num2);
}
결과:
arr + 4 is 0x7ffeb508ced0
&arr + 4 is 0x7ffeb508cf00
위에서 보듯이 arr + 4와 &arr + 4의 결과 값이 다른 것을 확인할 수 있다.
*과 []는 둘 다 연산자이다. []가 *보다 우선 순위가 높기 때문에 같은 표현식에 두 연산자가 존재하면 []부터 평가한다. 그런데 이 두 연산자가 평가되는 순서에 따라, 동작이 달라진다. 아래의 예시 코드를 살펴보자.
int main(void)
{
int *arr[4]; // int 포인터 4 개를 갖는 배열
int (*arr)[4]; // int 4 개를 갖는 배열을 가리키는 포인터
}
연산자 우선 순위. https://dojang.io/mod/page/view.php?id=188
&<배열>과 <배열>의 차이
https://www.geeksforgeeks.org/whats-difference-between-array-and-array-for-int-array5/
배열을 가리키는 포인터와 포인터를 요소로 갖는 배열. https://modoocode.com/24