31.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의 메모리 주소에 접근하여 값을 가져옴
즉, 포인터는 변수의 주소만 가리키며 역참조는 주소에 접근하여 값을 가져옵니다.
![](https://dojang.io/pluginfile.php/9403/mod_page/content/32/unit34-6.png)
포인터를 선언할 때도 *를 사용하고 역참조를 할 때도 *를 사용합니다. 같은 * 기호를 사용해서 헷갈리기 쉽지만 선언과 사용을 구분해서 생각하면 됩니다. 즉, 포인터를 선언할 때 *는 "이 변수가 포인터다"라고 알려주는 역할이고, 포인터에 사용할 때 *는 "포인터의 메모리 주소를 역참조하겠다"라는 뜻입니다.
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에 저장하게 됩니다.
![](https://dojang.io/pluginfile.php/9403/mod_page/content/32/unit34-7.png)
역참조 연산자는 자료형을 바꾸는 효과를 냅니다. 즉, 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는 자료형이 같습니다.
![](https://dojang.io/pluginfile.php/9403/mod_page/content/32/unit34-8.png)
이제 변수, 주소 연산자, 역참조 연산자, 포인터의 차이를 정리해보겠습니다.
![](https://dojang.io/pluginfile.php/9403/mod_page/content/32/unit34-9.png)
먼저 변수는 메모리 주소를 몰라도 값을 가져오거나 저장할 수 있습니다. 그냥 변수에 값을 할당하거나 그대로 출력하면 됩니다.
주소 연산자 &는 변수의 메모리 주소를 구합니다. 이 그림에서 10을 감싸고 있는 상자는 메모리 공간을 뜻하는데 주소 연산자는 메모리 공간이 어디에 있는지 위치만 알아낼 수 있습니다.
역참조 연산자 *는 그림에서 보면 상자 안까지 들어가서 값을 가져오거나 저장합니다. 즉, 메모리 주소를 알고 있으므로 메모리 주소를 거쳐서 그 안에 있는 값을 가져오거나 저장합니다.
마지막으로 포인터는 변수의 메모리 주소만 가리킵니다. 따라서 포인터는 메모리 공간이 어디에 있는지 위치만 알고 있습니다.