34.2 역참조 연산자 사용하기
포인터 변수에는 메모리 주소가 저장되어 있습니다. 이때 메모리 주소가 있는 곳으로 이동해서 값을 가져오고 싶다면 역참조(dereference) 연산자 *를 사용합니다.
- *포인터
dereference.c
#include <stdio.h> int main() { int *numPtr; // 포인터 변수 선언 int num1 = 10; // 정수형 변수를 선언하고 10 저장 numPtr = &num1; // num1의 메모리 주소를 포인터 변수에 저장 printf("%d\n", *numPtr); // 10: 역참조 연산자로 num1의 메모리 주소에 접근하여 값을 가져옴 return 0; }
실행 결과
10
역참조 연산자 *는 포인터 앞에 붙입니다. 다음과 같이 numPtr 앞에 *를 붙이면 numPtr에 저장된 메모리 주소로 가서 값을 가져옵니다. 여기서는 numPtr이 num1의 메모리 주소를 저장하고 있으므로 num1의 값인 10이 출력됩니다.
printf("%d\n", *numPtr); // 10: 역참조 연산자로 num1의 메모리 주소에 접근하여 값을 가져옴
즉, 포인터는 변수의 주소만 가리키며 역참조는 주소에 접근하여 값을 가져옵니다.
포인터를 선언할 때도 *를 사용하고 역참조를 할 때도 *를 사용합니다. 같은 * 기호를 사용해서 헷갈리기 쉽지만 선언과 사용을 구분해서 생각하면 됩니다. 즉, 포인터를 선언할 때 *는 "이 변수가 포인터다"라고 알려주는 역할이고, 포인터에 사용할 때 *는 "포인터의 메모리 주소를 역참조하겠다"라는 뜻입니다.
int *numPtr; // 포인터. 포인터를 선언할 때 * printf("%d\n", *numPtr); // 역참조. 포인터에 사용할 때 *
이번에는 포인터 변수에 역참조 연산자를 사용한 뒤 값을 저장(할당)해보겠습니다.
- *포인터 = 값;
dereference_assign.c
#include <stdio.h> int main() { int *numPtr; // 포인터 변수 선언 int num1 = 10; // 정수형 변수를 선언하고 10 저장 numPtr = &num1; // num1의 메모리 주소를 포인터 변수에 저장 *numPtr = 20; // 역참조 연산자로 메모리 주소에 접근하여 20을 저장 printf("%d\n", *numPtr); // 20: 역참조 연산자로 메모리 주소에 접근하여 값을 가져옴 printf("%d\n", num1); // 20: 실제 num1의 값도 바뀜 return 0; }
실행 결과
20 20
역참조 연산자는 값을 가져올 수도 있고 값을 저장할 수도 있습니다. 여기서는 *numPtr = 20;과 같이 numPtr에 저장된 메모리 주소에 접근하여 20을 저장했습니다. 따라서 printf로 *numPtr을 출력해보면 20이 나옵니다.
또 한가지 중요한 점은 *numPtr = 20;으로 20을 저장한 뒤 printf로 변수 num1의 값을 출력해보면 20이 나온다는 것입니다. 왜냐하면 numPtr에는 num1의 메모리 주소가 저장되어 있으므로 역참조 연산자로 값을 저장하면 결국 num1에 저장하게 됩니다.
역참조 연산자는 자료형을 바꾸는 효과를 냅니다. 즉, int *numPtr;에서 *numPtr처럼 역참조하면 pointer to int에서 pointer to를 제거하여 그냥 int로 만듭니다(int 포인터 → int).
만약 포인터 numPtr에 변수 num1을 할당한다면 역참조 연산자로 자료형을 맞춰주면 됩니다.
int *numPtr; int num1 = 10; numPtr = num1; // 컴파일 경고, numPtr은 int 포인터형이고 num1은 int형이라 자료형이 일치하지 않음 *numPtr = num1; // *numPtr은 int형이고 num1도 int형이라 자료형이 일치함
이 코드를 컴파일하면 다음과 같은 경고가 표시되며 Visual Studio에서는 화면 하단의 출력 탭에서 확인할 수 있습니다(만약 출력 탭이 없다면 메뉴에서 보기(V) > 출력(O)을 클릭하면 됩니다).
컴파일 결과
warning C4047: '=': 'int *'의 간접 참조 수준이 'int'과(와) 다릅니다.
여기서 int *의 간접 참조 수준이 int와 다르다고 나오는 것은 numPtr과 num1이 서로 자료형이 다르기 때문입니다. 따라서 *numPtr = num1;과 같이 int 포인터를 역참조하면 int와 자료형이 같아지므로 컴파일 경고가 발생하지 않습니다.
물론 주소 연산자 &도 자료형을 맞춰주는 역할을 합니다(&를 붙이지 않으면 앞과 같은 경고가 발생합니다).
int *numPtr; int num1; numPtr = &num1; // numPtr은 int 포인터형이고, &num1은 int형 변수의 주소이므로 자료형이 일치함 // numPtr은 pointer to int, &num1은 address of int이므로 자료형이 일치함
즉, pointer to int와 address of int는 자료형이 같습니다.
이제 변수, 주소 연산자, 역참조 연산자, 포인터의 차이를 정리해보겠습니다.
먼저 변수는 메모리 주소를 몰라도 값을 가져오거나 저장할 수 있습니다. 그냥 변수에 값을 할당하거나 그대로 출력하면 됩니다.
주소 연산자 &는 변수의 메모리 주소를 구합니다. 이 그림에서 10을 감싸고 있는 상자는 메모리 공간을 뜻하는데 주소 연산자는 메모리 공간이 어디에 있는지 위치만 알아낼 수 있습니다.
역참조 연산자 *는 그림에서 보면 상자 안까지 들어가서 값을 가져오거나 저장합니다. 즉, 메모리 주소를 알고 있으므로 메모리 주소를 거쳐서 그 안에 있는 값을 가져오거나 저장합니다.
마지막으로 포인터는 변수의 메모리 주소만 가리킵니다. 따라서 포인터는 메모리 공간이 어디에 있는지 위치만 알고 있습니다.